Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -168,8 +168,10 @@ Novas gravações podem ser pausadas e retomadas com uma preferência persistida
somente no dispositivo; pausar preserva tudo que já foi salvo, enquanto retomar
registra imediatamente a leitura atual quando ela estiver completa.
Leituras parciais nunca substituem uma linha de base completa. A linha do tempo
mostra todas as observações distintas, seus cinco sinais — seguidores, selos,
PRs, estrelas e repositórios — e a mudança desde o estado anterior.
mostra todas as observações distintas, seus seis sinais — seguidores, perfis
seguidos, selos, PRs, estrelas e repositórios — e a mudança desde o estado
anterior. Leituras antigas são migradas sem inventar a contagem de perfis
seguidos quando esse campo ainda não era preservado.

Quando existem pelo menos dois perfis nessa memória, o painel mostra órbitas
recentes ordenadas pela última observação, com um resumo dos cinco sinais e
Expand Down
5 changes: 5 additions & 0 deletions app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ function timelineChangeSummary(changes: AuditChanges | null) {

const signals = [
{ value: changes.followers, label: "seguidores" },
{ value: changes.following, label: "seguindo" },
{ value: changes.visibleAchievements, label: "selos" },
{ value: changes.mergedPullRequests, label: "PRs" },
{ value: changes.topRepositoryStars, label: "estrelas" },
Expand Down Expand Up @@ -195,6 +196,7 @@ function RecentOrbits({
</span>
<span className="recent-orbit-signals" aria-label={`Últimos sinais de ${profile.login}`}>
<span><strong>{timelineMetric(profile.followers)}</strong> seguidores</span>
<span><strong>{timelineMetric(profile.following)}</strong> seguindo</span>
<span><strong>{timelineMetric(profile.visibleAchievementCount)}</strong> selos</span>
<span><strong>{timelineMetric(profile.mergedPullRequests)}</strong> PRs</span>
<span><strong>{timelineMetric(profile.topRepositoryStars)}</strong> estrelas</span>
Expand Down Expand Up @@ -465,6 +467,7 @@ function ProgressHistory({
const changedSignals = memory.changes
? [
{ value: memory.changes.followers, label: "seguidores" },
{ value: memory.changes.following, label: "seguindo" },
{ value: memory.changes.visibleAchievements, label: "conquistas visíveis" },
{ value: memory.changes.mergedPullRequests, label: "PRs mesclados" },
{ value: memory.changes.topRepositoryStars, label: "estrelas no melhor projeto" },
Expand Down Expand Up @@ -596,6 +599,7 @@ function ProgressHistory({
<tr>
<th scope="col">Observação</th>
<th scope="col">Seguidores</th>
<th scope="col">Seguindo</th>
<th scope="col">Selos</th>
<th scope="col">PRs</th>
<th scope="col">Estrelas</th>
Expand All @@ -613,6 +617,7 @@ function ProgressHistory({
{index === 0 ? <small>mais recente</small> : null}
</th>
<td>{timelineMetric(entry.snapshot.followers)}</td>
<td>{timelineMetric(entry.snapshot.following)}</td>
<td>{timelineMetric(entry.snapshot.visibleAchievementCount)}</td>
<td>{timelineMetric(entry.snapshot.mergedPullRequests)}</td>
<td>{timelineMetric(entry.snapshot.topRepositoryStars)}</td>
Expand Down
33 changes: 25 additions & 8 deletions lib/audit-history.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,12 @@ export const MAX_SNAPSHOTS_PER_PROFILE = 12;
export const MAX_TRACKED_PROFILES = 8;

export type AuditSnapshot = {
version: 2;
version: 3;
login: string;
capturedAt: string;
complete: boolean;
followers: number | null;
following: number | null;
visibleAchievementCount: number | null;
mergedPullRequests: number | null;
topRepositoryStars: number | null;
Expand All @@ -23,7 +24,7 @@ export type AuditHistory = Record<string, AuditSnapshot[]>;

export type AuditHistoryBackup = {
format: typeof AUDIT_HISTORY_BACKUP_FORMAT;
version: 2;
version: 3;
exportedAt: string;
history: AuditHistory;
};
Expand All @@ -33,6 +34,7 @@ export type RecentAuditProfile = {
lastObservedAt: string;
observationCount: number;
followers: number | null;
following: number | null;
visibleAchievementCount: number | null;
mergedPullRequests: number | null;
topRepositoryStars: number | null;
Expand All @@ -41,6 +43,7 @@ export type RecentAuditProfile = {

export type AuditChanges = {
followers: number | null;
following: number | null;
visibleAchievements: number | null;
mergedPullRequests: number | null;
topRepositoryStars: number | null;
Expand All @@ -57,6 +60,7 @@ type SnapshotInput = {
profile: {
login: string;
followers: number;
following: number;
publicRepos: number;
};
metrics: {
Expand Down Expand Up @@ -87,15 +91,19 @@ function normalizeSnapshot(value: unknown): AuditSnapshot | null {
const followers = snapshot.version === 1 && snapshot.followers === undefined
? null
: snapshot.followers;
const following = (snapshot.version === 1 || snapshot.version === 2) && snapshot.following === undefined
? null
: snapshot.following;

if (
(snapshot.version !== 1 && snapshot.version !== 2) ||
(snapshot.version !== 1 && snapshot.version !== 2 && snapshot.version !== 3) ||
typeof snapshot.login !== "string" ||
!GITHUB_LOGIN_PATTERN.test(snapshot.login) ||
typeof snapshot.capturedAt !== "string" ||
!Number.isFinite(Date.parse(snapshot.capturedAt)) ||
typeof snapshot.complete !== "boolean" ||
!nullableCount(followers) ||
!nullableCount(following) ||
!nullableCount(snapshot.visibleAchievementCount) ||
!nullableCount(snapshot.mergedPullRequests) ||
!nullableCount(snapshot.topRepositoryStars) ||
Expand All @@ -112,11 +120,12 @@ function normalizeSnapshot(value: unknown): AuditSnapshot | null {
}

return {
version: 2,
version: 3,
login: snapshot.login,
capturedAt: snapshot.capturedAt,
complete: snapshot.complete,
followers,
following,
visibleAchievementCount: snapshot.visibleAchievementCount,
mergedPullRequests: snapshot.mergedPullRequests,
topRepositoryStars: snapshot.topRepositoryStars,
Expand Down Expand Up @@ -176,6 +185,7 @@ function sameSignals(left: AuditSnapshot, right: AuditSnapshot) {
return (
left.complete === right.complete &&
left.followers === right.followers &&
left.following === right.following &&
left.visibleAchievementCount === right.visibleAchievementCount &&
left.mergedPullRequests === right.mergedPullRequests &&
left.topRepositoryStars === right.topRepositoryStars &&
Expand All @@ -188,11 +198,12 @@ export function createAuditSnapshot(audit: SnapshotInput): AuditSnapshot {
const complete = Object.values(audit.sources).every((source) => source === "available");

return {
version: 2,
version: 3,
login: audit.profile.login,
capturedAt: audit.generatedAt,
complete,
followers: audit.profile.followers,
following: audit.profile.following,
visibleAchievementCount: audit.visibleAchievementCount,
mergedPullRequests: audit.metrics.mergedPullRequests,
topRepositoryStars:
Expand Down Expand Up @@ -262,6 +273,7 @@ export function listRecentAuditProfiles(history: AuditHistory): RecentAuditProfi
lastObservedAt: latest.capturedAt,
observationCount: completeSnapshots.length,
followers: latest.followers,
following: latest.following,
visibleAchievementCount: latest.visibleAchievementCount,
mergedPullRequests: latest.mergedPullRequests,
topRepositoryStars: latest.topRepositoryStars,
Expand Down Expand Up @@ -296,12 +308,14 @@ export function serializeAuditTimelineCsv(timeline: AuditTimelineEntry[]) {
"captured_at",
"login",
"followers",
"following",
"visible_achievements",
"merged_pull_requests",
"top_repository_stars",
"public_repositories",
"unlocked_achievements",
"followers_change",
"following_change",
"visible_achievements_change",
"merged_pull_requests_change",
"top_repository_stars_change",
Expand All @@ -315,12 +329,14 @@ export function serializeAuditTimelineCsv(timeline: AuditTimelineEntry[]) {
snapshot.capturedAt,
snapshot.login,
snapshot.followers,
snapshot.following,
snapshot.visibleAchievementCount,
snapshot.mergedPullRequests,
snapshot.topRepositoryStars,
snapshot.publicRepositories,
snapshot.unlockedAchievementSlugs?.join("|") ?? "",
changes?.followers ?? null,
changes?.following ?? null,
changes?.visibleAchievements ?? null,
changes?.mergedPullRequests ?? null,
changes?.topRepositoryStars ?? null,
Expand All @@ -336,7 +352,7 @@ export function serializeAuditTimelineCsv(timeline: AuditTimelineEntry[]) {
export function serializeAuditHistoryBackup(history: AuditHistory, exportedAt = new Date().toISOString()) {
const backup: AuditHistoryBackup = {
format: AUDIT_HISTORY_BACKUP_FORMAT,
version: 2,
version: 3,
exportedAt,
history: mergeAuditHistories({}, history),
};
Expand All @@ -352,7 +368,7 @@ export function parseAuditHistoryBackup(serialized: string): AuditHistoryBackup
!parsed ||
typeof parsed !== "object" ||
parsed.format !== AUDIT_HISTORY_BACKUP_FORMAT ||
(parsed.version !== 1 && parsed.version !== 2) ||
(parsed.version !== 1 && parsed.version !== 2 && parsed.version !== 3) ||
typeof parsed.exportedAt !== "string" ||
!Number.isFinite(Date.parse(parsed.exportedAt)) ||
!history
Expand All @@ -362,7 +378,7 @@ export function parseAuditHistoryBackup(serialized: string): AuditHistoryBackup

return {
format: AUDIT_HISTORY_BACKUP_FORMAT,
version: 2,
version: 3,
exportedAt: parsed.exportedAt,
history,
};
Expand Down Expand Up @@ -434,6 +450,7 @@ export function compareAuditSnapshots(current: AuditSnapshot, previous: AuditSna

return {
followers: delta(current.followers, previous.followers),
following: delta(current.following, previous.following),
visibleAchievements: delta(current.visibleAchievementCount, previous.visibleAchievementCount),
mergedPullRequests: delta(current.mergedPullRequests, previous.mergedPullRequests),
topRepositoryStars: delta(current.topRepositoryStars, previous.topRepositoryStars),
Expand Down
3 changes: 2 additions & 1 deletion tests/audit-history-preference.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,12 @@ import {
} from "../lib/audit-history-preference.ts";

const completeSnapshot = {
version: 2,
version: 3,
login: "octocat",
capturedAt: "2026-07-31T12:00:00.000Z",
complete: true,
followers: 10,
following: 6,
visibleAchievementCount: 2,
mergedPullRequests: 3,
topRepositoryStars: 4,
Expand Down
3 changes: 2 additions & 1 deletion tests/audit-history-storage.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,12 @@ import {

function snapshot(login, capturedAt) {
return {
version: 2,
version: 3,
login,
capturedAt,
complete: true,
followers: 10,
following: 6,
visibleAchievementCount: 2,
mergedPullRequests: 3,
topRepositoryStars: 4,
Expand Down
Loading