feat(dev): lab parked-work view (ADR-0017 slice 3) #144

Merged
dominik.polakovics merged 1 commit from afk/136 into main 2026-06-09 08:04:07 +02:00

ADR-0017 slice 3/3: a per-project Parked view to see and clean up the parked lab//afk/ branches and worktrees the guarded teardown keeps (dirty or unmerged).

What's here

  • Collapsed strip — a cheap, local-only parked count on each card, computed on the snapshot/poll path with no tea/network call. gatherParked reuses the reconciliation derivation (managedBranch + ownedBranches): managed branches no live session owns, each paired with its worktree if one exists.
  • Lazy detail (GET /parked/<project>, off the ~4s poll): per entry — branch + kind (lab/ vs afk/<N>), dirty ● / clean ○, commits ahead of mainline, last-commit age, unpushed warning, a best-effort PR badge (one tea ListPulls, Forgejo only), and the copyable worktree path. Fails loud in the strip on an enumeration error.
  • Discard (POST /parked/discard/<project>): force-removes the worktree and deletes the branch regardless of dirty/merged — the one unguarded teardown, bypassing the guarded rule, behind the existing two-step confirm. The branch rides as a form field (names contain /); only managed branches are accepted. Discarding an afk/<N> entry frees the issue (ADR-0013).
  • Morph gains a data-static client-owned-subtree rule so the lazily-fetched body survives background polls, and the strip is keyed so a poll patches it in place instead of rebuilding it (open state + fetched body preserved).
  • Git seam adds read-only stats: CommitsAhead, UnpushedCount (a never-pushed branch's unpushed equals its ahead count), LastCommitTime.
  • CONTEXT.md gains a "Parked work / Parked view" entry.

Verification

  • go test ./..., go vet ./..., go build ./... pass locally (pre-commit is eval-only — it does not build/run Go; the fw dry-build eval passed on commit).
  • Inline JS verified via the ephemeral jsdom approach (ADR-0004, no committed harness): the data-static body survives a poll without being wiped or snapping shut, the keyed strip survives a positional shift (an instance row appearing), and a non-static sibling still morphs.
  • New Go tests cover parked enumeration (live-owned exclusion; lab/+afk/; bare vs worktree-backed), the git helpers (pushed/never-pushed split), Discard-is-unguarded, afk/<N> → claimable, route dispatch, and the template stamping data-static.

Mobile-first (single column, ~44px summary, reuses the .menu/.btn idioms).

Closes #136

ADR-0017 slice 3/3: a per-project **Parked** view to see and clean up the parked `lab/`/`afk/` branches and worktrees the guarded teardown keeps (dirty or unmerged). ## What's here - **Collapsed strip** — a cheap, local-only parked count on each card, computed on the snapshot/poll path with no tea/network call. `gatherParked` reuses the reconciliation derivation (`managedBranch` + `ownedBranches`): managed branches no live session owns, each paired with its worktree if one exists. - **Lazy detail** (`GET /parked/<project>`, off the ~4s poll): per entry — branch + kind (`lab/` vs `afk/<N>`), dirty ● / clean ○, commits ahead of mainline, last-commit age, unpushed warning, a best-effort PR badge (one `tea ListPulls`, Forgejo only), and the copyable worktree path. Fails loud in the strip on an enumeration error. - **Discard** (`POST /parked/discard/<project>`): force-removes the worktree and deletes the branch **regardless** of dirty/merged — the one *unguarded* teardown, bypassing the guarded rule, behind the existing two-step confirm. The branch rides as a form field (names contain `/`); only managed branches are accepted. Discarding an `afk/<N>` entry frees the issue (ADR-0013). - **Morph** gains a `data-static` client-owned-subtree rule so the lazily-fetched body survives background polls, and the strip is keyed so a poll patches it in place instead of rebuilding it (open state + fetched body preserved). - **Git seam** adds read-only stats: `CommitsAhead`, `UnpushedCount` (a never-pushed branch's unpushed equals its ahead count), `LastCommitTime`. - `CONTEXT.md` gains a "Parked work / Parked view" entry. ## Verification - `go test ./...`, `go vet ./...`, `go build ./...` pass locally (pre-commit is eval-only — it does not build/run Go; the `fw` dry-build eval passed on commit). - Inline JS verified via the ephemeral jsdom approach (ADR-0004, no committed harness): the `data-static` body survives a poll without being wiped or snapping shut, the keyed strip survives a positional shift (an instance row appearing), and a non-static sibling still morphs. - New Go tests cover parked enumeration (live-owned exclusion; `lab/`+`afk/`; bare vs worktree-backed), the git helpers (pushed/never-pushed split), Discard-is-unguarded, `afk/<N>` → claimable, route dispatch, and the template stamping `data-static`. Mobile-first (single column, ~44px summary, reuses the `.menu`/`.btn` idioms). Closes #136
A per-project Parked strip surfacing the lab//afk/ branches and worktrees
the guarded teardown keeps (dirty or unmerged), with a per-entry Discard.

- Collapsed strip shows a cheap local-only count on the snapshot path (no
  tea/network). gatherParked reuses the reconciliation derivation
  (managedBranch + ownedBranches) to enumerate managed branches no live
  session owns, each paired with its worktree if one exists.
- Lazy /parked/<project> endpoint (off the ~4s poll) computes the per-entry
  detail: dirty/clean, commits ahead, last-commit age, unpushed warning, a
  best-effort PR badge (one tea ListPulls, Forgejo only), and the copyable
  worktree path. A failing enumeration fails loud in the strip.
- /parked/discard/<project> force-removes the worktree and deletes the branch
  regardless of dirty/merged state — the one unguarded teardown, bypassing
  the guarded rule. The branch rides as a form field (names contain "/") and
  must be managed. Discarding afk/<N> frees the issue (ADR-0013).
- The #live morph gains a data-static client-owned-subtree rule so the
  lazily-fetched body survives background polls, and the strip is keyed so a
  poll patches it in place instead of rebuilding it. Inline JS verified via
  the ephemeral jsdom approach (ADR-0004, no committed harness).
- New read-only git seam stats: CommitsAhead, UnpushedCount (a never-pushed
  branch's unpushed equals its ahead count), LastCommitTime.

CONTEXT.md gains a "Parked work / Parked view" entry.

Closes #136
Author
Owner

This was generated by AI while landing a PR.

Validation: PASS — merging via merge commit; this will auto-close #136.

  • Verification signal: go build ./..., go vet ./..., and go test ./... run against the PR branch — all green (go test ok, 42.7s). The repo's commit-time gate is eval-only and does not exercise the lab Go module, so this is the independent signal.
  • AFK contract: Closes #136 present ✓.
  • Review: the one destructive path — handleDiscarddiscardParked, which force-removes the worktree and force-deletes the branch, bypassing the guarded teardown — is doubly-gated by projectDir (resolves against the scanned project list, no path traversal) and managedBranch (lab///afk/ prefixes only); both are test-covered (TestHandleDiscard_rejectsNonManagedBranch, TestHandleDiscard_unguarded). New git helpers are read-only; per-field stats fail soft, enumeration fails loud. Template innerHTML is used only on the auto-escaped server fragment, error text via textContent. The data-static + data-key="parked" morph contract that keeps the lazily-fetched body alive across polls is Go-test-guarded (TestLivePartial_parkedStrip).
  • Conventions: title is Conventional Commits (feat(dev):); CONTEXT.md updated with the "Parked work / Parked view" domain entry.
> *This was generated by AI while landing a PR.* **Validation: PASS** ✅ — merging via merge commit; this will auto-close #136. - **Verification signal:** `go build ./...`, `go vet ./...`, and `go test ./...` run against the PR branch — all green (`go test` ok, 42.7s). The repo's commit-time gate is eval-only and does not exercise the lab Go module, so this is the independent signal. - **AFK contract:** `Closes #136` present ✓. - **Review:** the one destructive path — `handleDiscard` → `discardParked`, which force-removes the worktree and force-deletes the branch, bypassing the guarded teardown — is doubly-gated by `projectDir` (resolves against the scanned project list, no path traversal) and `managedBranch` (`lab/`//`afk/` prefixes only); both are test-covered (`TestHandleDiscard_rejectsNonManagedBranch`, `TestHandleDiscard_unguarded`). New git helpers are read-only; per-field stats fail soft, enumeration fails loud. Template `innerHTML` is used only on the auto-escaped server fragment, error text via `textContent`. The `data-static` + `data-key="parked"` morph contract that keeps the lazily-fetched body alive across polls is Go-test-guarded (`TestLivePartial_parkedStrip`). - **Conventions:** title is Conventional Commits (`feat(dev):`); CONTEXT.md updated with the "Parked work / Parked view" domain entry.
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Cloonar/nixos!144
No description provided.