Skip to content

chore: sync upstream-fanatics → personal 2026-05-02 - #47

Merged
tstapler merged 70 commits into
mainfrom
sync/work-to-personal-20260502
May 2, 2026
Merged

chore: sync upstream-fanatics → personal 2026-05-02#47
tstapler merged 70 commits into
mainfrom
sync/work-to-personal-20260502

Conversation

@tstapler

@tstapler tstapler commented May 2, 2026

Copy link
Copy Markdown
Owner

Summary

Merges 50 commits from TylerStaplerAtFanatics/stapler-squad into tstapler/stapler-squad to bring the personal fork up to date with work.

Key changes from work fork

  • feat(unfinished): Unfinished Work tab surfacing pending git commits across worktrees with session matching and multi-session picker
  • feat(tmux): Priority CM sender for low-latency input forwarding
  • feat(file-tree): Performance, themes, and file browser enhancements
  • feat(ux): Mobile nav, editable session info, status recovery
  • feat(squad-ux-polish): Batch session creation, RunOneShot PR creation
  • feat(sessions): One-off session creation with auto-generated directories
  • feat(unfinished): Session matching + wizard for linking worktrees to sessions
  • fix(server): Use poller cache for WatchSessions; block on PTY pause
  • fix(zombie): Eliminate forbidigo violations, add zombie reaper worker
  • fix(fork-limit): 6 fixes for macOS process fork exhaustion with 30+ sessions
  • chore(ci): Switch to release-please for on-demand releases (v1.23.1, v1.24.0, v1.25.0)

Conflict resolutions

  • Benchmark baselines: accepted work fork versions (more recent)
  • Registry JSONs: kept personal fork versions (newer lastModified timestamps, same content)
  • OmnibarCreationPanel.tsx: merged both — kept personal's image upload UI + work's isWorktreesLoading and one-off info banner
  • Makefile: merged both — added work's unfinished.proto registry scan
  • Code conflicts: merged carefully, preserving both sides' changes

Pair PR

The reverse sync (personal → work) is tracked in TylerStaplerAtFanatics/stapler-squad.

TylerStaplerAtFanatics and others added 30 commits April 25, 2026 10:18
…connect (#80)

* feat(terminal): cache cell pixel metrics for instant pre-sizing on reconnect

Extends the dimension cache (localStorage) to persist cellWidth/cellHeight
(pixels per column/row from xterm's renderService) alongside cols/rows.

On reconnect the init effect reads these metrics, measures the container via
getBoundingClientRect(), and pre-calculates cols/rows before xterm fires its
first onResize. The session-switch effect reads the pre-populated lastResizeRef
and calls connect() immediately — eliminating the 50 ms stability wait for
returning users entirely.

Fallback chain preserved:
  pre-size (cell metrics + valid container) → fast-connect (cached dims only)
    → stability wait (no cache)

Guards:
- isFinite() + falsy-check on cell dims from private API
- MIN_COLS=30 / MIN_ROWS=10 on both the calculated result and cache write
- Zero-size container skips pre-sizing silently

Tests: 20 passing in TerminalOutputBug (cell extraction, partial metrics,
floor division, zero-size, fallback paths); 5 passing in XtermTerminalBug.

* feat(terminal): add feature registry markers and pre-sizing E2E coverage

- Add // +feature: terminal-pre-sizing terminal-dimension-cache marker to TerminalOutput.tsx
- Register both features in docs/registry/frontend-features.json with test IDs
- Add E2E test verifying cellWidth/cellHeight are persisted in localStorage cache
  after terminal initialises (validates the pre-sizing feature end-to-end)

* test(e2e): add full-cycle tmux roundtrip integration tests

Exercises the complete browser→WebSocket→Go→tmux→PTY→shell→output→browser
loop. Verifies:
  1. Echo roundtrip — typed command output appears in tmux capture-pane
  2. TUI rendering — top -b -n 1 fills the pane with structured output
  3. Large scrollback — seq 1 2000 completes and terminal stays responsive

Tests run against the real stapler-squad server (port 8543) and use
tmux capture-pane from Node.js to read PTY output directly, bypassing
the WebGL renderer where DOM text is unavailable.

Both tmux availability and server reachability are guarded in beforeAll;
tests skip gracefully when either prerequisite is missing.

* test(e2e): add DOM-renderer project + window resize test

Adds a chromium-dom Playwright project that disables WebGL entirely via
--disable-webgl --disable-3d-apis.  With WebGL off, XtermTerminal.tsx's
existing guard (typeof WebGL2RenderingContext !== 'undefined') skips the
WebglAddon load, falling back to xterm.js's built-in DOM renderer.  Text
appears in real .xterm-rows > div spans and is directly assertable.

Changes:
- playwright.config.ts: add chromium-dom project (no WebGL)
- tmux-roundtrip.spec.ts:
  - Add readRenderedText() and measureXtermCols() helpers
  - TUI rendering test: assert on DOM rows when chromium-dom is active
  - New window resize test: shrinks viewport 40%, verifies xterm cols
    decrease, then verifies PTY (tput cols) matches via tmux capture-pane

---------

Co-authored-by: Tyler Stapler <tystapler@gmail.com>
…ies (#81)

* feat(sessions): add one-off session creation with auto-generated directories

Introduces a new "One-off" session mode that creates a fresh directory
with a YYYYMMDD-adjective-noun-NN name (e.g. 20260424-brave-falcon-07)
under a configurable base path (default ~/oneoff), then starts a
directory-type Claude session inside it.

- session/namegen: new package with 80×80 word lists, Generate(),
  GenerateAndCreate(), and GenerateAndCreateWithFn() for testable
  collision-retry logic using os.Mkdir atomicity
- config: OneOffBaseDir field + OneOffBaseDirOrDefault() helper with
  tilde expansion and ~/oneoff default
- proto: bool one_off = 14 in CreateSessionRequest
- session_service: relax path-required guard, generate+create dir on
  one_off=true, force SessionTypeDirectory
- frontend: One-off radio in OmnibarCreationPanel, path input hidden,
  oneOff flag threaded through OmnibarContext → useSessionService

* chore(registry): register one-off session feature + add e2e test + registry rules

- docs/registry/: add backend-features.json, frontend-features.json,
  coverage-gaps.json, schema.json, README.md to worktree; mark
  session:create as tested with one-off test IDs; add session-create-one-off
  frontend entry
- tests/e2e/one-off-session.spec.ts: Playwright e2e tests for the one-off
  creation flow (UI option visible, path hidden, one_off flag sent)
- .claude/rules/feature-registry.md: Claude rule mandating registry updates
  and e2e tests for all future feature PRs

* feat(omnibar): wire one_off flag through dispatch action + add session creation rules

* chore(registry): split session creation into one entry per mode

* feat(tmux): bundle pinned tmux binary with go:embed support

- Add git submodule at third_party/tmux (pinned to 3.4) with Bazel
  BUILD.bazel for cached artifact builds
- Add scripts/build-tmux.sh to build tmux from source with auto-dep
  install on macOS/Linux
- Route all tmux exec calls through Binary() which reads TMUX_BIN env
  var, falling back to system "tmux"
- Add embed_tmux build tag: go build -tags embed_tmux bundles the
  binary into the stapler-squad binary via //go:embed; extracted to
  ~/.cache/stapler-squad/tmux/ at runtime
- Wire CI to build pinned tmux, cache by configure.ac hash, run tests
  with TMUX_BIN pointing at the pinned binary
- Add CreateSession backend tests covering all session type invariants

* fix(ci): use system tmux in CI; document embed_tmux build in CLAUDE.md

The third_party/tmux submodule requires git submodule add to register
the gitlink in the index before it can be cloned in CI. Until that is
done, use system tmux (apt-get install tmux) which is simpler and
already reliable. The make test-with-pinned-tmux target provides
reproducible pinned-binary testing for local use.

Also documents build-tmux / build-embedded / TMUX_BIN workflow in
CLAUDE.md so contributors know how to use the new bundled-binary
feature.

* fix(ci): build pinned tmux 3.4 from source in CI with clone fallback

- Restore build-from-source CI steps (install deps, cache binary keyed
  to tmux-3.4-$runner.os-v1, build only on cache miss, TMUX_BIN wired)
- Fix build-tmux.sh submodule detection: check for mode 160000 gitlink
  in the index rather than 'git submodule status' which exits 0 even
  when the submodule is not registered
- Fix git clone into existing non-empty dir: clone to mktemp, then
  cp -rn to preserve our BUILD.bazel

* fix(tests): match tmux by basename in mock executor to support TMUX_BIN full path

When TMUX_BIN points to an absolute path (e.g. CI sets it to
$(pwd)/bin/tmux), cmd.Args[0] is the full path, not "tmux". The mock
executor was doing an exact-string match and falling through to the
"unexpected command" error path.

Use filepath.Base() so the mock accepts both the bare command name and
any absolute path to a tmux binary.

---------

Co-authored-by: Tyler Stapler <tystapler@gmail.com>
…hanges (#82)

* docs: Add feature plan for tmux-subprocess-optimization

* perf(phase1): TTL caching for IsDirty, CheckGHAuth, and Preview

Eliminates the majority of subprocess forks from the 138/s hotspot identified
by execution trace profiling. Three independent cache layers:

- IsDirty(): 15s RWMutex-protected TTL cache with skip-when-Claude-active hint.
  Adds InvalidateDirtyCache() called after commits so state is immediately clean.
- CheckGHAuth(): atomic.Value fast path + singleflight.Group on expiry (5min TTL).
  Eliminates 2.02s cumulative mutex delay — concurrent callers coalesce to one subprocess.
- Preview(): 500ms TTL cache in ReviewQueuePoller for non-controller sessions.
  Previously uncached; now at most 2 capture-pane calls/second regardless of session count.

Tests: fixed two IsDirty cache tests to call InvalidateDirtyCache() after writing
files directly to disk (mirrors real-world usage where external changes need explicit
cache busting). TestSessionRecoveryWorkflowEnd2End pre-existing failure, unaffected.

* feat(unfinished): add Unfinished Work tab for surfacing pending git changes

Adds a dedicated top-level tab that aggregates unfinished work across all
repos the user is actively working in — uncommitted changes, commits ahead of
main, and branches behind main — so there's one place to answer "what should
I pick up next?"

Sources (all three active simultaneously):
- Auto-spider: detects repo root of every active session's worktree and
  enumerates all worktrees via git worktree list --porcelain
- Watch dirs: user-configured root directories recursively scanned for git
  repos (fsnotify on .git/ dirs, depth-5 walk, periodic re-walk fallback)
- Pinned repos: manually added specific repo paths

Backend (Go):
- session/unfinished/scanner.go: 4-worker pool with circuit breaker and
  30s TTL cache; two-command scan (git status --porcelain +
  git rev-list --left-right --count HEAD...main)
- session/unfinished/watcher.go: fsnotify watching .git/ dirs only
- session/unfinished/state.go: dismiss/snooze persistence keyed by
  (repoPath, branchName); AI summary cache by diff hash; atomic writes
- session/unfinished/cache.go: 30s TTL cache
- server/services/unfinished_work_service.go: ConnectRPC service with
  10 RPCs; AI summary via lazy claude subprocess with semaphore (size 2)

Frontend (React/TypeScript, vanilla-extract CSS):
- Unfinished tab in Header and BottomNav with live badge count
- Repo-grouped item list with status chips (Uncommitted / ahead / behind)
- Inline accordion: diff stats, commits ahead, [View Files] [Open Session]
- Actions: Open Session, Commit & Push modal, Dismiss, Snooze, AI Summary
- Settings page: watch dir and pinned repo management

Proto: new unfinished.proto with UnfinishedWork message and 10 RPCs
Tests: 36 unit tests covering scanner and state store

* fix: remove unused event constructor and fix compound pseudo-selector in CSS

* fix: use sync.Once to prevent double-close panic in tmux registry, fix ConnectionIndicator contrast

---------

Co-authored-by: Tyler Stapler <tystapler@gmail.com>
…feature flag

Adds the control mode command dispatch layer (Phase 2 of tmux-optimization):

- cmdResult type + ErrControlModeNotRunning/ErrControlModeStopped sentinel errors
- cmCommandsEnabled atomic.Bool gated by STAPLER_SQUAD_CM_COMMANDS=true
- Extended processControlModeLine() state machine: %begin pops pendingCmds FIFO,
  body lines accumulate in cmdBodyBuf, %end/%error deliver to curCmdCh
- %output events broadcast normally even inside a %begin/%end block (R6 correctness)
- sendCMCommand(ctx, args...) serializes enqueue+write under cmdSendMu so tmux
  receives commands in the same order channels enter the FIFO queue
- StopControlMode uses cmdSendMu when closing stdin (races with sendCMCommand)
- EOF handler in readControlModeOutput drains curCmdCh and all pendingCmds with
  ErrControlModeStopped so no goroutines leak on control mode exit
- GetPaneDimensions migrated as the pilot: CM path with 3s context timeout,
  subprocess fallback on error or when CM is not running
- 12 new unit tests cover: single command, multi-line body, FIFO ordering under
  concurrent goroutines, error propagation, %output during response, nil fallback,
  stop drains, double-%begin reset, feature flag off/on paths
All tmux query functions now attempt the control-mode stdin path first
when STAPLER_SQUAD_CM_COMMANDS=true and control mode is running:

- CapturePaneContent / CapturePaneContentRaw / CapturePaneContentWithOptions
- GetCursorPosition / GetPaneCurrentPath / GetPanePID
- RefreshClient / SetWindowSize

Each falls back to the existing subprocess path on CM failure, preserving
identical behavior when the flag is off or control mode is not running.

Added cmEnabled() and cmCtx() helpers to reduce boilerplate at each site.
…pty, snap on activity

When the review queue has no items and an activity channel is wired, pollLoop
backs off from the 2s fast interval to SlowPollInterval (8s default), reducing
background subprocess activity by ~75% during quiet periods.

ReactiveQueueManager now creates an activityCh and wires it to the poller via
SetActivityChannel before Start(). On EventApprovalResponse or
EventUserInteraction the channel receives a non-blocking signal, causing pollLoop
to reset to the fast interval immediately — so any follow-up prompt surfaces
within 2s instead of waiting up to 8s.

Timer-based implementation (time.NewTimer + Reset) replaces the fixed Ticker,
allowing the interval to change at runtime without restarting the goroutine.

Tests:
- TestAdaptivePoller_BackoffToIdleInterval (R10)
- TestAdaptivePoller_SnapOnApprovalResponse (R11)
- install-service.sh: profiling enabled by default on :6060; add --no-profile and --profile-port flags
- Makefile: forward NO_PROFILE and PROFILE_PORT vars to install-service.sh
- Add BUG-018 through BUG-021 tracking docs for gob persistence, flate writer pooling, and mutex contention hotspots
- Add tmux-optimization project plan
…essions

Diagnosed kern.maxprocperuid pressure from high subprocess creation rate.
All fixes reduce concurrent fork count without sacrificing correctness.

Fix 1 (tmux/tmux.go): existsCacheDefaultTTL 500ms → 5s
  Subprocess fallback path only; push-based registry fast-path is unaffected.
  Reduces thundering-herd forks after tmux server restart (30 sessions × 2/sec → 1/10s).

Fix 2 (mux/multiplexer.go): exec.CommandContext + 2s timeout in monitorTmuxSessionPolling
  Prevents indefinitely-hung subprocess from consuming a process slot.

Fix 3 (tmux.go, tmux_process_manager.go, instance.go): eliminate double CapturePaneContent
  TmuxSession.HasUpdated() now returns content alongside updated/hasPrompt booleans.
  Instance.HasUpdated() uses the returned content directly instead of a second capture-pane call.

Fix 4 (control_mode.go): enable CM command dispatch by default (opt-out with =false)
  CM dispatch sends commands over the existing stdin pipe, eliminating one subprocess fork
  per tmux command across all 9 call sites.

Fix 5 (external_tmux_streamer.go): track drainStderr goroutine in WaitGroup
  Prevents a goroutine leak on control-mode restart; drainStderr now calls wg.Done().

Fix 6 (review_queue_poller.go): concurrency semaphore (N=5) in checkSessions()
  Converts sequential session check loop to bounded-concurrent goroutines.
  Caps simultaneous capture-pane subprocesses at 5 regardless of session count.
  Includes adaptive poller test robustness fix (poll for first tick instead of fixed sleep).
…, Project entity, prompt history (#77)

* feat(session): implement tmux session robustness improvements

- Add circuit breaker to executor layer for fault-tolerant tmux operations
- Add health checker with debounce threshold for session recovery
- Add tmux control mode streaming via -C attach (replaces pipe-pane/FIFO)
- Add review queue poller with improved error handling and backoff
- Add structured response stream with PTY-EOF detection callback
- Add external approval/streamer/tmux-streamer session types
- Add session_streamer ConnectRPC service for real-time terminal output
- Extend log package with per-session tagged logger (log.ForSession)
- Update tmux.TmuxSession with exit callback, onExitOnce, and control mode fields
- Add git diff context lines support

* refactor(session): extract TmuxManager/GitManager interfaces, fix encapsulation

- Add TmuxManager interface to TmuxProcessManager with 6 new delegation methods
  (SetOnExitCallback, ResetExitOnce, StartControlMode, StopControlMode,
   SubscribeToControlModeUpdates, UnsubscribeFromControlModeUpdates)
- Add GitManager interface to GitWorktreeManager with compile-time check
- Fix two swallowed transitionTo() errors in exit callbacks: log warning instead
  of _ = i.transitionTo(Stopped) at instance.go lines ~837 and ~2384
- Fix private field accesses in instance.go and health.go:
  i.tmuxManager.session -> Session(), instance.gitManager.worktree -> HasWorktree()
  instance.tmuxManager.session = ... -> SetSession(...)
- Simplify StartControlMode/StopControlMode/Subscribe/Unsubscribe to
  single-line delegation through TmuxProcessManager
- Add TestHealthCheckerDebounce: verifies 2-cycle debounce without real tmux
- Add TestLifecycleCallbackConcurrency: 20 concurrent goroutines, no panics
- Add TestTransitionToErrorInCallback: validates Stopped->Stopped returns error

* feat(squad-ux-polish): batch session creation, RunOneShot PR creation, Project entity, prompt history

Implements the squad-ux-polish feature set:

**S1 - Prompt History Store**
- Add PromptStore (session/prompts/store.go): SHA-256 IDs, ring-buffer capped at 500 entries,
  atomic write via os.Rename, sorted by last-used desc
- Wire ListPromptHistory and DeletePromptHistory RPCs
- Record initial_prompt on CreateSession when field is non-empty

**S2 - Batch Session Creation**
- Add BatchCreateSessions RPC: per-repo mutex via sync.Map, semaphore bounded at 3 concurrent
  workers, partial failure isolation (failed items don't cancel rest)

**S3 - RunOneShot PR Creation**
- Add RunOneShot RPC: runs `claude -p <prompt>` as subprocess with 120s default / 300s max
  timeout, extracts PR URL from output, persists GitHubPRURL back to session storage
- Add checkBranchDivergence helper (git rev-list --count origin/HEAD..HEAD)
- Wire "Create PR" button on SessionCard; surfaces error state ("❌ Failed – Retry?")

**S4 - Project Entity**
- Add Project ent schema with nullable FK edge on Session (SetNull on delete)
- Add ProjectService CRUD (CreateProject, ListProjects, UpdateProject, DeleteProject,
  AssignSessionsToProject)
- Add GroupByProject strategy in web UI grouping (hidden from dropdown until ProjectPanel UI
  is built to avoid confusing "No Project" single-bucket state)

**Build fixes**
- Remove leftover InitialPrompt references from ent_repository.go after CLAUDE.md injection removal
- Remove orphaned CLAUDE.md Prompt JSX block from SessionWizard.tsx
- Fix strategies.test.ts: migrate new Session({}) → create(SessionSchema, {}) for protobuf-es v2

* style: gofmt session_service.go and instance.go

* fix(ent): regenerate with --feature sql/upsert and correct one_shot field index

runtime.go was using sessionFields[24] (initial_prompt) for one_shot default,
causing a panic on init. Regenerated using the correct command from generate.go:
  go run -mod=mod entgo.io/ent/cmd/ent generate --feature sql/upsert ./schema

This restores OnConflictColumns on ApprovalRuleCreate and fixes the index to 25.

* docs(CLAUDE.md): document correct ent generate command with --feature sql/upsert

* ci: trigger build for ent fix

* ci: add regeneration note to runtime.go to trigger build

* ci: add workflow_dispatch to Build workflow

* ci: add workflow_dispatch to Lint workflow

* fix(ui): correct actionButton reference in SessionCard; sync ent after merge

* feat(squad-ux-polish): Phase D UI + Phase E E2E tests

Phase D UI (S1-5, S3-3, S4-3/4/5, S5-1/2):
- S5-1: Auto-title from repo basename + random suffix in SessionWizard
- S5-2: Terminal session preset (program selector + hides prompt fields)
- S1-5: InitialPrompt textarea + recent-prompts dropdown + file picker
- S3-3: Create PR button + confirmation modal in ReviewQueuePanel
- S4-3: GroupByProject strategy in grouping/strategies.ts + programs.ts
- S4-4: Multi-select checkboxes + BulkActions "Group as..." toolbar
- S4-5: Project group headers with inline rename/delete in SessionList
- sessionSchema.ts: GitHub URL validation + initialPrompt field

Phase E E2E tests (S6-1 through S6-5):
- session-create-wizard.spec.ts: full 4-step wizard creation flow
- session-create-omnibar.spec.ts: Ctrl+K omnibar creation flow
- session-title-autogen.spec.ts: auto-title + dirty flag + useTitleAsBranch
- project-grouping.spec.ts: multi-select → group → rename → delete flow
- session-create-github-url.spec.ts: GitHub URL title extraction

Unit tests:
- server/services/oneshot_test.go: extractPRURL (10 cases) + BatchCreateSessions semaphore
- session/prompts/store_test.go: PromptStore CRUD + ring-buffer eviction

S2-3 (Batch tab UI) intentionally deferred — backend complete.

* fix(ent): regenerate with correct field indices after initial_prompt insertion

one_shot was at index 24 in runtime.go but initial_prompt was inserted before it
(shifting one_shot to 25). Runtime panic: interface conversion nil → bool on init.

* feat(registry): add feature specs, +api/+feature markers, and test linkage for squad-ux-polish RPCs and UI

Backend // +api: markers added:
  session:list-prompt-history, session:delete-prompt-history
  session:batch-create (tested: 4 unit tests)
  session:run-one-shot (tested: TestExtractPRURL x2)
  project:create, project:list, project:update, project:delete, project:assign-sessions

Frontend // +feature: markers added:
  SessionWizard.tsx → session-create-wizard, session-title-autogen, session-create-github-url
  BulkActions.tsx   → project-grouping, session-bulk-select
  ReviewQueuePanel.tsx → review-queue-pr-creation

Registry entries added to backend-features.json (9 new) and
frontend-features.json (7 new) with E2E test linkage.
coverage-gaps.json updated.

* chore: resolve main merge, add isolated test target driven by feature registry

- Merge origin/main (omnibar improvements, goroutine leak fixes, delete-by-UUID)
- Add `make test-ux-polish` target: reads testIds from docs/registry/backend-features.json
  and builds the -run pattern dynamically, so the target auto-covers newly registered tests
- Add project_service_test.go and prompt_history_test.go (26 tests, all green)
- Add ReviewQueuePanel.test.tsx component test
- Fix .golangci.yml CGO issue via session/procinfo path exclusion (pre-existing)

* feat(registry): split monolithic JSON into per-feature files (conflict-proof)

Previously backend-features.json was a single 889-line file — any two branches
that ran make registry-generate would conflict on the features array and the
generatedAt timestamp.

Now each RPC gets its own file at:
  docs/registry/features/backend/<domain>/<action>.json   (committed)
  docs/registry/features/frontend/<type>/<id>.json        (committed)

Monolithic files (backend-features.json, frontend-features.json, coverage-gaps.json)
are now gitignored generated aggregates — use `make registry-aggregate` to rebuild them.

Changes:
- Scanner writes to per-feature directory; reads existing files to preserve testIds
- methodToID extended with 9 new RPCs (project, prompt-history, profile, defaults, etc.)
- TestScanProto_NoUnmappedMethods: fails if a new proto RPC lacks a methodToID entry
- tools/scanner/aggregate.py: assembles monolithic JSON from per-feature files
- validate-registry.sh: compares committed per-feature files vs scanner output
- make test-ux-polish: reads testIds from docs/registry/features/**/*.json
- make registry-aggregate: new target for local/CI monolithic assembly

* docs(registry): update docs + CI for per-feature registry structure

- CLAUDE.md: rewrite Feature Registry section — per-feature file layout,
  updated make commands (registry-aggregate added), how to add testIds manually
- tools/scanner/README.md: full rewrite reflecting new directory structure,
  methodToID requirement, aggregate script usage
- registry-validation.yml:
  - Fix PR comment trigger (was checking for JSON fields 'addedIds'/'removedIds'
    that the new plain-text validate script no longer emits)
  - Add 'Report coverage gaps' step: counts features with no testIds, posts
    covered/total/pct to PR comment (advisory, never blocks)

* feat(registry): add testIds to 25 more backend features (46.6% coverage)

Register existing tests into per-feature JSON files — no new test code written.
All registered tests were verified to exist and pass.

Features with testIds added:
  approval: delete-rule (3), get-analytics (3), list-pending (3),
            list-rules (2), resolve (7), upsert-rule (4)
  checkpoint: create (4), list (4)
  file: get-content (6), list (6), search (8)
  logs: get (5)
  notification: send (4)
  path: list-completions (21)
  session: acknowledge (3), create (2), delete (4), fork (7),
           get (2), get-diff (3), get-vcs-status (3), list (1),
           rename (4), update (6)
  workspace: switch (5)

Coverage: 9/73 → 34/73 (12.3% → 46.6%)

Untested features are streaming RPCs, tmux/git/GitHub-dependent endpoints,
and service RPCs without unit-testable validation logic.

* fix(ci): extract Python heredoc to script, fix YAML parse error in registry-validation workflow

The multi-line JS template literal containing backtick fences (\`\`\`) at
column 0 terminated the YAML block scalar prematurely, causing 0-job failures.

Fixes:
- Extract Python coverage reporter to tools/scanner/report-coverage.py
- Replace JS template literal with array.join() to avoid column-0 backticks

* test(services): add isolated service-layer tests for approval, checkpoint, and session CRUD

Tests use createTestStorage(t) with t.TempDir() SQLite — no server or tmux required.
These are registered in docs/registry/features/backend as testIds.

* fix(tests): gofmt all changed files + fix TestRenameSession_DuplicateTitle

- Run gofmt -w on all files flagged by the lint check
- Fix TestRenameSession_DuplicateTitle: RenameSession uses loadInstancesWithWiring
  (reads from storage), not the poller — use addPausedSession to persist sessions
  so they can be found during the duplicate-title check

* fix(tmux): prevent double-close panic in SubscribePaneExit under ctx+firePaneExit race

When both ctx.Done() fires and firePaneExit() runs concurrently:
- firePaneExit removes ch from subscribers and closes it
- the goroutine selects ctx.Done(), sees ch is gone, but still calls close(ch)

Fix: track whether we actually removed ch from subscribers; only close if removed.

* fix(poller): use atomic.Int64 for tickCount to eliminate data race

tickCount was read by test goroutines and written by the poll loop
goroutine concurrently without synchronization. Switching to
atomic.Int64 fixes the -race detector failure in
TestAdaptivePoller_BackoffToIdleInterval.

* chore: gofmt all PR-modified files

CI lint step flagged 11 files touched by this PR as not gofmt-formatted.

* fix(bench): add -run=^$ to skip tests in benchmark CI jobs

Without -run=^$, go test -bench runs all tests plus benchmarks. The
Tier 1 job was timing out (20 min job kill) because the new service
tests added in this PR (75+ tests × 8 count × SQLite init ≈ 11 min)
exceeded the 10-minute go test timeout before benchmarks could complete.

With -run=^$, only benchmark functions run. services package drops from
84s to 19s (count=1), keeping Tier 1 well under the 20-minute job cap.

Apply same fix to Tier 2 to prevent the same issue at scale.

* test: eliminate real sleeps with clock injection and fast poll intervals

- tokenBucket: inject clock via now func(), replace 1.1s sleep with fake advance
- IdleDetector: inject clock via now func(), replace ~1.4s of sleeps with fake clock
- notification subscriber tests: reduce coalescing interval 50ms→5ms, sleeps 120ms→15ms
- review queue integration test: 100ms poll interval, reduce 6.5s sleeps to ~750ms

Saves ~9s per test run; removes timing-sensitivity on slow CI.

* fix(bench): target specific packages in Tier 1 instead of ./...

./... compiled ~30 packages for each benchmark run. Tier 1 benchmarks
live in exactly 4 packages (server/events, server/terminal,
server/services, session/scrollback). Targeting them directly eliminates
wasted compilation and brings Tier 1 from 20+ min timeout to <5 min.

* fix(bench): reduce Tier 1 count 8→3, raise timeout 10m→15m

Services benchmarks create a real SQLite DB per benchmark-per-count,
making count=8 × 5 functions = 40 DB setups, ~15+ min total.
count=3 cuts that to 15 setups (~5 min). Tier 2 on main retains
count=8 with a 50-min job timeout for authoritative baseline data.

* fix(bench): remove server/services from Tier 1, restore count=8

server/services benchmarks create a real SQLite DB per run, making
them inherently slow (~12+ min for count=8, ~13 min for count=3).
Tier 1 is a quick sanity-check tier; SQLite-backed benchmarks belong
in Tier 2 (50-min job timeout, runs on main only).

Tier 1 now covers only EventBus, DeltaGeneration, CircularBuffer —
the three stateless, CPU-bound critical-path benchmarks that complete
in <3 min with count=8 on 3 packages.

Tier 2 retains ./... -bench=. to cover SessionService benchmarks as
part of the authoritative baseline on merge to main.

* feat(one-off): default program to system config instead of hardcoding claude

Frontend was hardcoding program: "claude" in two places, bypassing
config.DefaultProgram. Backend already handles program="" correctly via
ResolveDefaults(). Now the form and dispatch both send "" when no program is
selected, and a new "System default" option is first in the dropdown.

* feat(settings): add one-off base directory setting to global defaults UI

Exposes one_off_base_dir in SessionDefaultsConfig proto and
UpdateGlobalDefaultsRequest so the settings form can read and persist it.
Users can now change ~/oneoff to any path via Settings → Defaults.
Also updates the one-off creation hint to point to settings.

---------

Co-authored-by: Tyler Stapler <tystapler@gmail.com>
Merges personal repo's commits into work repo, including:
- feat(auth): web-based passkey device registration from /account page
- feat(omnibar): quick-navigation creation mode with mode state machine
- feat(sessions): session action sheet with Delete/Pause/Resume, rename, tag-edit
- feat(terminal): toolbar compact/expand toggle with localStorage persistence
- feat(ux): Milestones 2-3 — token contract, telemetry, action hook
- perf(phase1): TTL caching for IsDirty, CheckGHAuth, and Preview
- fix(session): MCP flag, Stopped→Running reconciliation, UUID lookups
- fix(tmux): BUG-010/012 — global registry contamination, stale socket cleanup
- fix(ui): accessibility, animation cap, cross-platform shortcuts, mobile fixes
- ci: mobile UX regression rules in lint pipeline
- Various fix(mobile), fix(ci), fix(terminal), fix(review-queue) patches

Benchmark baselines excluded — CI will regenerate.
ent/ schema merged: mcp_server_url (personal) + initial_prompt/one_shot/Project edge (work).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- session/tmux: add variadic opts to NewTmuxSessionWithServerSocket so
  callers can pass TmuxSessionOption (e.g. WithRegistry(nil))
- testutil: pass WithRegistry(nil) in TmuxTestServer.CreateSession and
  CreateSessionWithoutStarting to prevent the background reconnect loop
  from attaching to isolated server sockets; the loop's keepalive
  attach-session attempts caused intermittent new-session exit-status-1
  failures under CI load
- Header.css.ts: change active nav link text from vars.color.primary
  (#0070f3 in light theme) to vars.color.textPrimary (overridden to
  #ededed inside the dark header) — eliminates the 3.36:1 contrast
  ratio that fails WCAG 2.1 AA; blue underline still signals active state

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…instance.go

NewTmuxSessionWithServerSocket was called without WithRegistry(nil) in three
places in instance.go, causing GetServerRegistry to spawn a background
reconnectLoop on each isolated server socket. The loop repeatedly tried
attach-session on a keepalive that doesn't exist on isolated sockets,
interfering with concurrent new-session calls and producing exit status 1
(TestSessionCreationWithRealPrograms/Bash flake in CI).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…t spurious reconnect loop

WithRegistry(nil) was set after GetServerRegistry was called, so the
background reconnect loop still started on isolated sockets even when the
caller explicitly passed WithRegistry(nil).

Fix: apply functional opts first, then call GetServerRegistry only when no
explicit registry was provided (registryExplicit == false). This prevents
the reconnect loop from starting on isolated test sockets, which fixes
TestSessionCreationDoesNotHang/ClaudeSessionCreation and
TestColdRestore_WithoutUUID flakes in CI.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ork-20260427

chore: sync personal → upstream-fanatics 2026-04-27
github-actions Bot and others added 15 commits May 1, 2026 02:04
)

* feat(file-tree): performance, themes, and file browser enhancements

Backend caching:
- Wire DirCache into FileService.ListFiles — eliminates cold os.ReadDir on every RPC
- New GitignoreCache mirrors DirCache for []gitignore.Pattern with TTL invalidation
- Wire GitignoreCache into loadGitignorePatterns and collectAllGitignorePatterns — eliminates full WalkDir per search query
- Add GetFileService() accessor to SessionService

Frontend FileTree performance:
- Module-level EMPTY_GIT_STATUS_MAP constant prevents new Map() reference every render
- useMemo chain: treeData → displayedData → dirStatusMap keyed on actual deps
- Flatten handleToggle — no longer calls buildTreeData redundantly
- ResizeObserver dynamic height/width replaces hardcoded height={600} width="100%"

File viewer themes:
- Add Shiki dual-theme CSS selectors (lightTheme/darkTheme class-based) to activate --shiki-light/--shiki-dark CSS variables
- useAppTheme hook reads document.documentElement classList via MutationObserver
- CodeMirror conditionally applies oneDark only in dark mode

File browser:
- ServeFileRaw HTTP handler at GET /api/files/raw with path traversal protection, 10MB limit, SVG CSP sandbox header
- Download button (anchor with download attr) in FileContentViewer breadcrumb for all file types
- Inline image viewer for image/* content types using rawUrl from the new endpoint

* fix(executor): wrap non-ExitError from cmd.Wait() with 'failed to start' prefix

On Linux, exec-not-found errors can surface via cmd.Wait() instead of
cmd.Start(), bypassing the existing wrap. Detect non-ExitError returns
from the done channel and wrap them consistently so callers always see
'failed to start command:' for command-not-found failures.

Pre-existing failure on main; also fixes TestTimeoutExecutor_Run_InvalidCommand
and TestTimeoutExecutor_ErrorMessages/Start_failure_includes_command.

---------

Co-authored-by: Tyler Stapler <tystapler@gmail.com>
In creation_with_repo mode the selected repo path lives in
modeState.path, not in detection.localPath. repoPathForWorktrees
previously ignored modeState.path so useWorktreeSuggestions never
fired, leaving the dropdown empty. Fall back to modeState.path as
the fetch source in that mode.

Also surfaces isWorktreesLoading to show a disabled "Loading
worktrees…" select while the fetch is in flight.
Introduces a dedicated runCMSender goroutine that owns all stdin writes
to the control mode process. User send-keys go through highPriSendCh
and always drain before normPriSendCh (background polling, resize,
capture-pane), so interactive keystrokes never queue-starve behind
background operations.

Background ops now check cmEnabledForBackground() which additionally
gates on normPriSendCh having headroom — when the queue is full,
background commands fall back to subprocess so the queue stays clear
for user input.

Adds Instance.SendInputViaControlMode / TmuxProcessManager.SendInputViaControlMode
and wires the WebSocket input handler to try the CM high-priority path
first, falling back to subprocess send-keys on failure. Input errors are
now non-fatal (logged as warnings) so a missed keystroke doesn't kill
the stream.
WatchSessions was doing a full SQLite scan on every new connection.
Switch to the in-memory poller cache (same as ListSessions) to avoid
the DB round-trip on initial snapshot.

PTY reader loop was busy-spinning when the client signalled backpressure
(ptyPaused=true). Replace the spin with a blocking select on the pause
channel so the goroutine sleeps until the client is ready to receive again.
window.history.replaceState in the nav link onClick was intercepted by
Next.js's patched router, causing navigation to "/" instead of the target
route when clicking nav links from the sessions page.

Adds unit tests (Header.test.tsx) and e2e regression spec (nav-navigation.spec.ts).
Merges 50 commits from TylerStaplerAtFanatics/stapler-squad into
tstapler/stapler-squad. Key additions from work fork:

- feat(unfinished): Unfinished Work tab surfacing pending git commits
- feat(tmux): priority CM sender for low-latency input forwarding
- feat(file-tree): performance, themes, and file browser enhancements
- feat(ux): mobile nav, editable session info, status recovery
- feat(squad-ux-polish): batch session creation, RunOneShot PR creation
- feat(sessions): one-off session creation with auto-generated directory
- fix(server): use poller cache for WatchSessions; block on PTY pause
- chore(ci): switch to release-please for on-demand releases

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings May 2, 2026 20:07
@tstapler tstapler added the minor label May 2, 2026
@github-actions

github-actions Bot commented May 2, 2026

Copy link
Copy Markdown
Contributor

UX Analysis

Check Status Details
✅ Axe Core (WCAG 2.1 AA) success Critical/serious violations block merge
⚠️ Lighthouse Performance Score: unknown Warning if < 70 (non-blocking)
🤖 Claude UX Analysis Advisory See docs/qa/ for findings

Axe Core excludes terminal rendering areas (intentional design).
Lighthouse runs in desktop preset for this developer tool.

@github-actions

github-actions Bot commented May 2, 2026

Copy link
Copy Markdown
Contributor

✅ Registry Validation

Registry Validation
===================

Building backend scanner...
Scanning backend features...
Wrote 74 feature files to /tmp/tmp.LaCHwr9bHk/backend
Wrote 11 feature files to /tmp/tmp.LaCHwr9bHk/backend

=== Backend Registry Diff ===
Committed: 84  Generated: 84  Divergence: 0.0%
⚠️  66 feature(s) missing // +api: marker (markerFound: false)

✅ Registry validation passed. Divergence: 0.0%

Test Coverage: 39/84 features have testIds (46.4%)

Registry validation is in observation mode until 2026-05-02.
After that date, divergence > 2% will block merges.
Coverage reporting is advisory only.

@github-actions

github-actions Bot commented May 2, 2026

Copy link
Copy Markdown
Contributor

E2E RPC Latency

list-sessions-ttfb-mean: 3ms (▼ faster -16.1%; baseline: 3ms)
list-sessions-total-mean: 4ms (▼ faster -20.4%; baseline: 5ms)

@github-actions

github-actions Bot commented May 2, 2026

Copy link
Copy Markdown
Contributor

Go Benchmarks (Tier 1)

goos: linux
goarch: amd64
pkg: github.com/tstapler/stapler-squad/server/events
cpu: AMD EPYC 7763 64-Core Processor                
                                                  │ benchmarks/go/tier1-baseline.txt │          tier1-bench.txt          │
                                                  │              sec/op              │   sec/op     vs base              │
EventBusPublish-4                                                       12.25n ±  1%   12.24n ± 3%       ~ (p=0.520 n=8)
EventBusSubscribe-4                                                     1.487µ ±  3%   1.494µ ± 2%       ~ (p=0.524 n=8)
EventBusPublishWithSubscribers/\x01_subscribers-4                       61.13n ±  1%   61.24n ± 2%       ~ (p=0.959 n=8)
EventBusPublishWithSubscribers/__subscribers-4                          771.6n ±  3%   768.5n ± 2%       ~ (p=0.878 n=8)
EventBusPublishWithSubscribers/d_subscribers-4                          9.175µ ±  2%   8.797µ ± 8%       ~ (p=0.105 n=8)
EventBusPublishWithSubscribers/Ϩ_subscribers-4                         101.84µ ±  3%   93.16µ ± 3%  -8.52% (p=0.000 n=8)
EventBusConcurrentPublish-4                                             52.52n ± 12%   48.77n ± 4%  -7.14% (p=0.028 n=8)
EventBusSubscriberCount-4                                               5.688n ±  1%   5.679n ± 0%       ~ (p=0.061 n=8)
EventBusEndToEnd-4                                                      86.23n ±  2%   85.93n ± 1%       ~ (p=0.645 n=8)
geomean                                                                 301.7n         294.7n       -2.30%

                                                  │ benchmarks/go/tier1-baseline.txt │           tier1-bench.txt            │
                                                  │               B/op               │     B/op      vs base                │
EventBusPublish-4                                                       0.000 ± 0%       0.000 ± 0%       ~ (p=1.000 n=8) ¹
EventBusSubscribe-4                                                   1.157Ki ± 0%     1.157Ki ± 0%       ~ (p=1.000 n=8)
EventBusPublishWithSubscribers/\x01_subscribers-4                       0.000 ± 0%       0.000 ± 0%       ~ (p=1.000 n=8) ¹
EventBusPublishWithSubscribers/__subscribers-4                          0.000 ± 0%       0.000 ± 0%       ~ (p=1.000 n=8) ¹
EventBusPublishWithSubscribers/d_subscribers-4                          0.000 ± 0%       0.000 ± 0%       ~ (p=1.000 n=8) ¹
EventBusPublishWithSubscribers/Ϩ_subscribers-4                          0.000 ± 0%       0.000 ± 0%       ~ (p=1.000 n=8)
EventBusConcurrentPublish-4                                             0.000 ± 0%       0.000 ± 0%       ~ (p=1.000 n=8) ¹
EventBusSubscriberCount-4                                               0.000 ± 0%       0.000 ± 0%       ~ (p=1.000 n=8) ¹
EventBusEndToEnd-4                                                      0.000 ± 0%       0.000 ± 0%       ~ (p=1.000 n=8) ¹
geomean                                                                            ²                 -0.00%               ²
¹ all samples are equal
² summaries must be >0 to compute geomean

                                                  │ benchmarks/go/tier1-baseline.txt │          tier1-bench.txt           │
                                                  │            allocs/op             │ allocs/op   vs base                │
EventBusPublish-4                                                       0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=8) ¹
EventBusSubscribe-4                                                     6.000 ± 0%     6.000 ± 0%       ~ (p=1.000 n=8) ¹
EventBusPublishWithSubscribers/\x01_subscribers-4                       0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=8) ¹
EventBusPublishWithSubscribers/__subscribers-4                          0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=8) ¹
EventBusPublishWithSubscribers/d_subscribers-4                          0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=8) ¹
EventBusPublishWithSubscribers/Ϩ_subscribers-4                          0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=8) ¹
EventBusConcurrentPublish-4                                             0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=8) ¹
EventBusSubscriberCount-4                                               0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=8) ¹
EventBusEndToEnd-4                                                      0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=8) ¹
geomean                                                                            ²               +0.00%               ²
¹ all samples are equal
² summaries must be >0 to compute geomean

pkg: github.com/tstapler/stapler-squad/server/terminal
                                            │ benchmarks/go/tier1-baseline.txt │          tier1-bench.txt          │
                                            │              sec/op              │   sec/op     vs base              │
DeltaGenerator_LargeANSI_100KB-4                                   11.65µ ± 2%   11.58µ ± 2%       ~ (p=0.279 n=8)
DeltaGenerator_RapidSequential-4                                   2.315µ ± 1%   2.311µ ± 0%       ~ (p=0.368 n=8)
DeltaGenerator_FullScreen_WithCompression-4                        3.199µ ± 1%   3.193µ ± 0%       ~ (p=0.136 n=8)
DeltaGenerator_ProgressBar-4                                       2.169µ ± 0%   2.165µ ± 0%       ~ (p=0.150 n=8)
DeltaGeneration-4                                                  2.085µ ± 1%   2.068µ ± 1%  -0.77% (p=0.000 n=8)
geomean                                                            3.298µ        3.285µ       -0.38%

                                            │ benchmarks/go/tier1-baseline.txt │           tier1-bench.txt            │
                                            │               B/op               │     B/op      vs base                │
DeltaGenerator_LargeANSI_100KB-4                                  13.00Ki ± 0%   13.00Ki ± 0%       ~ (p=1.000 n=8) ¹
DeltaGenerator_RapidSequential-4                                  1.742Ki ± 0%   1.742Ki ± 0%       ~ (p=1.000 n=8) ¹
DeltaGenerator_FullScreen_WithCompression-4                       2.024Ki ± 0%   2.024Ki ± 0%       ~ (p=1.000 n=8) ¹
DeltaGenerator_ProgressBar-4                                      1.430Ki ± 0%   1.430Ki ± 0%       ~ (p=1.000 n=8) ¹
DeltaGeneration-4                                                 1.649Ki ± 0%   1.649Ki ± 0%       ~ (p=1.000 n=8) ¹
geomean                                                           2.551Ki        2.551Ki       +0.00%
¹ all samples are equal

                                            │ benchmarks/go/tier1-baseline.txt │          tier1-bench.txt           │
                                            │            allocs/op             │ allocs/op   vs base                │
DeltaGenerator_LargeANSI_100KB-4                                    10.00 ± 0%   10.00 ± 0%       ~ (p=1.000 n=8) ¹
DeltaGenerator_RapidSequential-4                                    9.000 ± 0%   9.000 ± 0%       ~ (p=1.000 n=8) ¹
DeltaGenerator_FullScreen_WithCompression-4                         9.000 ± 0%   9.000 ± 0%       ~ (p=1.000 n=8) ¹
DeltaGenerator_ProgressBar-4                                        10.00 ± 0%   10.00 ± 0%       ~ (p=1.000 n=8) ¹
DeltaGeneration-4                                                   7.000 ± 0%   7.000 ± 0%       ~ (p=1.000 n=8) ¹
geomean                                                             8.927        8.927       +0.00%
¹ all samples are equal

                                            │ benchmarks/go/tier1-baseline.txt │          tier1-bench.txt           │
                                            │               B/s                │     B/s       vs base              │
DeltaGenerator_LargeANSI_100KB-4                                  8.185Gi ± 4%   8.232Gi ± 2%       ~ (p=0.279 n=8)
DeltaGenerator_FullScreen_WithCompression-4                       3.303Gi ± 1%   3.308Gi ± 0%       ~ (p=0.161 n=8)
geomean                                                           5.199Gi        5.219Gi       +0.37%

pkg: github.com/tstapler/stapler-squad/session/scrollback
                                      │ benchmarks/go/tier1-baseline.txt │          tier1-bench.txt           │
                                      │              sec/op              │   sec/op     vs base               │
CircularBuffer_ConcurrentReadWrite-4                         3.549µ ± 2%   3.438µ ± 2%   -3.14% (p=0.001 n=8)
CircularBuffer_BurstAppend-4                                 102.2µ ± 0%   101.3µ ± 0%   -0.87% (p=0.000 n=8)
CircularBuffer_GetLastN_LargeBuffer-4                        18.48µ ± 2%   16.59µ ± 2%  -10.24% (p=0.000 n=8)
CircularBuffer_GetRange_Sequential-4                         9.534µ ± 1%   9.385µ ± 2%   -1.56% (p=0.001 n=8)
CircularBufferAppend-4                                       97.72n ± 0%   96.94n ± 0%   -0.80% (p=0.010 n=8)
CircularBufferGetLastN-4                                     1.746µ ± 1%   1.743µ ± 1%        ~ (p=0.424 n=8)
CircularBufferConcurrentAppend-4                             130.9n ± 1%   131.4n ± 1%        ~ (p=0.247 n=8)
geomean                                                      2.822µ        2.755µ        -2.40%

                                      │ benchmarks/go/tier1-baseline.txt │           tier1-bench.txt            │
                                      │               B/op               │     B/op      vs base                │
CircularBuffer_ConcurrentReadWrite-4                        6.062Ki ± 0%   6.062Ki ± 0%       ~ (p=1.000 n=8) ¹
CircularBuffer_BurstAppend-4                                62.50Ki ± 0%   62.50Ki ± 0%       ~ (p=1.000 n=8) ¹
CircularBuffer_GetLastN_LargeBuffer-4                       56.00Ki ± 0%   56.00Ki ± 0%       ~ (p=1.000 n=8) ¹
CircularBuffer_GetRange_Sequential-4                        28.00Ki ± 0%   28.00Ki ± 0%       ~ (p=1.000 n=8) ¹
CircularBufferAppend-4                                        24.00 ± 0%     24.00 ± 0%       ~ (p=1.000 n=8) ¹
CircularBufferGetLastN-4                                    6.000Ki ± 0%   6.000Ki ± 0%       ~ (p=1.000 n=8) ¹
CircularBufferConcurrentAppend-4                              32.00 ± 0%     32.00 ± 0%       ~ (p=1.000 n=8) ¹
geomean                                                     3.077Ki        3.077Ki       +0.00%
¹ all samples are equal

                                      │ benchmarks/go/tier1-baseline.txt │           tier1-bench.txt           │
                                      │            allocs/op             │  allocs/op   vs base                │
CircularBuffer_ConcurrentReadWrite-4                          2.000 ± 0%    2.000 ± 0%       ~ (p=1.000 n=8) ¹
CircularBuffer_BurstAppend-4                                 1.000k ± 0%   1.000k ± 0%       ~ (p=1.000 n=8) ¹
CircularBuffer_GetLastN_LargeBuffer-4                         1.000 ± 0%    1.000 ± 0%       ~ (p=1.000 n=8) ¹
CircularBuffer_GetRange_Sequential-4                          1.000 ± 0%    1.000 ± 0%       ~ (p=1.000 n=8) ¹
CircularBufferAppend-4                                        1.000 ± 0%    1.000 ± 0%       ~ (p=1.000 n=8) ¹
CircularBufferGetLastN-4                                      1.000 ± 0%    1.000 ± 0%       ~ (p=1.000 n=8) ¹
CircularBufferConcurrentAppend-4                              1.000 ± 0%    1.000 ± 0%       ~ (p=1.000 n=8) ¹
geomean                                                       2.962         2.962       +0.00%
¹ all samples are equal

                             │ benchmarks/go/tier1-baseline.txt │          tier1-bench.txt           │
                             │               B/s                │     B/s       vs base              │
CircularBuffer_BurstAppend-4                       597.5Mi ± 0%   602.7Mi ± 0%  +0.87% (p=0.000 n=8)

@github-actions

github-actions Bot commented May 2, 2026

Copy link
Copy Markdown
Contributor

Frontend Terminal Throughput

terminal-throughput-mean: 17 KB/s ▼ -2.1% (baseline: 17 KB/s)
terminal-throughput-p50: 17 KB/s ▼ -3.4% (baseline: 17 KB/s)

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Syncs the personal fork with upstream work-fork changes, introducing a large set of new UX features (notably “Unfinished Work”), tmux/process reliability improvements, file browsing performance upgrades, and CI/release automation updates.

Changes:

  • Adds/extends “Unfinished Work” across backend proto/service, scanner/session-matching, and frontend UI navigation.
  • Improves operational robustness (tmux fork-pressure monitoring, zombie reaping/subreaper on Linux, widespread exec.CommandContext + timeouts).
  • Updates frontend navigation and file tooling (shared nav pages, mobile BottomNav, FileTree perf/resize, file download/image preview), plus release-please migration.

Reviewed changes

Copilot reviewed 135 out of 157 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
web-app/tests/e2e/navigation.spec.ts New nav E2E coverage (desktop/mobile).
web-app/src/lib/routes.ts Route helpers updated (query-param session).
web-app/src/lib/nav-pages.ts Centralized nav-page definitions.
web-app/src/lib/hooks/useSessionService.ts Include workingDir in updates payload.
web-app/src/components/unfinished/UnfinishedItemDetail.tsx Session picker + open/reattach routing.
web-app/src/components/unfinished/UnfinishedItemDetail.css.ts Styles for session picker.
web-app/src/components/ui/Modal.css.ts Raises modal z-index.
web-app/src/components/sessions/VcsPanel.tsx Adds GitHub repo/PR info section.
web-app/src/components/sessions/VcsPanel.css.ts Styles for GitHub section in VCS panel.
web-app/src/components/sessions/OmnibarCreationPanel.tsx Worktree-loading UX improvements.
web-app/src/components/sessions/Omnibar.tsx Improves worktree suggestion path selection.
web-app/src/components/sessions/FileTree.tsx Memoization + ResizeObserver sizing + toggle logic.
web-app/src/components/sessions/FileContentViewer.tsx Download link + image preview + theme-aware CM.
web-app/src/components/sessions/FileContentViewer.css.ts Shiki dual-theme + download/image styles.
web-app/src/components/layout/tests/Header.test.tsx New Header nav regression/unit tests.
web-app/src/components/layout/tests/BottomNav.test.tsx Expanded BottomNav unit tests + omnibar mock.
web-app/src/components/layout/Header.tsx NAV_PAGES-driven header nav + aria-current.
web-app/src/components/layout/BottomNav.tsx Feature marker for bottom nav.
web-app/src/app/unfinished/page.tsx Feature marker for unfinished page.
web-app/src/app/page.tsx Handles worktree-based wizard query params.
tools/scanner/validate-registry.sh Scans unfinished.proto in validation.
tools/scanner/backend/proto_scanner.go Adds unfinished RPC method→ID mapping.
testutil/tmux_test.go Adds context timeouts to tmux test calls.
testutil/tmux.go Uses exec.CommandContext for tmux ops.
testutil/mocks.go Context-based exec for real/mock commands.
testutil/expect.go Context-based exec for interactive sessions.
tests/e2e/nav-navigation.spec.ts New e2e regression coverage for nav bug.
tests/e2e/demo.spec.ts Adds “Unfinished Work” demo scene.
tests/demo/helpers.go Context/timeouts for build/server exec.
tests/demo/demo_test.go Timeout for playwright demo run.
session/vcs/jj.go Adds command timeouts + WaitDelay.
session/vcs/git.go Adds command timeouts + WaitDelay.
session/vcs/detect.go Adds git detection timeout + WaitDelay.
session/vc/jj_provider_test.go Context-based jj exec in tests.
session/vc/jj_provider.go Adds timeout + WaitDelay for jj.
session/vc/git_provider_test.go Context-based git exec in tests.
session/vc/git_provider.go Adds timeout + WaitDelay for git.
session/unfinished/state_test.go Context-based exec in helper.
session/unfinished/state.go Uses CommandContext for diff hashing.
session/unfinished/scanner.go SessionIDs + CommandContext git calls.
session/tmux_process_manager.go Adds control-mode input forwarding API.
session/tmux/zombie_reaper_windows.go Windows no-op zombie reaper.
session/tmux/zombie_reaper.go Non-Windows zombie reaper implementation.
session/tmux/zombie_detector.go Zombie scan + watcher (ps-based).
session/tmux/tmux_test.go Context/timeouts for tmux exec in tests.
session/tmux/subreaper_other.go No-op subreaper on non-Linux.
session/tmux/subreaper_linux.go Linux PR_SET_CHILD_SUBREAPER support.
session/tmux/session_recovery_test.go Context-based exec in recovery tests.
session/tmux/server_registry.go Context/timeouts + PID tracking for tmux CM.
session/state_machine_test.go Allows Stopped→Running transition in tests.
session/state_machine.go Allows Stopped→Running (recovery) transition.
session/review_queue_uncommitted_changes_test.go Updates poller API usage.
session/review_queue_poller_test.go Updates poller API usage.
session/repo_path.go Adds timeouts/WaitDelay to git operations.
session/mux/tmux_options_test.go Context/timeouts for tmux option tests.
session/mux/tmux_options.go Context/timeouts for tmux option ops.
session/mux/multiplexer.go Context/timeouts for tmux lifecycle ops.
session/integration_test.go Context-based git exec in tests/bench.
session/instance_cold_restore_test.go Makes restore checks less flaky.
session/instance_approve_deny_test.go Approve from Stopped now succeeds.
session/instance.go Recovery wiring + command builder refactor + CM input.
session/git_worktree_manager.go Timeout for git rev-parse.
session/git/worktree_ops.go Adds timeouts; cleanup behavior tweaks.
session/git/worktree_git.go Adds timeouts to git/gh commands.
session/git/worktree_creation_test.go Context-based git exec in tests.
session/git/worktree.go Timeout for git worktree listing.
session/git/util.go Timeouts for gh auth + git queries.
session/external_tmux_streamer.go Context CM attach + PID tracking + timeouts.
session/claude_controller.go Reduces PTY buffer size (memory).
server/services/utility_service.go Hoists regex to package-level var.
server/services/unfinished_work_service.go Adds storage-backed session ID mapping.
server/services/session_service.go WatchSessions uses poller cache; PTY pause blocks; exposes FileService.
server/services/notification_service_test.go Adds end-to-end tests for stable ID resolution.
server/services/notification_service.go Resolves title→stable UUID for notifications.
server/services/gitignore_cache_test.go Tests for new gitignore cache.
server/services/gitignore_cache.go Adds gitignore TTL cache.
server/services/file_service_test.go Updates search helper signature.
server/services/file_service.go Adds DirCache + GitignoreCache; adds raw file handler.
server/services/connectrpc_websocket.go CM input fast-path + timeouts; shared ANSI regex.
server/services/approval_handler_integration_test.go Tests for resolveSessionID + UUID notifications.
server/services/approval_handler.go Resolves header/cwd to stable session UUID.
server/server.go Fork-pressure alerts + zombie watcher/reaper + raw file endpoint registration.
server/review_queue_manager_test.go Adds tests for stable ID in notifications.
server/review_queue_manager.go Emits notifications with stable UUID.
server/middleware/gzip.go Adds encoder pooling for gzip/zstd.
server/mcp/tools_vcs_test.go Context-based git exec in tests.
server/dependencies.go Wires storage into UnfinishedWorkService.
release-please-config.json Adds release-please configuration.
proto/session/v1/types.proto UnfinishedWorktree session_ids (repeated).
gen/proto/go/session/v1/types.pb.go Regenerated types for session_ids change.
profiling/profiling.go Adds /debug/fork-pressure endpoint + mux handler.
pkg/classifier/classifier.go Adds git command timeouts/WaitDelay.
main.go Adds timeouts/WaitDelay to hostname/dns helpers.
github/client.go Adds timeouts/WaitDelay around gh/git calls.
executor/timeout_executor_test.go Context-based exec in tests.
executor/timeout_executor.go Refactors to CommandContext + WaitDelay usage.
executor/circuit_breaker_test.go Context-based exec in tests.
docs/registry/features/frontend/ui/unfinished-work.json Feature registry entry for Unfinished Work UI.
docs/registry/features/frontend/ui/header-nav.json Feature registry entry for Header nav.
docs/registry/features/frontend/ui/bottom-nav.json Feature registry entry for Bottom nav.
docs/registry/features/backend/unfinished/watch.json Backend feature registry for unfinished watch.
docs/registry/features/backend/unfinished/update-config.json Backend feature registry for unfinished config update.
docs/registry/features/backend/unfinished/undismiss.json Backend feature registry for unfinished undismiss.
docs/registry/features/backend/unfinished/snooze.json Backend feature registry for unfinished snooze.
docs/registry/features/backend/unfinished/scan.json Backend feature registry for unfinished scan.
docs/registry/features/backend/unfinished/list.json Backend feature registry for unfinished list.
docs/registry/features/backend/unfinished/get-config.json Backend feature registry for unfinished get-config.
docs/registry/features/backend/unfinished/get-ai-summary.json Backend feature registry for unfinished AI summary.
docs/registry/features/backend/unfinished/dismiss.json Backend feature registry for unfinished dismiss.
docs/registry/features/backend/unfinished/commit-push.json Backend feature registry for unfinished commit/push.
docs/features.md Documents Unfinished Work feature.
daemon/daemon.go Context-based daemon exec launch.
config/config_test.go Context-based exec in mocks/tests.
config/config.go Context-based exec for timeout executor.
cmd/ssq-hooks/main.go Adds timeouts for service install/uninstall commands.
benchmarks/frontend/throughput-baseline.json Updates benchmark baselines.
benchmarks/e2e/latency-baseline.json Updates benchmark baselines.
Makefile Adds unfinished.proto registry scan.
CLAUDE.md Updates PR/release requirements (conventional commits).
CHANGELOG.md Adds release-please generated changelog.
.release-please-manifest.json Adds release-please version manifest.
.golangci.yml Updates forbidigo rules (exec.Command discouraged).
.github/workflows/release-please.yml Adds release-please workflow.
.github/workflows/label-check.yml Removes semver-label enforcement workflow.
.github/workflows/auto-tag.yml Removes auto-tag workflow (superseded).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +515 to 523
delCtx, delCancel := context.WithTimeout(context.Background(), 10*time.Second)
deleteCmd := exec.CommandContext(delCtx, "git", "branch", "-D", branch)
deleteCmd.WaitDelay = 2 * time.Second
delErr := deleteCmd.Run()
delCancel()
if delErr != nil {
// Log the error but continue with other worktrees
log.ErrorLog.Printf("failed to delete branch %s: %v", branch, err)
}
Comment on lines +355 to +372
// ResizeObserver: track container dimensions for react-window (requires numeric width/height).
const containerRef = useRef<HTMLDivElement>(null);
const [dims, setDims] = useState({ w: 300, h: 600 });

useEffect(() => {
const el = containerRef.current;
if (!el) return;
const ro = new ResizeObserver(([entry]) => {
requestAnimationFrame(() => {
const { width, height } = entry.contentRect;
if (width > 0 && height > 0) {
setDims({ w: Math.floor(width), h: Math.floor(height) });
}
});
});
ro.observe(el);
return () => ro.disconnect();
}, []);
Comment on lines +363 to +365
const rawUrl = `/api/files/raw?sessionId=${encodeURIComponent(sessionId)}&path=${encodeURIComponent(filePath)}`;
const downloadUrl = `${rawUrl}&download=true`;

Comment on lines +188 to +193
export const downloadButton = style({
display: "inline-flex",
alignItems: "center",
gap: vars.space[1],
padding: `${vars.space[1]} ${vars.space[2]}`,
fontSize: vars.fontSize.sm,
Comment on lines 145 to 150
r, ok := s.scanner.GetResultByKey(parts[0], parts[1])
if !ok {
return nil
}
r.SessionIDs = s.sessionPathIndex()[r.WorktreePath]
return &sessionv1.UnfinishedWorkEvent{
@github-actions

github-actions Bot commented May 2, 2026

Copy link
Copy Markdown
Contributor

🎬 E2E Feature Demos

2 shard(s) recorded feature flows for this PR.

recordings shard 1
recordings shard 2

Demo preview opens directly in browser (single-file HTML). Raw WebM recordings in ZIP. Expires after 30 days.

newDispatchTestSession was written for the pre-priority-queue API
where sendCMCommand wrote directly to controlModeStdin. After the
priority CM sender refactor (feat(tmux): priority CM sender), sendCMCommand
now enqueues to normPriSendCh and expects runCMSender to be running.

Initialize highPriSendCh, normPriSendCh, cmSenderExited and start
runCMSender in the test helper to fix TestCMDispatch_SingleCommand,
TestCMDispatch_ConcurrentSendCMCommand, and TestCMFeatureFlag_OnUsesCMPath.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@github-actions

github-actions Bot commented May 2, 2026

Copy link
Copy Markdown
Contributor

✅ Registry Validation

Registry Validation
===================

Building backend scanner...
Scanning backend features...
Wrote 74 feature files to /tmp/tmp.DFFOJeM9kQ/backend
Wrote 11 feature files to /tmp/tmp.DFFOJeM9kQ/backend

=== Backend Registry Diff ===
Committed: 84  Generated: 84  Divergence: 0.0%
⚠️  66 feature(s) missing // +api: marker (markerFound: false)

✅ Registry validation passed. Divergence: 0.0%

Test Coverage: 39/84 features have testIds (46.4%)

Registry validation is in observation mode until 2026-05-02.
After that date, divergence > 2% will block merges.
Coverage reporting is advisory only.

@tstapler
tstapler merged commit df2f762 into main May 2, 2026
22 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants