From 7bc4594b94d7c3fac0c4edbd75f8b3721fdd9bae Mon Sep 17 00:00:00 2001 From: nntrivi2001 Date: Thu, 24 Sep 2026 09:54:51 +0700 Subject: [PATCH] fix(lanes,git): harden the lane git surface and fix client races in the Changes panel MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Server (git-diff.js, git-ops.js, worktree.js, routes/lanes.js): - Every lane git call runs with GIT_LITERAL_PATHSPECS=1 and -z porcelain, so a file name is a file name, never a glob, and odd names parse correctly. - A lane that is a subdirectory of its repo sees and acts on ONLY its own paths (reads, stage/unstage/discard/commit, and gitFacts.files). - Any file named settings.local.json (the lane's router API key) is refused as 403 ESECRET on disk (symlinks resolved) and at every committed rev. - commit refuses with 400 ESTAGEDOUTSIDE while anything outside the lane is staged (checked with --no-renames, so a move into the lane is caught). - push resolves its remote (upstream → only remote → origin), else 400 ENOREMOTE; unstaging a rename unstages both halves. - Git writes serialize on the repo's git dir, not the lane id, so two lanes on one index cannot collide on index.lock. - git refusing an action (a numeric exit status) answers 409 EGIT with git's stderr instead of a 500 carrying a numeric code; one shared parseNumstat. Client: - Changes panel: no stale answer from a previous lane or an older quick-open query repaints the panel; discard disarms on lane switch; Enter on a file path opens it instead of folding; bounded tree expand-all. - Large diffs (> 500 rows) render only the visible rows, with stable width and in-sync split columns; mirrored split scroll is clamped. - Git bar: no "in sync" without counts, a clear no-remote message, branch list re-read after a switch, and no previous-lane facts after a lane switch. - Tooltips no longer flicker or close on unrelated scrolls. --- CLAUDE.md | 4 +- client/src/components/Tooltip.tsx | 98 ++- .../src/components/__tests__/Tooltip.test.tsx | 182 ++++- client/src/components/lanes/AddLaneModal.tsx | 22 +- client/src/components/lanes/LaneCard.tsx | 9 +- .../src/components/lanes/LaneChangesPanel.tsx | 304 ++++++--- client/src/components/lanes/LaneDiffView.tsx | 447 ++++++++++-- client/src/components/lanes/LaneGitBar.tsx | 89 ++- .../lanes/__tests__/AddLaneModal.test.tsx | 5 +- .../lanes/__tests__/LaneCard.test.tsx | 11 + .../lanes/__tests__/LaneChangesPanel.test.tsx | 311 ++++++++- .../lanes/__tests__/LaneDiffView.test.tsx | 225 +++++- .../lanes/__tests__/LaneGitBar.test.tsx | 148 ++++ client/src/i18n/locales/en/lanes.json | 6 +- client/src/i18n/locales/vi/lanes.json | 6 +- docs/API.md | 54 +- openapi.yaml | 16 +- .../__tests__/git-diff-ops-hardening.test.js | 639 ++++++++++++++++++ server/__tests__/lanes-api.test.js | 16 + server/lib/git-diff.js | 343 ++++++++-- server/lib/git-ops.js | 104 ++- server/lib/worktree.js | 58 +- server/openapi-extra/lanes.js | 21 +- server/routes/lanes.js | 56 +- 24 files changed, 2832 insertions(+), 342 deletions(-) create mode 100644 server/__tests__/git-diff-ops-hardening.test.js diff --git a/CLAUDE.md b/CLAUDE.md index c83ee206..d47c2328 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,6 +1,6 @@ # Claude Code Working Guide -_Last synced: v1.8.3 (2026-09-23) + `GET /api/settings/info` reporting `server.memory.heapLimit` (V8's `heap_size_limit`), which the Dashboard's V8 Heap bar and Health Score now divide by instead of `heapTotal` + every `claude plugin install|update -y` (`cc-plugin-cli.js`, `skill-plugins.js`, both detached update helpers, `/agenthub-update`) retrying WITHOUT `-y` when an older Claude Code answers `unknown option '-y'` + the Dashboard heatmap ramp mixing `--accent` into `--surface-0` so it reads on every theme + the xterm terminal following the selected DARK theme (`client/src/lib/terminalTheme.ts`, live via `term.options.theme`) and chart colours using structural tokens + the Add-lane modal asking `POST /api/lanes/ensure` with `exact: true` (#42) — without it a directory NESTED inside an existing lane resolves to that parent lane with `created: false`, so the modal closed on the parent and no new card ever appeared; `exact` counts only a lane on that very directory, and a `created: false` answer now names the lane that owns the folder instead of closing. The CLI, the run console and Sagi keep the parent lookup + Settings → **Appearance** (`client/src/components/settings/AppearanceSettings.tsx`: mode Dark/Light/**System** — `prefers-color-scheme`, followed live — plus one NAMED theme remembered per mode: dark Default/Darcula/Apple Dark, light Default/Apple Light/IntelliJ Light/Canvas). A theme is a CSS-variable override block in `client/src/index.css` keyed by `data-theme` on `` (`.dark[data-theme=…]` / `[data-mode="light"][data-theme=…]`, `data-mode` is set on `` too), and `--tint-base`/light `--syn-plain` are now `var(--surface-3)`/`var(--fg-primary)` so no theme restates them; `hooks/useTheme.ts` is a `useSyncExternalStore` reader over `localStorage` (`theme` = `dark|light|system`, `agenthub.theme.dark|light`) that caches NOTHING in the module, so the Sidebar and Settings can never disagree — do not reintroduce per-instance `useState` there; an inline `