Skip to content

Render uncommitted (dirty) changes + live-refresh the whole scene (#74)#111

Merged
thalida merged 24 commits into
mainfrom
feat/issue-74-dirty-changes
Jul 20, 2026
Merged

Render uncommitted (dirty) changes + live-refresh the whole scene (#74)#111
thalida merged 24 commits into
mainfrom
feat/issue-74-dirty-changes

Conversation

@thalida

@thalida thalida commented Jul 20, 2026

Copy link
Copy Markdown
Owner

Closes #74.

Two workstreams under one theme: the city reflects your working tree live.

A — Dirty changes in the scan (backend)

Previously codecity rendered git's committed snapshot, so a file you'd edited but not committed showed its last-commit date, which drives building brightness: freshly-edited work rendered dim/stale, the opposite of reality. And nothing flagged which buildings were dirty.

  • The scanner parses the git status --porcelain -z it already runs into a dirty-path set (single call; tracked paths that differ from HEAD, staged or unstaged; untracked/ignored excluded).
  • A dirty file's modified date is overridden to its working-tree mtime, so it renders at its true recency (bright) through the existing brightness encoding. created is untouched.
  • New wire fields: FileNode.dirty: bool and RepoStats.dirtyFileCount: int (Pydantic + TypedDict + generated + hand TS; cache schema bumped 15→16; the dirty-path set is folded into the scan signature so per-file dirty transitions invalidate the cache).
  • The Overview almanac resurfaces an "Uncommitted — N files" stat (only when count > 0), earning back the marker removed in feat(info): World Almanac — repo metadata in the Info tab (#60) #72.

Enumeration is unchanged (git ls-files): genuinely untracked files stay invisible until you git add or ignore them.

B — Live-refresh the whole scene on content edits (frontend)

With live updates on, a content edit kept the layout-reuse fast path (keyed on structure-only tree_signature), which froze building positions and never rebuilt streets, and the right-sidebar panes read a stale picker snapshot.

  • The layout-reuse gate now keys on a layout signature (structure + per-file size + media dims) instead of structure alone. A content edit takes the full re-pack path (streets + building positions re-solve, colors/heights refresh); a dates-only change or settings Save still reuses.
  • The right-sidebar file/road panes re-derive the selected node from live MANIFEST by path (new findNodeByPath), so they refresh regardless of the picker snapshot.

Behavior changes worth calling out

  • Buildings can move under the camera on a content-changing save (live updates on): that's the intended re-pack. Idle/dates-only polls still reuse, so there's no needless re-pack. The layout packer runs in a Web Worker.
  • repo.dirty narrowed: a repo dirty only with untracked files now reports clean (was: any porcelain output). No consumer relied on the old meaning; it removes a spurious re-scan on untracked-file creation and matches "untracked files are invisible."

Non-goals (possible follow-ups)

Untracked-file rendering; a per-building dirty highlight/outline; per-dirty-file landmarks in the almanac; a per-file "uncommitted changes" line in the FileInfoPane.

Testing

just test-api (294), just test-app (2606), just lint, just gen-types (no-op) all green; layout golden bench byte-identical. Built TDD across 11 commits with per-task review and a whole-branch review.

To try it: open a local repo with uncommitted changes vs. a clean/remote one, live updates on; edit a tracked file and watch its building brighten + the scene re-pack, the almanac count appear, and the selection pane refresh.

🤖 Generated with Claude Code

thalida and others added 12 commits July 19, 2026 17:22
…#74)

Adds the pydantic + TypedDict + TS fields, bumps the manifest cache schema
to v16, and updates the hand-authored fixtures the new required fields
touch (test_models.py, EMPTY_REPO_STATS, and frontend FileNode literals)
so the suite stays green even though the scanner doesn't set `dirty` yet
(Task A3).
Threads the dirty-paths set (Task A1's _collect_dirty_paths) from
scan_tree through _build_tree into _file_node, which stamps
FileNode.dirty and overrides `modified` to the working-tree mtime for
dirty files (their git-history date is stale by definition).
compute_repo_stats now counts file nodes with dirty === true (media
included) into RepoStats.dirtyFileCount, alongside the existing
one-pass superlative accumulation.
Collapse _collect_repo_info to a single `git status --porcelain -z`
call, returning the dirty-path set alongside RepoInfo so scan_tree no
longer walks it twice; the signature-scan path just discards the set.
Also moves test_scan_dirty.py's mid-file imports to the top (E402) and
documents that a dirty file's `modified` is always the working-tree
filesystem date, regardless of git history.
…e test (#74)

Sort children by path before walking in computeLayoutSignature, mirroring
compute_tree_signature's defensive sort in scan.py, so the hash is
deterministic regardless of manifest child order. Also add a test proving
the signature reflects the path SET, not just per-file size/date.
The layout-reuse gate keyed on tree_signature (paths + nesting only), so a
live content edit that changes a file's size (no rename) was wrongly treated
as a no-op and reused the frozen layout — streets/positions never re-solved.
Swap the gate to compare computeLayoutSignature (structure + per-file size)
of the new manifest against the committed one; a size change now bumps
structureRevision (full re-pack), a dates-only change still reuses.

applyManifestScenic.test.ts's manifest fixture lacked a `path` on its tree
root; computeLayoutSignature walks the tree (unlike the old string-field
compare) and needs it, so add it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ture, layout-sig media dims, stale comments, perf) (#74)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@thalida thalida linked an issue Jul 20, 2026 that may be closed by this pull request
thalida and others added 12 commits July 19, 2026 20:31
…er (#74)

The right-sidebar file preview fetched content in an effect keyed only on
fullPath, so a live edit to the still-selected file left stale content until a
re-select. Thread the file's mtime through fileUrl/fetchFileText/fetchFileBytes
as a cache-busting version param and add it to the preview effect deps, so text,
font, and media previews (and the README pane) refresh on save.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…rnaries (#74)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
#74)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…anup (#74)

Co-Authored-By: Claude Sonnet 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…e_signature (#74)

Clarify the three-signature ladder on the manifest wire format: the bare
`signature` field (mtime/size/dirty/repo fingerprint) is easily confused
with `tree_signature`/`layout_signature`. Rename to `content_signature`
and `structure_signature` respectively, document the ladder at the
Manifest model/type, and bump the manifest cache schema version since
the field rename changes the cached shape.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@thalida
thalida merged commit 2d61f43 into main Jul 20, 2026
1 check passed
@thalida
thalida deleted the feat/issue-74-dirty-changes branch July 20, 2026 21:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Explore rendering uncommitted (dirty) changes in the city

1 participant