3ad8d8bebae68e11d5d2970809b7e1c6bf7a6911
5 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
950d2298a1 |
fix(projects): close the design gaps in the git panel
An audit of the project-detail panel against docs/mockups/project-detail-git.html turned up 59 differences. The ones that changed what you see: Controls that had class names but no rules, so they rendered as raw OS widgets: - .proj-wt-form had no layout at all — the branch input and Create Worktree button sat flush together, and the button jumped to its own line the moment a validation error appeared between them. - .proj-wt-error / .proj-wt-actions-error had no rule, so a validation failure rendered as 16px near-white body text, indistinguishable from content. - .proj-wt-remove (the worktree ✕) and .proj-wt-prune had no rule either. - .proj-fanout-mode never reset `appearance`, so WebKit kept its own light dropdown — the one control in the panel that stayed a native grey widget. - --danger was never defined, so every validation message fell back to an off-palette red. Cascade bugs — a later rule at equal specificity was silently winning: - .proj-wt-row-current's amber wash and border were completely dead. "You are here" rendered identically to every other row. - .proj-wt-branch inherited the 16px user-agent default in the UI sans, making a branch name the largest text on the page. Now 13.5px mono. Layout: - .proj-syncband dropped flex-wrap. Wrapping produced ragged half-empty rows between 721px and 1000px — at 900px the Fetch button dropped alone onto a second row. One cell now absorbs the shrink so the band stays a single row down to the 720px column break. - Commit rows are a 4-column grid, not flex, and the ↑ mark cell is rendered on every row instead of only unpushed ones. Previously no two lines agreed on where the sha started, so the unpushed group did not read as a group. - Pushed commit subjects step down to --text-dim, which is what makes the upstream boundary mean anything. Honesty of the sync band — the design's two hard rules: - Green "✓ in sync" was reachable when ahead/behind were UNKNOWN, because `?? 0` folded undefined into zero. Unknown counts now render "↑ —" / "↓ —" and are marked unverified. Green requires real zeros and a fresh fetch. - A stale ↓ painted the digit warning-red. Since a missing FETCH_HEAD is the default state, the largest glyph in the band was almost always red and the panel read as "this repo is broken". The doubt now sits on the `unverified` chip and the footnote, never on the number. Also: semantic tokens (--sunk/--warn/--ok/--dirty/--mono-font) replacing hex literals, one chip language across the panel, section headings as tracked caps with the count as a separate lighter element, and a ≤720px rule that drops the age column — placed after the base rule, since a media query adds no specificity. Verified in a real browser at 1280/1000/900/780/721/600/420px: the sync band holds one row down to 721px and stacks below it, commit columns align on every row, and nothing overflows or scrolls the page horizontally. |
||
|
|
042c4cd4a9 |
fix(projects): pin the worktree actions right and put the count on the heading
Two layout defects the screenshots caught that the DOM tests could not. 1. `.proj-wt-row` was declared twice — my two-line-card rule earlier in the file, the original flex rule later. Same specificity, so the later copy won and the Open button sat against the text instead of at the card's right edge. Folded the grid into the ORIGINAL rule and left a note where the duplicate was. One rule per selector; a second declaration of a layout property is a silent override waiting to happen. 2. The unpushed count rendered as its own line under "Recent commits". The design has it ON the heading, because it qualifies that heading rather than making a separate statement. renderGitLog now takes the heading element and writes the badge there, clearing any previous badge first so a re-render cannot stack duplicates. Both were invisible to the existing assertions: they checked that the elements exist and carry the right text, which was true in each case. Added a test for the no-duplicate-badge path; the right-edge alignment is a pure CSS outcome and is verified by screenshot, not by the suite. |
||
|
|
f711db9315 |
fix(projects): build the git panel the way the design specifies
The shipped panel was functionally right — every number matched git — but it was not the approved design. I implemented from the plan's semantics (which state is green, when to flag stale) and let the mock's layout drift, and nothing caught it: the tests asserted behaviour, and no assertion covered structure. Three deviations, now closed against docs/mockups/project-detail-git.html. 1. The sync band was compressed into one row of chips. Restored to the four labelled cells: Upstream / To push / To pull / Fetch, with display-size numerals and a footnote under each count. The footnotes are load-bearing, not decoration — "Last fetched 19d ago — this number cannot be trusted" is what tells a reader WHY the zero is suspect. Compressed to "fetched 19d ago", the causal link was left for the reader to infer, and not having to infer it is the entire point of the panel. The unverified state is a chip on the count again, so the doubt attaches to the digit. 2. The working-tree count sat inside the band. Moved to the title line beside the branch, where the design puts it: the band is about the remote, and the dirty count is not. Reads "● 3 uncommitted" instead of a bare dot; the dot remains as the fallback when a server sends no count. 3. Worktree rows were the old single line with the path right-aligned against the chips. Rebuilt as the two-line card the design calls for — identity and state on top, path underneath — plus the Open button, which was specified and simply missing, so a worktree row could be read but not entered. Open reuses the detail view pointed at the worktree path: a linked worktree is a project directory, so this needs no new concept and no new route. Also added the unpushed count beside the commit-list heading, so the number is readable before the eye walks the rows hunting for rails. Tests now assert structure, not just behaviour: cell captions exist, the stale footnote says why, counts render at display size, the path is outside the chip row, and Open fires with its worktree. That is the gap that let the drift through, so it is the gap that is now covered. Verified: tsc and build clean; npm test green (unit 78 files / 2159, e2e 27). |
||
|
|
8fe1f52e5d |
feat(projects): show real git state on the project detail page
The page carried one bare `●` for "dirty" and nothing else, so "do I have
commits I haven't pushed" and "which worktree am I in" still meant dropping
into a terminal. Design mock: docs/mockups/project-detail-git.html; plan and
task breakdown (G1-G7): docs/plans/w6-project-git-panel.md.
One rule drives the whole feature: ahead/behind compare against `@{u}`, a
LOCALLY CACHED remote ref that only a fetch moves. This repo was the live
example while building — `↑9` true, `↓0` false, because FETCH_HEAD had not
moved in 19 days. So:
- `ahead` needs only local refs and is never flagged.
- `behind` is flagged `stale` once FETCH_HEAD is older than an hour.
- Exactly ONE state may render green: ↑0 ↓0 AND a fresh fetch. Green means
"I checked, ignore this"; getting it wrong is lying to the user.
- No upstream (the normal state of a fresh worktree branch) leaves ahead and
behind undefined — it renders an explicit `no upstream`, never the green
path. That fall-through is the easiest bug to ship here.
What landed:
G1 SyncState (upstream/ahead/behind/lastFetchMs/detached) + ProjectDetail.sync
and .dirtyCount. All additive and optional — the Android and iOS clients
decode these shapes. The ahead/behind helper already existed for the list
view; buildProjectDetail had simply never called it.
Fixes a pre-existing bug on the way: readBranch read <repo>/.git/HEAD
directly, so it returned nothing inside a LINKED worktree, where .git is a
file. resolveGitDirs now resolves both the per-worktree gitdir (HEAD) and
the shared common dir (FETCH_HEAD).
G2 POST /projects/git/fetch. Same discipline as push: the remote is derived
server-side and no remote or refspec is ever read from the body, so a
client cannot aim it at an arbitrary URL. Touches refs/remotes only — no
working tree, no index, no merge; it is not a pull. Own rate-limit bucket
so refreshes cannot eat the budget a real push needs. On failure
lastFetchMs is left alone, so the UI keeps saying "stale" instead of
pretending it refreshed.
G3 makeSyncBand replaces the bare dot: upstream name, ↑n, ↓n, stale flag,
dirty count, Fetch button (disabled on a detached HEAD).
G4 The commit list marks unpushed commits and draws the upstream boundary
once, after the last of them. Marking is server-side from `rev-list`,
deliberately NOT "the first N rows": `git log` is date-ordered, so merging
an older branch interleaves unpushed commits BELOW pushed ones, and that
shortcut fails in the dangerous direction — calling an unpushed commit
pushed. A regression test builds exactly that backdated-merge shape.
G5 The worktree section is always "Worktrees (n)" (it used to rename itself
to "Branch" at n=1) and the current row carries its own state chips.
G6 Cost control. The plan called for a .git-mtime cache; that was dropped
during implementation because a fingerprint over HEAD/index/reflog does
NOT move when push updates a remote-tracking ref — the cached `ahead`
would still claim "9 to push" right after a successful push, which is the
exact lie the feature exists to prevent. Replaced with three measures that
cannot go stale: in-flight coalescing (N devices watching one repo cost
one probe, entry dropped as it settles, nothing cached across time),
skipping the re-render when the payload is byte-identical (this also stops
the 5 s re-mount of the commit log, two more git spawns per tick), and
pausing the timer while the document is hidden.
G7 Per-worktree state via GET /projects/worktree/state, kept narrower than
/projects/detail so N rows do not pay for worktree listing and CLAUDE.md
reads nothing renders. Needed an unplanned prerequisite: ProjectSessionRef
carried no cwd, so sessions could not be attributed to a worktree. Added
it, plus countSessionsByWorktree, which matches DEEPEST-first because
.claude/worktrees/<name> lives INSIDE the main checkout and prefix
matching would count every worktree session against the parent repo too.
Out of scope, unchanged: no reset, no checkout, no clean, no rebase, no
force-push. stage/commit/push stay exactly as they were.
Verified: tsc and build clean; 46 new tests.
|
||
|
|
1dd12b035a |
feat(cockpit): quick wins — sync chip, cost budget guard, digest, recent commits (W3)
Four small, high-delight features that turn passive capture into glanceable signals.
- Sync chip on project cards: ahead/behind vs upstream + last-commit time, folded
into the existing concurrent per-repo metadata pass (git rev-list --count
--left-right @{u}...HEAD + git log -1 --format=%ct; no upstream → undefined, no route).
- Cost budget guard: COST_BUDGET_USD env (0 = off); a per-session one-shot latch
(Session.budgetNotified, cost is monotonic so never re-armed) fires a single push
on threshold crossing in manager.handleStatusLine; the already-broadcast telemetry
frame carries the warn (tg-cost-warn styling derived from costUsd>=budget via
/config/ui — no new ServerMessage). web-push title added to sw-push.js.
- "While you were away" digest: GET /digest?since= → {finished, needsInput, stuck,
totalCostUsd, sessions[]} aggregate over manager.list(); FE banner on reconnect.
- Recent commits per project: src/http/git-log.ts (NUL-delimited git log → CommitInfo[]),
GET /projects/log?path= (isValidGitDir), textContent-inert render in project detail.
All git via execFile (no shell) + validated cwd; new routes read-only; commit
messages rendered via textContent. Verified: typecheck + build:web clean, 1904 pass
at --test-timeout=30000 (two default-5s failures are slow-sandbox real-subprocess
timeout flakes — the known ring-buffer test + a new real-git-clone sync test — not
logic regressions).
|