Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
2c93cec
[tm-diff-categories-4vl] Parse and validate diffCategories schema
0101 Jul 27, 2026
500fc39
[tm-diff-categories-f0g] Add bounded glob matcher and file classifica…
0101 Jul 27, 2026
1295e12
[tm-diff-categories-crb] Emit category path and status in diff summar…
0101 Jul 27, 2026
05115c0
[tm-diff-categories-lhq] Resolve repo-root categorization on each sum…
0101 Jul 27, 2026
34f791a
[tm-diff-categories-4hf] Render nested category tree in the diff viewer
0101 Jul 27, 2026
63ac3af
[tm-diff-categories-s0l] Compute disclosure defaults and preserve cat…
0101 Jul 27, 2026
e0893d8
[tm-diff-categories-qo2] Add configure-categories toolbar action
0101 Jul 27, 2026
9507cb7
tm-diff-categories-okj Integrate category disclosure with file intera…
0101 Jul 27, 2026
b5638c2
tm-diff-categories-r5c Fix glob matcher allocation and quadratic star…
0101 Jul 27, 2026
4adc7f1
tm-diff-categories-juy Add node-count and name-length bounds to diffC…
0101 Jul 27, 2026
1feda7f
[tm-diff-categories-2p0] Pin the viewer template's schema prose to th…
0101 Jul 27, 2026
3cc5df1
tm-diff-categories-2jy Pair comparison context and categorization
0101 Jul 27, 2026
1d8ca98
tm-diff-categories-tqa Correct the spec claim that the configure acti…
0101 Jul 27, 2026
706bc79
[tm-diff-categories-3yh] Extract DiffTemplate.html CSS and JS into se…
0101 Jul 27, 2026
ffff5e5
[tm-diff-categories-3yh] Update future improvement scope
0101 Jul 27, 2026
a549d8a
[tm-diff-categories-84m] Extract the categorization fixtures out of D…
0101 Jul 27, 2026
0517d32
tm-diff-categories-dbp Extract the category E2E tests out of DiffView…
0101 Jul 27, 2026
70086a4
[tm-diff-categories-je6] Clean up withCategorizationDefaults in DiffE…
0101 Jul 27, 2026
3930ee9
[tm-diff-categories-3o9] Use TestUtils.withTempDir in the categorizat…
0101 Jul 27, 2026
bfd263e
tm-diff-categories-2ki Extract a shared sequential-summary routing he…
0101 Jul 27, 2026
6ba06ab
[tm-diff-categories-52r] Remove redundant summary request instrumenta…
0101 Jul 27, 2026
c4148a7
[tm-diff-categories-81f] Add end-to-end diff grouping verification tests
0101 Jul 27, 2026
db0887e
Report progress and refresh after the configure-categories action
0101 Jul 28, 2026
aa92936
[tm-diff-categories-wgy.5] Check a repository's diff categories from …
0101 Jul 28, 2026
a588d2c
[tm-diff-categories-wgy.5] Keep diff category spec current
0101 Jul 28, 2026
93be318
[tm-diff-categories-wgy.6] Improve the configure prompt: display orde…
0101 Jul 28, 2026
b66fb29
tm-diff-categories-e8v Flatten single-child category chains into one row
0101 Jul 28, 2026
a83bc7e
[tm-diff-categories-e5j] Show aggregated line stats on category headers
0101 Jul 28, 2026
d8873d9
Address review feedback on diff categories
0101 Jul 29, 2026
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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ tm worktrees # list all worktree
tm add C:\code\my-project # watch a root (applies on next server restart)
tm remove C:\code\my-project # stop watching a root
tm roots # list watched roots
tm categories # report what the repo's diff categories match
```

All commands accept `--port` (default: 5000, env: `TREEMON_PORT`). You can also run `.\tm.ps1` directly from the repo root without installing.
Expand Down
147 changes: 147 additions & 0 deletions docs/spec/diff-file-categories.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docs/spec/future/code-improvements.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ its own worktree. This file is the entry point; detailed designs live in their o
| 10 | **Share the `WorktreeDiffTests` git fixture** — the suite creates a real repository per test in `[<SetUp>]` (`initRepoOnMain`), making it the single largest contributor to Fast-suite runtime. Build the repository once in `[<OneTimeSetUp>]` and give each test its own branch or clone. | — | Idea |
| 11 | **Fast-suite runtime exceeds its documented budget** — AGENTS.md advertises `<60s` for `Category=Fast`; the suite has been several times that for a while, dominated by browser-driven fixtures (`DashboardTests`, `CreateWorktreeServerTests`, `ArchiveTests`) plus #10. Either bring the suite back under budget or correct the figure, because a stale number stops it acting as a gate. | — | Idea |
| 12 | **Pin a SystemView to a chosen session** — SystemView interactions resolve per interaction to the worktree's most recently active live session, so with two agents alternating the target follows whoever spoke last. A user-visible pin would make it sticky. Deliberately out of scope when the resolution rule was adopted: storage would exist solely to hold rare, uncontended overrides. | `docs/spec/canvas-interaction-routing.md` | Idea |
| 13 | **Split `WorktreeDiff.fs` and `DiffTemplate.html`** — both exceed the 1,000-line limit in `review/rules/file-size-limit.md`. Extract the entry parsing and untracked-content handling from `WorktreeDiff.fs` so it owns comparison orchestration and result types only; move the viewer CSS and JS out of `DiffTemplate.html` into embedded assets, leaving an HTML shell and making the renderer independently testable. | `docs/spec/worktree-diff-viewer.md` | Idea |
| 13 | **Split `WorktreeDiff.fs`** — it exceeds the 1,000-line limit in `review/rules/file-size-limit.md`. Extract the entry parsing and untracked-content handling so the file owns comparison orchestration and result types only. | `docs/spec/worktree-diff-viewer.md` | Idea |
| 14 | **Process: route review findings that need a decision to a human** — focused-review marks some findings as needing a product decision rather than a fix. Implementing the reviewer's suggested mechanism instead of answering the question has produced whole subsystems that were later removed. Make that state a stop in the review→fix flow. | — | Idea (process) |


Expand Down
15 changes: 9 additions & 6 deletions docs/spec/worktree-diff-viewer.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
## Expected Behavior

- A non-archived worktree card shows the neutral, icon-only **Diff** action only when the server-provided `HasDiff` signal reports committed, tracked local, or untracked comparison content and the generated `diff.html` SystemView is present in the scanned inventory. `HasDiff` is true only for a comparison that positively found content: it combines the net merge-base-to-`HEAD` diff with a pathspec-safe porcelain check for staged, unstaged, and untracked content that excludes exactly Treemon's generated `.agents/canvas/diff.html`, and an unreadable layer contributes nothing. It is independent of the tracked-only `IsDirty` Sync guard and `WorkMetrics`. Clean, archived, and not-yet-ready cards render no placeholder action. Activation revalidates the SystemView before opening and targeting it; the same view can open in a standalone browser tab.
- The view loads a changed-file summary with every file collapsed until the user explicitly opens one. Changed files form a keyboard-operable accordion with at most one open panel. Explicit pointer or keyboard activation persists that file's stable selection key (change kind plus old/new paths) per worktree; later summary loads, Refresh, layer-filter changes, and page reopen restore and request it only when the newly issued summary contains the same key, even when its opaque identity changed. Missing, invalid, or stale memory leaves every file collapsed with no first-file fallback or file request. Selecting another replaces it, while activating the open header clears the remembered selection, collapses to zero panels, and aborts or ignores its in-flight patch request. The accordion owns vertical scrolling so every header and expanded patch remains reachable. A selection whose layers contain no changed files shows an explicit empty state.
- The view loads a changed-file summary with every file collapsed until the user explicitly opens one. Changed files form a keyboard-operable accordion with at most one open panel; when the repository declares diff categories, the same file rows are nested inside collapsible category sections instead of one flat list, as specified in `docs/spec/diff-file-categories.md`. Explicit pointer or keyboard activation persists that file's stable selection key (change kind plus old/new paths) per worktree; later summary loads, Refresh, layer-filter changes, and page reopen restore and request it only when the newly issued summary contains the same key, even when its opaque identity changed. Missing, invalid, or stale memory leaves every file collapsed with no first-file fallback or file request. Selecting another replaces it, while activating the open header clears the remembered selection, collapses to zero panels, and aborts or ignores its in-flight patch request. The accordion owns vertical scrolling so every header and expanded patch remains reachable. A selection whose layers contain no changed files shows an explicit empty state.
- Three inclusion checkboxes appear above the accordion: **Already committed**, **Local changes**, and **Untracked**. Each always shows its independent file count, even when unselected; a zero-count layer is disabled, while a typed count failure is shown as unavailable rather than as zero. A path touched in multiple layers contributes to each layer's count. First use selects Already committed and Local changes but not Untracked. Later selections persist per worktree in browser storage. Changing a checkbox refreshes only that page instance's summary and identity snapshot.
- Already committed compares the merge base with `HEAD`; Local changes compares `HEAD` with the live tracked worktree and therefore includes staged and unstaged changes; Untracked adds the bounded synthesized additions. Selected layers compose into one summary entry and one patch per path. Selecting Already committed plus Local changes is the merge-base-to-worktree comparison, while a path changed in both layers includes only the selected layers' hunks. With every checkbox cleared, the view shows an explicit filtered-empty state.
- Treemon's generated `.agents/canvas/diff.html` is excluded from both tracked and untracked summary entries, so provisioning the viewer cannot make an otherwise clean worktree appear changed even when `.agents/` is not ignored.
Expand All @@ -23,7 +23,7 @@
- Old and new line numbers remain aligned with their logical diff rows across additions, deletions, context lines, and hunk boundaries. A wrapped row shows each applicable line number once, aligned with the row's first visual line; continuation lines stay inside that row. The two gutters never overlap each other or the code text.
- Line-number gutters use exactly the same monospace font size and line height as code in unified and split layouts.
- Syntax highlighting loads after the plain patch is visible, so highlighting never blocks initial rendering. A successful load applies visible token-level syntax classes to supported code. A failed highlighter load leaves the plain patch usable and a later file selection or view toggle retries the load without requiring a page reload.
- Diff-view toolbar controls are icon-only SVG buttons with accessible labels, tooltips, and preserved pressed-state semantics. Added, modified, deleted, renamed, and untracked entries use colored `+`, `~`, `−`, `→`, and `+` symbols respectively. Added and untracked entries remain distinguishable through semantic classes, accessible labels, and tooltips even though both use `+`.
- Diff-view toolbar controls are icon-only SVG buttons with accessible labels, tooltips, and preserved pressed-state semantics. An embedded view whose canvas transport is available carries one further control, `Analyze repository and configure diff groups`, which asks the worktree's agent session to write the repository's diff categories as specified in `docs/spec/diff-file-categories.md`; it uses the same neutral treatment as the other controls and is not rendered in a standalone top-level tab, which has no pane to receive the message. Added, modified, deleted, renamed, and untracked entries use colored `+`, `~`, `−`, `→`, and `+` symbols respectively. Added and untracked entries remain distinguishable through semantic classes, accessible labels, and tooltips even though both use `+`.
- Renamed entries expose old and new paths. Deleted, binary, oversized, truncated, untracked, and symlink entries have explicit states rather than disappearing or failing silently. Binary and symlink entries expose unavailable line statistics. Tracked text keeps accurate Git numstat counts even when its patch later exceeds the byte or line render limit; untracked unreadable, oversized, or truncated content remains unavailable because its bounded content scan cannot safely produce complete counts. When a tracked deletion is paired with a same-path binary or symlink recreation, the tracked deletion patch remains renderable and is followed by an explicit non-renderable replacement marker; its summary statistics are unavailable because the composed replacement has a non-countable half. Untracked symlinks are never dereferenced.
- Selecting diff text exposes the generic SystemView Explain, Remove, and Comment actions. The payload includes the selected text and structured diff source context—file, hunk header, and old/new line ranges—but omits generic DOM-derived `contextBefore` and `contextAfter` because rendered diff markup makes them noisy and redundant. The file-header action programmatically selects the displayed path, so file-level actions carry the same file identity with null hunk and line ranges.
- A summary with more than 1,000 changed paths returns a `too-many-files` state and no partial file list. A selected file returns at most 2 MiB and 20,000 diff lines. If either capture limit is reached, the server returns an explicit `oversized` or `truncated` state and does not send a partial patch to diff2html. Every Git-backed diff response has a 10-second deadline, including process termination and API response completion, and preserves a timeout as a `timeout` state rather than collapsing it into `git-error`.
Expand All @@ -41,7 +41,7 @@ The canvas server applies a shared loopback-host predicate as middleware before
The card Diff action sets an explicit canvas worktree target while leaving dashboard card focus unchanged. The normal pane behavior still follows the focused card; the next explicit card selection clears this target override.
In-document canvas links resolve and validate their destination against that active pane target, so navigation remains within the targeted worktree when card focus differs.

The summary route is `GET /<encoded-known-worktree>/diff-summary?committed=<bool>&local=<bool>&untracked=<bool>`; omission of all three parameters includes all layers for compatibility, while partial, duplicate, malformed, or unsupported parameters return 400. The browser always sends all three fixed booleans, including its first-use `committed=true&local=true&untracked=false` selection, and never sends refs or paths. The file route remains `GET /<encoded-known-worktree>/diff-file?identity=<opaque-id>` with no other parameters. Both require one valid UUID-valued `X-Treemon-Diff-Viewer` header. Valid semantic results are tagged JSON responses; malformed headers or queries return 400, while unknown worktrees and absent, forged, evicted, removed, or stale identities return generic 404 responses without repository content. Every summary outcome, including filtered-empty, replaces or clears only the requesting viewer's prior identity snapshot, and issued identities retain the selected layer range for later file requests. If the store rejects that replacement or clear because a newer generation has started, the response is tagged `stale` rather than exposing the superseded semantic result or any unusable identities.
The summary route is `GET /<encoded-known-worktree>/diff-summary?committed=<bool>&local=<bool>&untracked=<bool>`; omission of all three parameters includes all layers for compatibility, while partial, duplicate, malformed, or unsupported parameters return 400. The browser always sends all three fixed booleans, including its first-use `committed=true&local=true&untracked=false` selection, and never sends refs or paths. The file route remains `GET /<encoded-known-worktree>/diff-file?identity=<opaque-id>` with no other parameters. `GET /<encoded-known-worktree>/diff-categorization` takes no parameters and answers the categorization block alone, reading and validating the repository configuration without running any Git command, so a viewer waiting for an agent to configure diff groups polls a file read rather than a diff. All three require one valid UUID-valued `X-Treemon-Diff-Viewer` header. Valid semantic results are tagged JSON responses; malformed headers or queries return 400, while unknown worktrees and absent, forged, evicted, removed, or stale identities return generic 404 responses without repository content. Every summary outcome, including filtered-empty, replaces or clears only the requesting viewer's prior identity snapshot, and issued identities retain the selected layer range for later file requests. If the store rejects that replacement or clear because a newer generation has started, the response is tagged `stale` rather than exposing the superseded semantic result or any unusable identities.

`ProcessRunner` is the single process-execution API for data capture — see `docs/spec/process-execution.md` for its argument-list contract and the removal of the string-argument entry points. It provides a monotonic response deadline and bounded stdout/stderr capture; its recursive capture drains streams even after a limit is reached so child processes cannot block on full pipes. The canvas route creates one deadline before known-worktree lookup and passes it through the API and every sequential diff Git call. Diff Git calls use `ProcessStartInfo.ArgumentList`, `--` before paths, NUL-delimited machine output, `--no-ext-diff`, `--no-textconv`, and rename detection.

Expand All @@ -54,7 +54,7 @@ The summary route is `GET /<encoded-known-worktree>/diff-summary?committed=<bool
- Diff content always wraps and never scrolls horizontally. Split view preserves two columns and wraps each side rather than changing layout at narrow widths.
- The file navigator is an at-most-one-open accordion because file paths and content form one browsing flow; users may collapse the current file to leave no patch visible.
- Card and in-view controls use icon-only SVG glyphs with accessible text. File statuses use compact symbols plus semantic color while retaining full accessible labels.
- diff2html 3.4.52 is vendored and served from a versioned immutable local route; no renderer asset is fetched from a third-party origin.
- diff2html 3.4.52 is vendored and served from a versioned immutable local route; no renderer asset is fetched from a third-party origin. The viewer's own stylesheet and script are self-hosted the same way but from stable, unversioned routes, so they are served revalidating rather than immutable — an edited viewer must not be shadowed by a frozen cache entry.
- Over-limit summaries and patches are rejected as explicit states; partial patches are not rendered because an incomplete patch is not reliable input for diff2html.
- Agent-mediated review uses generic SystemView selection interactions rather than renderer-specific comment widgets.
- Diff selection interactions resolve, per interaction, to the most recently active session holding
Expand Down Expand Up @@ -90,15 +90,18 @@ The summary route is `GET /<encoded-known-worktree>/diff-summary?committed=<bool
| `src/Server/WorktreeDiff.fs` | Renderer-neutral diff types and exact live-worktree comparison |
| `src/Server/WorktreeDiffApi.fs` | Opaque identity snapshots, tagged JSON mapping, and guarded diff route handlers |
| `src/Server/CanvasDocServer.fs` | Known-worktree diff data routes and generated view serving |
| `src/Server/DiffAssets.fs` | Versioned self-hosted renderer asset routes |
| `src/Server/DiffAssets.fs` | Self-hosted asset routes: versioned immutable renderer bundle, revalidating viewer assets |
| `src/Server/DiffProvisioner.fs` | Keeps `diff.html` synchronized with the embedded template and gates its visibility on comparison content |
| `src/Server/DiffTemplate.html` | File navigator and diff2html rendering shell |
| `src/Server/DiffTemplate.html` | Viewer document structure; links the renderer bundle and the viewer assets |
| `src/Server/Assets/diff/viewer.css` | Viewer styling: shell, toolbar, file navigator, categories, and diff2html layout overrides |
| `src/Server/Assets/diff/viewer.js` | Viewer behavior: summary loading, file accordion, layers, highlighting, categories |
| `src/Client/CardViews.fs` | Content/readiness-gated worktree-card Diff action |
| `src/Client/CanvasPane.fs` | Diff SystemView tab glyph and hosting |

## Related Specs

- `docs/spec/canvas-interaction-routing.md` — selection actions, source metadata, and interaction-session routing
- `docs/spec/diff-file-categories.md` — per-repository nested grouping of the changed-file accordion
- `docs/spec/canvas-pane.md` — generic SystemView hosting and navigation
- `docs/spec/beadspace-canvas.md` — generated SystemView provisioning and same-origin data pattern
- `docs/spec/worktree-monitor.md` — worktree-card behavior and base-branch resolution
Loading
Loading