Move three derived values to the backend, and stop re-declaring the wire types - #142
Merged
Conversation
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
ReadmePanere-scanned the root dir's children for a file whose stem isreadmeon every manifest change. The scanner resolves it once at manifest-wrap instead.One deviation from the issue: it ships
readmeModifiedas well asreadmePath. 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 becomesAuthorStat.hue; the client keeps only the OKLCH conversion, which is a display concern.colorForAuthortakes a hue now. The fireflies read it off thestatsthey already receive;RightSidebarthreads a name to hue map intoCommitPaneStateexactly as it already doesbusynessThresholds.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 ofcommitLineRanges— so it moves to the bundle ascommitDateRangesand the controller readsrange[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_scanassertscommitDateRanges[HEAD]equals the live manifest'sdateRangesin 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 viacommitLineRanges, and the issue specifies it, but it is visible while dragging.Wire types are derived now, not re-declared
Adding
readmePathneeded edits intypes/manifest.tsandmanifest.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.tshand-wrote 18 interfaces mirroring the generated schemas. They're aliases now. Only theNodeKindenum stays hand-written, plus thetypefield onFileNode/DirNodeandtreeonManifest, which thread through it —NodeKinddeliberately widens the wire's discriminator with scene-only kinds (Gem,Label,Commit).manifest.contract.tsdrops from ~110 lines to the one coupling a derived type genuinely can't check: thatNodeKind.Fileis still assignable to the wire's literal. That's anextendscheck rather than the oldEquals— 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.hueandcommitDateRangesneeded onlyjust gen-types, no hand-written type edit. It also caught real rot —CommitEntry's doc still claimeddatewasYYYY-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