Skip to content

Move three derived values to the backend, and stop re-declaring the wire types - #142

Merged
thalida merged 4 commits into
mainfrom
feat/issue-134-readme-path
Jul 25, 2026
Merged

Move three derived values to the backend, and stop re-declaring the wire types#142
thalida merged 4 commits into
mainfrom
feat/issue-134-readme-path

Conversation

@thalida

@thalida thalida commented Jul 25, 2026

Copy link
Copy Markdown
Owner

Closes #134
Closes #133
Closes #135

Three siblings from the #132 backend-calc audit, each a pure function of data the backend already has, plus a type change that fell out of doing them.

#134 — root README

ReadmePane re-scanned the root dir's children for a file whose stem is readme on every manifest change. The scanner resolves it once at manifest-wrap instead.

One deviation from the issue: it ships readmeModified as well as readmePath. The fetch is versioned by mtime so an edited README re-fetches rather than reusing a cached body, and a bare path would have meant either dropping that or a full-tree DFS to recover it — worse than the shallow scan it replaces. So the single field the issue asked for wouldn't have achieved the issue's own goal.

#133 — author hue

The name to hue FNV-1a ran client-side, feeding the firefly orbs and the commit pane's author dot down two separate paths. It's a pure function of a name the backend already dedups into stats.authors, so it becomes AuthorStat.hue; the client keeps only the OKLCH conversion, which is a display concern.

colorForAuthor takes a hue now. The fireflies read it off the stats they already receive; RightSidebar threads a name to hue map into CommitPaneState exactly as it already does busynessThresholds.

The decoration golden passes unchanged — real orb colours are bit-identical. A Python golden pins the hash against values computed with the JavaScript it replaced (Alice=143, Bob=220, plus a unicode name to prove it still hashes UTF-8 bytes and not UTF-16 code units).

#135 — Timeline weathering date ranges

The scrub controller rebuilt the created/modified ms spread over the present file set every frame, walking every union building to do it. It's a deterministic replay over deltas — the date analog of commitLineRanges — so it moves to the bundle as commitDateRanges and the controller reads range[floor(pos)].

Parity was the stated risk, and it had partly resolved itself: the issue warns about day-precision commit dates and Python's naive-datetime-is-local trap, but #138 gave commit dates real ISO-8601 UTC timestamps, so neither applies any more. What remained was mirroring the rule exactly — a file uses its own date once its last change is behind it, the date of its latest change commit before that, and its genesis commit only when it has no created date of its own.

Two guards: test_head_date_range_matches_live_scan asserts commitDateRanges[HEAD] equals the live manifest's dateRanges in ms (the date twin of the existing line-range contract), and the scrubController weathering tests now assert against a fixture mirror of the backend replay, so a divergence in the rule fails them instead of silently flattening colour.

Behaviour note: reading range[floor(pos)] means the ranges snap per commit rather than interpolating mid-scrub. That's what heights already do via commitLineRanges, and the issue specifies it, but it is visible while dragging.

Wire types are derived now, not re-declared

Adding readmePath needed edits in types/manifest.ts and manifest.contract.ts — the drift guard is what caught it, which is the point, but it's a tax on every wire field forever.

types/manifest.ts hand-wrote 18 interfaces mirroring the generated schemas. They're aliases now. Only the NodeKind enum stays hand-written, plus the type field on FileNode/DirNode and tree on Manifest, which thread through it — NodeKind deliberately widens the wire's discriminator with scene-only kinds (Gem, Label, Commit).

manifest.contract.ts drops from ~110 lines to the one coupling a derived type genuinely can't check: that NodeKind.File is still assignable to the wire's literal. That's an extends check rather than the old Equals — a string-enum member is its own type in TypeScript and never identical to the bare literal.

This paid off inside this very PR: adding AuthorStat.hue and commitDateRanges needed only just gen-types, no hand-written type edit. It also caught real rot — CommitEntry's doc still claimed date was YYYY-MM-DD, untrue since #138. Field prose now comes from the Pydantic descriptions, so it can't drift separately again.

Caches

Manifest schema v21 (readmePath/readmeModified) then v22 (AuthorStat.hue); timeline v7 (commitDateRanges). First load after merge re-scans.

Full gate green via the pre-push hook: 365 pytest, ruff, 2830 vitest, eslint, prettier, typecheck. Verified locally against mounted repos.

🤖 Generated with Claude Code

thalida added 4 commits July 25, 2026 15:44
ReadmePane re-scanned the root dir's children for a file whose stem is
"readme" on every manifest change. It's a pure function of the tree, so
the scanner does it once at manifest-wrap instead.

Ships readmeModified alongside readmePath: the fetch is versioned by
mtime so an edited README re-fetches rather than reusing a cached body,
and a bare path would have meant either losing that or a full-tree DFS
to recover it — worse than the shallow scan it replaces.

Manifest schema cache bumped to v21.

Closes #134
types/manifest.ts hand-wrote 18 interfaces that had to mirror the
generated schemas exactly, with manifest.contract.ts asserting they
matched. Adding readmePath needed an edit in both files, and the guard
is what caught it — which is the point, but it's a tax on every wire
field forever.

They're aliases now. FileNode and DirNode keep only their `type` field,
since NodeKind widens the wire's discriminator with scene-only kinds
(Gem, Label, Commit); Manifest keeps only `tree`, which threads through
that. Everything else is Schemas['X'].

The contract file drops to the one coupling a derived type can't check:
that NodeKind.File is still assignable to the wire's literal. That's an
`extends` check, not the old Equals — a string-enum member is its own
type in TS and never identical to the bare literal.

CommitEntry's doc claimed `date` was day-precision, which stopped being
true in #138. Field prose now comes from the Pydantic descriptions, so
it can't rot separately again.
The name->hue FNV-1a ran client-side, feeding both the firefly orbs and
the commit pane's author dot from two separate call paths. It's a pure
function of a name the backend already dedups into stats.authors, so it
moves to AuthorStat.hue and the client keeps only the OKLCH conversion,
which is a display concern.

colorForAuthor now takes a hue instead of a name; the fireflies read it
off the stats they already receive, and RightSidebar threads a name->hue
map into CommitPaneState the way it already does busynessThresholds.

The decoration golden passes UNCHANGED, which is the point: real orb
colors are bit-identical, and a Python-side golden pins the hash against
values computed with the JavaScript it replaced (Alice=143, Bob=220, and
a unicode name to prove it still hashes UTF-8 bytes, not UTF-16 units).

Manifest schema cache bumped to v22.

Closes #133
The scrub controller rebuilt the created/modified ms spread over the
present file set on every frame, walking every union building to do it.
It's a deterministic replay over deltas, the date analog of
commitLineRanges, so it moves to the bundle as commitDateRanges and the
controller reads range[floor(pos)].

Parity was the risk, and #138 had already removed the worst of it: commit
dates now carry a real time, so there's no day-precision-vs-full-ISO
mixing and no naive-datetime-is-local trap left. The backend mirrors the
client rule exactly: a file uses its own date once its last change is
behind it, the date of its latest change commit before that, and its
genesis commit only when it has no created date of its own.

Two guards. api/tests/test_timeline.py asserts commitDateRanges[HEAD]
equals the live manifest's dateRanges in ms, the date twin of the
existing line-range contract. The scrubController tests keep asserting
real weathering against a fixture mirror of the backend replay, so a
divergence in the rule fails them rather than silently flattening color.

Timeline cache bumped to v7.

Closes #135
@thalida thalida linked an issue Jul 25, 2026 that may be closed by this pull request
@thalida
thalida merged commit 57f3360 into main Jul 25, 2026
1 check passed
@thalida
thalida deleted the feat/issue-134-readme-path branch July 25, 2026 20:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant