chore: sync upstream-fanatics → personal 2026-05-02 - #47
Conversation
…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
) * 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>
UX Analysis
|
✅ Registry ValidationTest Coverage: 39/84 features have
|
E2E RPC Latency |
Go Benchmarks (Tier 1) |
Frontend Terminal Throughput |
There was a problem hiding this comment.
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.
| 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) | ||
| } |
| // 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(); | ||
| }, []); |
| const rawUrl = `/api/files/raw?sessionId=${encodeURIComponent(sessionId)}&path=${encodeURIComponent(filePath)}`; | ||
| const downloadUrl = `${rawUrl}&download=true`; | ||
|
|
| export const downloadButton = style({ | ||
| display: "inline-flex", | ||
| alignItems: "center", | ||
| gap: vars.space[1], | ||
| padding: `${vars.space[1]} ${vars.space[2]}`, | ||
| fontSize: vars.fontSize.sm, |
| r, ok := s.scanner.GetResultByKey(parts[0], parts[1]) | ||
| if !ok { | ||
| return nil | ||
| } | ||
| r.SessionIDs = s.sessionPathIndex()[r.WorktreePath] | ||
| return &sessionv1.UnfinishedWorkEvent{ |
🎬 E2E Feature Demos2 shard(s) recorded feature flows for this PR. recordings shard 1 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>
✅ Registry ValidationTest Coverage: 39/84 features have
|
Summary
Merges 50 commits from
TylerStaplerAtFanatics/stapler-squadintotstapler/stapler-squadto bring the personal fork up to date with work.Key changes from work fork
Conflict resolutions
lastModifiedtimestamps, same content)OmnibarCreationPanel.tsx: merged both — kept personal's image upload UI + work'sisWorktreesLoadingand one-off info bannerMakefile: merged both — added work'sunfinished.protoregistry scanPair PR
The reverse sync (personal → work) is tracked in
TylerStaplerAtFanatics/stapler-squad.