fix(web): dead-band terminal resize loop, RPC dedup, and WebGL fallback - #272
Merged
tstapler merged 2067 commits intoJul 28, 2026
Conversation
…n is always visible Button was pushed off-screen on narrow viewports due to flex-row layout with flexGrow:1 on the text. Switch to column direction so the button always renders below the error message. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…detection Show GitHubBadge inline in SessionRow (row/list view) so PR status is visible without switching to card view. Previously the badge only rendered in SessionCard (card view). Switch getCurrentBranchName from subprocess (git rev-parse) to go-git direct file read — no subprocess overhead. Add exported GetCurrentBranchName wrapper and CurrentBranch() method on Instance that falls back to live git read for directory sessions (Branch field is always empty for non-worktree sessions). Add UpdatePRStatus() helper for atomic in-memory PR status updates from PRStatusPoller. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: repair broken release pipeline and build-from-source path Every GoReleaser release since v1.9.0 has failed with "found 3 builds with the ID 'stapler-squad'" because none of the three build entries in .goreleaser.yaml declared an explicit id, so GoReleaser assigned them all the same default. This is why brew install pulls the ancient 1.9.0 build (Formula/stapler-squad.rb hasn't updated since) and why install.sh's release-asset download has had nothing to fetch for every tag from v1.20.1 through v1.32.0. Give each build block an explicit unique id. Also fixes two things blocking the build-from-source path: - config/executor.go: lookPathOnlyExecutor.Command used a raw exec.Command instead of safeexec.CommandContext, tripping the norawexec custom lint rule and failing `make build` outright. - Makefile: `go build` never set the version ldflag, so both `make build` and plain `go build .` reported the stale hardcoded "1.1.2" regardless of what was actually built. Derive VERSION from `git describe` and pass it via -ldflags, matching what GoReleaser already does for tagged releases. Verified locally: `make build` now succeeds end-to-end and `./stapler-squad version` reports the real git-described version. `goreleaser check` and a full `goreleaser release --snapshot --clean` (with the GITHUB_* env vars CI provides) both succeed, including Homebrew formula generation. Fixes #143 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix: isolate TestGetConfigDir from ambient STAPLER_SQUAD_* env vars GetConfigDir() checks STAPLER_SQUAD_TEST_DIR and STAPLER_SQUAD_INSTANCE before falling through to test-mode auto-detection. When the test process inherits either from its environment (e.g. running inside a stapler-squad-managed session), the "uses test mode isolation for tests" subtest short-circuits on the ambient value instead of exercising auto-detection, and fails. Clear both for the duration of the subtest and restore them afterward. Verified with `go test ./config/... -run TestGetConfigDir -count=3` and a full `go test ./config/... -count=1`. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix: sanitize VERSION and wire it into build-embedded too Code review on this branch surfaced two real gaps in the version-ldflag fix: 1. Security: git tag names may legally contain shell metacharacters (backtick, $()). Make's $(VERSION) substitution is pure text substitution done before the shell parses the recipe line, so those characters land as live shell syntax inside the double-quoted `-ldflags` argument — anyone who can get a maliciously-tagged ref fetched into a checkout gets command execution on `make build` / `make install-service`. Strip VERSION to a safe charset before it ever reaches the shell. (Checked whether the analogous `VERSION=$(git describe ...)` in .github/workflows/build.yml has the same problem: it doesn't. That's a bash variable expansion of an already-computed string, not a macro substitution before the shell parses the command — bash does not re-evaluate `$()`/backticks embedded in an expanded variable's value. Verified empirically. Left that file alone.) 2. Completeness: `build-embedded` (the tmux-bundled single-binary target used by `make build-tmux` -> `make build-embedded`) builds the same stapler-squad binary as the primary `stapler-squad` target but wasn't wired to the new LDFLAGS, so it would have kept shipping the exact stale "1.1.2" version string issue #143 complains about. Verified: `make build` still succeeds and reports a correct, sanitized version. `make -n build-embedded` confirms the ldflags now appear in that target's go build invocation. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * ci: add goreleaser check as a regression guard for .goreleaser.yaml The build-ID collision this PR fixes broke every release for 15+ months with zero visibility: the only place it ever surfaced was a failed Action run on a tag push (release.yml only runs `goreleaser release` on `push: tags: v*`), which nobody was watching closely enough to catch. Add a small, fast, dedicated workflow that runs `goreleaser check` on every change to .goreleaser.yaml, so a config mistake like this one fails a PR check immediately instead of silently breaking every subsequent release. `goreleaser check` also fails non-zero for known-but-accepted deprecation warnings, not just genuine invalidity, so a naive `args: check` step would have gone red on day one against this repo's existing config (it still uses the classic `brews` publisher, which GoReleaser wants migrated to `homebrew_casks` — a real behavioral change for end users, not a syntax rename: casks use different install semantics, code-signing/Gatekeeper expectations, and app-bundle lifecycle hooks that don't apply to a plain CLI binary, and would very likely break the `brew install` command this repo's README documents. That migration needs its own careful, tested PR, not a blind swap bundled into an install-bug fix). Fixed the two safe, pure-syntax deprecations in the same commit (`archives.format`/ `format_overrides.format` -> `formats`, now a list — verified via a full snapshot build that archive naming/extension per-OS is unchanged) and left `brews` alone. The new workflow's check step distinguishes "configuration is invalid" (hard fail) from "valid, but uses deprecated properties" (pass, tracked separately) by output content rather than exit code, so it stays a real regression guard instead of either being permanently red on accepted debt or silently disabled. Verified locally: - `goreleaser check` on the current config: valid, only the accepted `brews` deprecation remains. - Simulated the exact original bug (duplicate build ids) against a scratch copy of the config: the same check logic correctly reports "configuration is invalid" and would fail CI. - Full `goreleaser release --snapshot --clean` still succeeds end-to-end after the formats-list migration, archive names/ extensions unchanged. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix: sync registry validation with github_user.proto and add missing feature files CI's Registry Validation check was failing on this PR (unrelated to the actual fix, but blocking it from going green): `tools/scanner/validate-registry.sh` never scans `proto/session/v1/github_user.proto`, even though the Makefile's `registry-generate-backend` target does. Both were last touched independently, and the validation script's hardcoded proto list was never updated when github_user.proto's RPCs (added in 3be7e09, well before this branch existed) were registered. The result: `docs/registry/features/backend/*.json` never had entries for ListGitHubAccounts/PollGitHubDeviceAuth/RevokeGitHubToken/ StartGitHubDeviceAuth, and the validation script would report them as "Removed RPCs" (154 committed vs. 147 generated, 4.55% divergence) forever, regardless of whether the per-feature files existed — the scanner it runs simply never looks at that proto file. - Added the missing `github_user.proto` scan step to validate-registry.sh, matching the Makefile. - Ran `make registry-generate` to create the 4 missing per-feature JSON files these RPCs were always supposed to have. Verified: `./tools/scanner/validate-registry.sh` now reports "Committed: 154 Generated: 154 Divergence: 0.0%" and exits 0. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> --------- Co-authored-by: Tyler Stapler <tystapler@gmail.com> Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
…rsing crash) (#150) * fix: shell-quote claude launch args to stop injection and flag-parsing crash Backlog/triage spawned sessions died on launch: the prompt is interpolated into a shell command (tmux launches programs through a shell), and Go's %q produces double quotes, which do not suppress backtick/$(...)/$VAR expansion. Backlog prompts are full of backtick-wrapped tokens (`/backlog/done-N`, etc.), so the shell executed each as a command instead of passing it to claude. Separately, backlog prompts begin with "--- BACKLOG ITEM DATA ---", which claude's arg parser rejected as an unrecognized flag once quoting was fixed. Add shellQuote (POSIX single-quoting, the same style already used for --mcp-config) and apply it to every claude flag value that gets interpolated into the shell command: --append-system-prompt, --allowedTools, --permission-mode, and the positional prompt. Insert a bare "--" before the prompt so a leading "--" in the prompt text is treated as data, not flags. Verified against the real claude CLI that both -- as an end-of-options separator and --append-system-prompt-file are accepted, and confirmed via a real shell execution that a $(...) payload in a backlog-shaped prompt no longer executes. Fixes #148 * fix: close remaining shell-injection gaps found by review Multi-agent review of the shellQuote fix found the same vulnerability class still present two call sites over: - --resume value: claudeSessionID traces back to the client-supplied resume_id field on CreateSessionRequest with no format validation, and was still interpolated unquoted into the shell-executed launch command in the same function that was just patched. - claudeMCPConfigFlag hand-rolled its own shell single-quoting (a literal '...' wrapper) instead of reusing shellQuote, leaving a second, untested implementation of the same job living next to the new one. Not currently exploitable (MCPServerURL/UUID aren't attacker-supplied today) but a latent gap in the same file that just added the primitive meant to prevent this. Also add regression tests the review flagged as missing: --allowedTools and --permission-mode had zero shell-safety coverage even though shellQuote was applied to both, so a partial revert of just those two lines would have passed the full suite silently. Reworked the two existing Prompt/AppendSystemPrompt regression tests to assert against hand-written expected literals instead of calling shellQuote() again, so they don't just verify the function against itself. Added only-single-quote, embedded-newline, and combined backtick+quote cases to TestShellQuote's table. Confirmed session/claude_command_builder.go's separate --resume path is not affected: it validates the session ID against a strict UUID v4 regex before use, and is not wired into any production call site today. --------- Co-authored-by: Tyler Stapler <tystapler@gmail.com>
…detection (#149) * fix(analytics): escape analytics session_id mismatch and dead mangle detection Escape event rows were tagged with the tmux session name instead of the stable session UUID, so the web UI (which queries by stable UUID) never found any data even though capture itself was working (185K+ rows in the live DB). Mangle detection was fully implemented and unit-tested but never wired into production — SetCorrelator was never called, emitEventWithStageAndSeq always recorded Stage 1 observations instead of checking Stage 2 against them, and the Stage 2 tap computed session_seq from the wrong buffer offset. - Thread instance.GetStableID() into the escape parser via a new ResponseStream.SetStableSessionID, scoped narrowly so cc.sessionName's other use sites (PTY naming, persistence dirs, rate limiting) are untouched - Wire MangleCorrelator per-parser with its eviction loop tied to stream lifetime; branch RecordStage1 vs CheckStage2 by stage instead of always recording - Fix Stage 2 session_seq to use the coalesced frame's start offset, not its end offset, so it aligns with Stage 1's numbering - Convert totalSequences/totalMangled to atomic.Int64 (both stages write through the same parser instance from different goroutines) - Mirror escape analytics defaults into DefaultConfig() to match LoadConfigFromPath, per the existing "must mirror" comment Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix(analytics): redesign mangle correlation to be offset-independent Code review on PR #149 found the byte-offset arithmetic fix for Stage 2 correlation couldn't work regardless of the arithmetic: streamViaControlMode's data comes from a separate tmux control-mode client, not the same producer as Stage 1's raw PTY read, so the two sides have no shared byte-offset numbering. Verified empirically (live tmux experiment with two simultaneous client attachments) that the two streams carry identical content in the same order, just offset by a constant that resets on each client's own connect/resize redraw — a calibration problem, not a content mismatch. Redesigned MangleCorrelator to correlate ordinally per (session, sequence type) instead of by byte position, which is robust to that offset entirely. Also addresses the review's MAJOR findings: sessionID is now atomic.Pointer[string] instead of an unsynchronized plain string; the parser setter is renamed SetStableSessionID to stop colliding with a tmux-name-keyed SetSessionID called 4 lines away; the correlator eviction goroutine is now tracked by ResponseStream's WaitGroup (so Stop() actually blocks on it) and panic-recovered; and the production wiring line in ClaudeController.Start() now has a test that would catch a regression back to the tmux name. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * chore: restore .claude/scheduled_tasks.lock accidentally deleted in prior commit Unrelated to this PR's changes — an environment-local lock file got staged as deleted before this session started and was swept up by a non-path-scoped git commit. Restoring it to match origin/main. --------- Co-authored-by: Tyler Stapler <tystapler@gmail.com> Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
… related bugs (#152) * fix(backlog): resolve GitHub URLs in repo path, add first-visit tour The Repository Path field silently accepted a GitHub URL and used it verbatim as a filesystem path, producing garbage paths and silent triage failures (stapler-squad#148's "Related" section). It also had no guidance on what it expected, so users had no way to know a URL wasn't a valid local path. - BacklogService now resolves GitHub URLs/shorthand in repo_path to a local clone (same machinery CreateSession already uses for the Omnibar), or returns a clear validation error instead of storing garbage. Covers both CreateBacklogItem and the UpdateBacklogItem fix-up path. - RepoPathInput gained an optional hint line and live GitHub-URL detection ("Will clone owner/repo to ~/.stapler-squad/repos/..."). - BacklogItemForm explains the two previously-unlabeled checkboxes and shows "Cloning repository…" while a fresh clone is in flight. - New BacklogTourModal walks first-time visitors through the item lifecycle, the repo-path gotcha explicitly, and what the skip flags do; reopenable via a "?" button in the page header. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix: shell-quote claude launch prompts, stop triage poll from losing edits Two backlog-adjacent bugs Carl filed while debugging the repo-path issue above: - stapler-squad#148: backlog/triage session prompts were interpolated into the shell command with Go's %q (double quotes), so backtick- wrapped tokens and $(...) in the auto-generated prompt were executed by the shell, and a leading "--" was parsed as a claude CLI flag — spawned sessions died on launch. Now single-quoted (shellQuote, which suppresses all shell expansion) with a "--" separator before the prompt. - stapler-squad#146: BacklogItemDetail's full-screen loading guard unmounted <BacklogItemForm> on every 5s triage-status poll, so any unsaved acceptance criteria typed during triage were silently discarded. The loader now only shows on the initial load, and the poll is suspended while the edit form is open. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * chore(e2e): install missing test deps, extend server-boot timeout allure-playwright (declared in package.json) was missing from the committed node_modules, breaking `npx playwright test` outright. Installed it and its transitive deps. Also bumped the test-server health-check timeout from 30s to 90s: a cold test-mode boot (DB init + demo seeding) was observed taking ~30-45s before /health responds, right at the old cap. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * revert(e2e): don't commit node_modules lock manifest without the packages The previous commit updated .package-lock.json (npm's per-tree manifest) after `npm install` pulled in allure-playwright and ~350 transitive deps, but those package directories are gitignored and weren't force-added — committing just the manifest without the actual files would claim the tree is in sync when it isn't. tests/e2e/node_modules is vendored (git-tracked despite .gitignore), so fully fixing the missing-dependency gap means force-adding ~thousands of new files, which is out of scope for this PR. Leaving the test-server.ts timeout bump from the previous commit in place since that's independently correct; flagging the vendoring gap separately. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix: address code review findings (shell-quote gap, tour checkbox bug, path traversal) Multi-dimension code review (Testing, Code Quality, Architecture, Security) on PR #152 surfaced two CRITICALs, both cross-validated by 2-3 independent reviewers, plus several MAJOR issues: - CRITICAL: AllowedTools/PermissionMode in instance_tmux.go still used Go's %q instead of the new shellQuote — the exact same shell-injection class this PR fixes for AppendSystemPrompt/Prompt, just on two sibling fields populated directly from client RPC input. - CRITICAL: BacklogTourModal's "Don't show this again" checkbox was a no-op — onClose (mapped to setTourComplete) unconditionally persisted onboarded=true regardless of the checkbox state. Fixed by changing the modal's callback contract to onComplete(persist: boolean), with a new hideTour() on the hook for the non-persisting path. - MAJOR (security): GitHub owner/repo regexes in repo_path.go didn't reject "." / ".." segments, so a crafted repo_path could resolve the clone directory outside ~/.stapler-squad/repos/github.com/. Added an isTraversalSegment guard across all 4 parse branches. - MAJOR: hardcoded 24px margin replaced with the vars.space token; extracted BacklogTourModal's reused modal-chrome styles out of OnboardingModal's own CSS module into a new shared components/ui/ModalTour.css.ts (OnboardingModal re-exports from it, so OnboardingModal.tsx needed no changes). - MAJOR (testing): replaced two circular shellQuote()-derived test oracles with hardcoded literals, added a message-content assertion the Update-path resolver-error test was missing, and strengthened the poll-suspended-while- editing test to assert the actual unsaved acceptance criterion survives rather than just checking a fetch call count. Deferred (documented, not blocking): DRY duplication between backlog_service.go/session_service.go's GitHub resolution, a matching hardcoded path format in the frontend hint text, and the pre-existing synchronous-clone-in-RPC-handler pattern this PR extends to a second call site (mirrors existing CreateSession behavior). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> --------- Co-authored-by: Tyler Stapler <tystapler@gmail.com> Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
#155) * fix: make web-build generate proto bindings on a fresh clone `make web-build` builds `web-app/out` without depending on `proto-gen`, so a clean checkout fails with "Module not found: '@/gen/session/v1/session_pb'" because the TypeScript protobuf bindings were never generated. `make build` was unaffected since it lists `proto-gen` as a direct prerequisite of the top-level target. Add `proto-gen` as a prerequisite of `web-app/out` so the TS bindings exist before the Next.js build runs, regardless of which entry point is used. `proto-gen` is a no-op when the bindings are already up to date, so this doesn't slow down repeat builds. Fixes #144 (Bug 1). Bug 2 (go-m1cpu SIGSEGV) is already resolved — the repo depends on gopsutil/v4, which dropped the go-m1cpu cgo dependency. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * test: add CI smoke test for standalone `make web-build` The existing CI pipeline never exercises the Makefile's own dependency graph: `.github/actions/prepare` hand-runs `buf generate` and `pnpm run build` directly, bypassing `make` entirely. That's exactly why the missing `proto-gen` prerequisite on `web-app/out` (previous commit, fixes #144) went undetected - no CI job ever invoked `make web-build` or `make build` as a fresh clone would. Add a standalone job that checks out cleanly (no shared artifacts, no manual buf/pnpm pre-steps) and runs `make web-build` directly, then asserts the generated TS proto bindings exist. Verified this job's steps fail against the pre-fix Makefile with the exact reported error ("Module not found: '@/gen/session/v1/session_pb'") and pass against the fix. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * chore: untrack stale generated proto files that were force-committed gen/, web-app/src/gen/, and .proto-gen.stamp are already in .gitignore, but 19 generated files under gen/proto/go/session/v1/ and web-app/src/gen/session/v1/ were force-committed into git anyway (going back through at least PR #60, #51, #54) and never cleaned up. The tracked set was also incomplete/stale - e.g. session.pb.go and session_pb.ts (generated from session.proto, the largest proto file) were never committed at all, while sessionv1connect/session.connect.go (which references types defined in session.pb.go) was. This is exactly what produced the "undefined: v1.CreateSessionRequest" compile errors and "Module not found '@/gen/session/v1/session_pb'" webpack errors in #144 on any workflow that skipped `proto-gen` - the stale committed files gave inconsistent partial signals instead of a clean "not generated yet" failure. `git rm --cached` only removes them from the index; the working-tree copies (freshly regenerated by `make web-build` in the previous commits) are untouched, and .gitignore now actually takes effect for this tree going forward. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> --------- Co-authored-by: Tyler Stapler <tystapler@gmail.com> Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
* docs: add requirements.md for GitHub issue/PR link surfacing Reset this branch onto origin/main first (it was ~1989 commits stale and missing the backlog feature entirely). Requirements grounded in the actual current session/backlog_* and server/services/backlog_service.go code per the item's 10 acceptance criteria. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YNymQbhmnrC9PcT7H7U63h * docs: add phase-2 research for GitHub issue/PR link surfacing Stack, features, architecture, pitfalls, and build-vs-buy research for adding ExternalURL end-to-end (ent schema, plugin mapping, prompt rendering, sync backfill). Key findings: stdlib-only (no new dep), ent auto-migration via existing --feature sql/upsert generate command, fact line belongs inside the prompt's trusted-data boundary while the closingKeywordFor instruction line stays outside it, and SyncOne's anyField gate needs an explicit fix so it doesn't skip the unconditional ExternalURL backfill when title/description/priority are all user-modified. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YNymQbhmnrC9PcT7H7U63h * docs: add implementation plan, ADR-001, and reviews for GitHub issue link Plan covers 6 phases / 7 epics / 13 stories / 32 tasks with a concrete Given-When-Then example per acceptance criterion. ADR-001 records two non-obvious decisions: SyncOne's anyField must be set independently of UserModifiedFields gating for the ExternalURL backfill, and the fact line renders inside BuildSessionInitialPrompt's inert-data boundary while the closingKeywordFor instruction line stays outside it. Architecture and adversarial review both caught the same real bug pre- implementation: the instruction-line format string would have rendered "Fixes: <url>" (extra colon) instead of AC3's literal "Fixes <url>" / "Related: <url>" asymmetric punctuation. Fixed in plan.md and both reviews confirmed RESOLVED after a repair-loop pass. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YNymQbhmnrC9PcT7H7U63h * docs: validation plan, pre-mortem, and a real defect fix from sdd:4-validate Validation.md maps all 9 ACs to concrete tests and closes 3 test-coverage gaps the plan's task list hadn't named explicitly (a migration NULL-safety test, a Create/Get round-trip test, and an AttachSessionToItem integration test — the last one closes exactly the kind of silent two-literal drift this whole feature exists to fix). Pre-mortem's P1 finding was a real, confirmed defect, not just an unverified assumption: fetched GitHub's own docs and confirmed the closing-keyword parser only recognizes "#N"/"owner/repo#N", never a bare URL. The original design (Fixes <full-url>) would have silently failed to auto-close anything. Added a githubShortRefFor helper (ADR-001 Decision 3) to derive and render the correct short reference instead, and propagated the fix through plan.md's tasks/tests and validation.md. Cross-artifact consistency check also caught a stale requirements.md line claiming the AC6 backfill "bypasses" BacklogItemUpdate, when the actual (correct) design routes through it and only bypasses the UserModifiedFields gate — fixed. Product Triad Review: all three legs (Product/UX/Engineering) ready, zero blockers. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YNymQbhmnrC9PcT7H7U63h * feat(backlog): surface linked GitHub issue/PR to agent prompts Imported backlog items now carry the originating GitHub issue/PR URL end to end, so an agent working the item can reference it in its PR and GitHub can auto-close/cross-reference on merge. - ent schema/BacklogItemData/BacklogItemUpdate gain ExternalURL (session/ent/schema/backlog_item.go, session/repository.go), populated by both GitHubIssuesPlugin and GitHubPRsPlugin (capped at 500 chars, matching the existing Title/Description truncation pattern). - SyncOne backfills ExternalURL on pre-existing rows unconditionally, bypassing UserModifiedFields local-wins, with anyField set independently so the backfill fires even when title/description/ priority are all user-locked (ADR-001 Decision 1). Only backfills items still returned by the plugin's state=open Fetch — a documented, tested limitation, not a bug. - BuildSessionInitialPrompt renders a "Linked GitHub Issue/PR: <url>" fact line inside the prompt's inert-data boundary, and a deterministic closing-keyword instruction line outside it (ADR-001 Decision 2), gated on ExternalURL being non-empty so the prompt is byte-identical to today when it's absent. - githubShortRefFor derives the actual "owner/repo#N" reference GitHub's closing-keyword parser recognizes (confirmed via GitHub's own docs during sdd:4-validate — a bare full URL is NOT a recognized closing-keyword form, only #N/owner/repo#N are). Fixes a defect the original design would have shipped silently (ADR-001 Decision 3). - Both hand-built ent.BacklogItem{} literals in server/services/backlog_service.go (SpawnSessionFromItem, AttachSessionToItem) now include ExternalURL. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YNymQbhmnrC9PcT7H7U63h * fix(backlog): harden githubShortRefFor and sanitize ExternalURL in prompt sdd:6-verify Layer 1/2 review findings (0 blockers, applying the two worthwhile SUGGEST items inline): - githubShortRefFor now strips query string/fragment/trailing-slash noise from the parsed issue/PR number, so a URL like ".../issues/42?tab=comments" renders "acme/widget#42" instead of a malformed "acme/widget#42?tab=comments" that GitHub wouldn't recognize. Added cross-reference comments between closingKeywordFor and githubShortRefFor noting they use different URL-shape strictness by design (both real producers are github.com URLs, so they can't disagree today) rather than restructuring tested pure functions this late for a currently-unreachable case. - item.ExternalURL is now run through sanitizeField before rendering in both the fact line and instruction line, matching the existing convention for every other externally-sourced field (Description, Notes, LastCommitMessage) rather than being the one exception. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YNymQbhmnrC9PcT7H7U63h --------- Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
…-terminal-resize-loop-fix # Conflicts: # server/dependencies.go # server/server.go # server/services/workspace_service.go # session/capture_test.go # session/history_linker.go # session/history_linker_test.go # session/instance.go # session/tmux/tmux.go # session/tmux/tmux_test.go # web-app/jest.config.js # web-app/jest.setup.js # web-app/package-lock.json # web-app/package.json # web-app/src/app/page.tsx # web-app/src/components/sessions/SessionCard.module.css # web-app/src/components/sessions/SessionCard.tsx # web-app/src/components/sessions/SessionList.tsx # web-app/src/components/sessions/TerminalOutput.tsx # web-app/src/components/sessions/XtermTerminal.tsx # web-app/src/components/sessions/__tests__/XtermTerminal.test.tsx # web-app/src/lib/hooks/useSessionService.ts # web-app/src/lib/hooks/useTerminalFlowControl.ts # web-app/src/lib/hooks/useTerminalStream.ts
Fixes surfaced by running the merged test suite after merging origin/main into the terminal-resize-fit-loop branch (commit 65afd7b): - XtermTerminal.tsx: remove the reintroduced synchronous initial onResize() call. Upstream deliberately deleted this (commit acb0750, "prevent premature resize from corrupting dimension cache") because it fired onResize(80,24) with xterm's construction-time defaults before fitAddon.fit() ever ran, corrupting TerminalOutput's dimension cache. Re-adding it during the merge reintroduced that exact regression, caught by the upstream regression suite XtermTerminalBug.test.tsx ("Bug 1"). - XtermTerminal.test.tsx: add a buffer stub to MockTerminal (origin's new updateScrollbar() reads buffer.active unconditionally), stub a minimal WebGL2RenderingContext global (jsdom has none, and the merged component now gates its WebGL load behind `typeof WebGL2RenderingContext !== 'undefined'` per xterm.js issue #2033), flush the dynamic import('@xterm/addon-webgl') microtask chain after mount, and recalibrate the resize-sampler test helpers' fake-timer advances for the flat 150ms debounce (replacing the old adaptive 10ms-for-first-3 debounce) instead of the old adaptive timing. - TerminalOutput.test.tsx: add the supporting context/hook mocks (AnalyticsContext, ApprovalsContext, useHandedness, TerminalStreamManager, etc.) TerminalOutput now depends on, update the Fit button's aria-label and account for it living behind the (now-collapsible) toolbar toggle, and flush the XtermTerminal lazy-import Suspense boundary plus the new 250ms post-connection resize delay before asserting. - BacklogItemForm.test.tsx: fix a pre-existing tsc error (unrelated to this merge, inherited from origin/main) by typing the onSubmit mock instead of letting it infer a zero-arg jest.Mock. - pnpm-lock.yaml: regenerated after adding @xterm/addon-canvas back to package.json (needed for the Canvas-fallback feature; origin/main's dependency bump to xterm.js 6.x never had this addon since upstream lacks the WebGL->Canvas fallback feature). All fixes verified with `go build ./...`, `go test ./...`, `npx tsc --noEmit`, and the full `npx jest` suite (2840 passing; 3 pre-existing failures on origin/main unrelated to this merge — SessionCard.click.test.tsx and SessionCard.approval-suppression.test.tsx are missing an AnalyticsContext mock that predates this branch, and BacklogEmptyState.test.tsx has an unhandled promise rejection from mockRejectedValue, also pre-existing). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KSdgF9zfk4PcYuQE61ooHi
… in code review
- useTerminalFlowControl.ts: cancel any pending deferred resize timer
unconditionally BEFORE the value-dedup early-return in resize(), so a
bounce-back call (A -> B deferred -> back to A) can no longer dedup-return
while leaving a stale deferred send for B still scheduled. Added a
regression test reproducing the exact bounce-back scenario.
- XtermTerminal.tsx: guard the async @xterm/addon-webgl import with a
`cancelled` flag set in the effect's unmount cleanup, preventing
loadAddon() from running against an already-disposed terminal and leaking
an orphaned WebGL context on a fast session-switch unmount.
- useTerminalFlowControl.test.ts: remove dead duplicate jest.mock('@bufbuild/protobuf', ...)
merge-conflict leftover.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KSdgF9zfk4PcYuQE61ooHi
…-terminal-resize-loop-fix
triggerCanvasFallback() lacked the same cancelled-guard the async WebGL loader uses, so a post-unmount onContextLoss could call loadAddon() on a disposed terminal (caught but logged spuriously). The post-resize currentPaneRequest timer in useTerminalFlowControl was also never tracked or cleared on unmount, unlike its sibling pendingResizeTimerRef. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011HafE8LJ1P3qsX6TokT9Fc
…-terminal-resize-loop-fix # Conflicts: # .claude/commands/perf/make-it-faster.md # .claude/docs/codesigning.md # .github/workflows/benchmark.yml # .github/workflows/build.yml # .github/workflows/demo-publish.yml # .github/workflows/release-please.yml # .github/workflows/release.yml # .github/workflows/ux-analysis.yml # .gitignore # .golangci.yml # .release-please-manifest.json # CHANGELOG.md # CLAUDE.md # Formula/stapler-squad.rb # Makefile # config/config.go # config/config_test.go # config/defaults_test.go # config/types.go # docs/api/features/unfinished-work.md # docs/demos/accessibility-Accessibilit-2de24-ndary-routes-are-accessible-chromium-dom.gif # docs/demos/accessibility-Accessibilit-2de24-ndary-routes-are-accessible.gif # docs/demos/accessibility-Accessibilit-8ae87-us-accessibility-violations-chromium-dom.gif # docs/demos/accessibility-Accessibilit-8ae87-us-accessibility-violations.gif # docs/demos/backlog-Backlog-Backlog-To-b1401-after-it-has-been-dismissed-chromium-dom.gif # docs/demos/backlog-Backlog-Backlog-To-b1401-after-it-has-been-dismissed.gif # docs/demos/backlog-Backlog-Backlog-To-e4158-g-it-persists-across-reload-chromium-dom.gif # docs/demos/backlog-Backlog-Backlog-To-e4158-g-it-persists-across-reload.gif # docs/demos/backlog-Backlog-Empty-Stat-1590e--button-reveals-inline-form-chromium-dom.gif # docs/demos/backlog-Backlog-Empty-Stat-1590e--button-reveals-inline-form.gif # docs/demos/backlog-Backlog-Empty-Stat-2f4c8--button-disabled-when-empty-chromium-dom.gif # docs/demos/backlog-Backlog-Empty-Stat-2f4c8--button-disabled-when-empty.gif # docs/demos/backlog-Backlog-Empty-Stat-82f48--Clicking-cancel-hides-form-chromium-dom.gif # docs/demos/backlog-Backlog-Empty-Stat-82f48--Clicking-cancel-hides-form.gif # docs/demos/backlog-Backlog-Empty-Stat-96beb-ycle-diagram-and-CTA-button-chromium-dom.gif # docs/demos/backlog-Backlog-Empty-Stat-96beb-ycle-diagram-and-CTA-button.gif # docs/demos/backlog-Backlog-Filter-Zer-700a3-lters-button-resets-filters-chromium-dom.gif # docs/demos/backlog-Backlog-Filter-Zer-700a3-lters-button-resets-filters.gif # docs/demos/backlog-Backlog-Filter-Zer-9d4df-filters-shows-empty-message-chromium-dom.gif # docs/demos/backlog-Backlog-Filter-Zer-9d4df-filters-shows-empty-message.gif # docs/demos/backlog-Backlog-Item-Creat-a24f6-t-item-via-empty-state-form-chromium-dom.gif # docs/demos/backlog-Backlog-Item-Creat-a24f6-t-item-via-empty-state-form.gif # docs/demos/backlog-Backlog-Item-Creat-e6bc5--the-list-after-empty-state-chromium-dom.gif # docs/demos/backlog-Backlog-Item-Creat-e6bc5--the-list-after-empty-state.gif # docs/demos/backlog-Backlog-Item-Creat-ee2ef-d-with-default-priority-P3--chromium-dom.gif # docs/demos/backlog-Backlog-Item-Creat-ee2ef-d-with-default-priority-P3-.gif # docs/demos/backlog-Backlog-Page-Navig-eb97f-s-accessible-and-functional-chromium-dom.gif # docs/demos/backlog-Backlog-Page-Navig-eb97f-s-accessible-and-functional.gif # docs/demos/backlog-Backlog-Page-Navig-f014a-age-loads-and-is-accessible-chromium-dom.gif # docs/demos/backlog-Backlog-Page-Navig-f014a-age-loads-and-is-accessible.gif # docs/demos/backlog-Backlog-Status-Tra-98946-ed-to-ready-via-detail-pane-chromium-dom.gif # docs/demos/backlog-Backlog-Status-Tra-98946-ed-to-ready-via-detail-pane.gif # docs/demos/backlog-Backlog-Status-Tra-a3f73-Item-button-appears-in-list-chromium-dom.gif # docs/demos/backlog-Backlog-Status-Tra-a3f73-Item-button-appears-in-list.gif # docs/demos/backlog-Backlog-Status-Tra-a8d60-ture-not-yet-exposed-in-UI--chromium-dom.gif # docs/demos/backlog-Backlog-Status-Tra-a8d60-ture-not-yet-exposed-in-UI-.gif # docs/demos/backlog-Backlog-Triage-e2e-000e4-bled-when-repoPath-is-empty-chromium-dom.gif # docs/demos/backlog-Backlog-Triage-e2e-000e4-bled-when-repoPath-is-empty.gif # docs/demos/backlog-sources-settings-b-1d1f3-urce-and-see-it-in-the-list.gif # docs/demos/backlog-sources-settings-b-2c08b-le-a-source-s-enabled-state-chromium-dom.gif # docs/demos/backlog-sources-settings-b-2c08b-le-a-source-s-enabled-state.gif # docs/demos/backlog-sources-settings-b-6f2de-ce-removes-it-from-the-list-chromium-dom.gif # docs/demos/backlog-sources-settings-b-6f2de-ce-removes-it-from-the-list.gif # docs/demos/browser-passthrough-browse-5468d-tate-when-cdp-not-connected-chromium-dom.gif # docs/demos/browser-passthrough-browse-5468d-tate-when-cdp-not-connected.gif # docs/demos/browser-passthrough-browse-fb705-wser-tab-when-cdp-available-chromium-dom.gif # docs/demos/browser-passthrough-browse-fb705-wser-tab-when-cdp-available.gif # docs/demos/bulk-select-bulk-select-bu-1b31d-sessions-show-paused-status-chromium-dom.gif # docs/demos/bulk-select-bulk-select-bu-1b31d-sessions-show-paused-status.gif # docs/demos/bulk-select-bulk-select-bu-468ba--sessions-removed-from-list-chromium-dom.gif # docs/demos/bulk-select-bulk-select-bu-468ba--sessions-removed-from-list.gif # docs/demos/bulk-select-bulk-select-es-e26ba-xes-hidden-and-toolbar-gone-chromium-dom.gif # docs/demos/bulk-select-bulk-select-es-e26ba-xes-hidden-and-toolbar-gone.gif # docs/demos/bulk-select-bulk-select-sh-fb302-row-3-rows-1-3-are-selected-chromium-dom.gif # docs/demos/bulk-select-bulk-select-sh-fb302-row-3-rows-1-3-are-selected.gif # docs/demos/bulk-select-bulk-select-un-f128e--in-toast-sessions-reappear-chromium-dom.gif # docs/demos/bulk-select-bulk-select-un-f128e--in-toast-sessions-reappear.gif # docs/demos/demo-Demo-Flow.gif # docs/demos/enter-detection-enter-dete-79093-hould-loadPageWithoutErrors-chromium-dom.gif # docs/demos/enter-detection-enter-dete-79093-hould-loadPageWithoutErrors.gif # docs/demos/history-search-History-Sea-95175-ory-search-UI-is-accessible-chromium-dom.gif # docs/demos/history-search-History-Sea-95175-ory-search-UI-is-accessible.gif # docs/demos/insights-insights-dashboar-36984-ingOrData-When-apiAvailable-chromium-dom.gif # docs/demos/mobile-navigation-mobile-n-efd32-sionList-When-sessionsExist-chromium-dom.gif # docs/demos/mobile-navigation-mobile-n-efd32-sionList-When-sessionsExist.gif # docs/demos/nav-navigation-nav-navigat-11cca-hen-session-param-is-in-URL-chromium-dom.gif # docs/demos/nav-navigation-nav-navigat-11cca-hen-session-param-is-in-URL.gif # docs/demos/nav-navigation-nav-navigat-399f4-s-back-from-unfinished-page-chromium-dom.gif # docs/demos/nav-navigation-nav-navigat-399f4-s-back-from-unfinished-page.gif # docs/demos/nav-navigation-nav-navigat-84fe4-avigates-from-sessions-page-chromium-dom.gif # docs/demos/nav-navigation-nav-navigat-84fe4-avigates-from-sessions-page.gif # docs/demos/nav-navigation-nav-navigat-98d3c-avigates-from-sessions-page-chromium-dom.gif # docs/demos/nav-navigation-nav-navigat-98d3c-avigates-from-sessions-page.gif # docs/demos/nav-navigation-nav-navigat-ed5f6-hen-session-param-is-in-URL-chromium-dom.gif # docs/demos/nav-navigation-nav-navigat-ed5f6-hen-session-param-is-in-URL.gif # docs/demos/onboarding-hook-install-on-a4529-n-the-final-onboarding-step-chromium-dom.gif # docs/demos/onboarding-hook-install-on-a4529-n-the-final-onboarding-step.gif # docs/demos/review-queue-Review-Queue--12c9a-tem-from-DOM-optimistic-UI--chromium-dom.gif # docs/demos/review-queue-Review-Queue--12c9a-tem-from-DOM-optimistic-UI-.gif # docs/demos/review-queue-Review-Queue--45ba3--present-after-page-renders-chromium-dom.gif # docs/demos/review-queue-Review-Queue--45ba3--present-after-page-renders.gif # docs/demos/review-queue-Review-Queue--bf4d4-eue-page-loads-successfully-chromium-dom.gif # docs/demos/review-queue-Review-Queue--bf4d4-eue-page-loads-successfully.gif # docs/demos/review-queue-Review-Queue--e61a4-ies-acknowledge-data-testid-chromium-dom.gif # docs/demos/review-queue-Review-Queue--e61a4-ies-acknowledge-data-testid.gif # docs/demos/review-queue-Session-Creat-7ee30-eation-wizard-has-all-steps-chromium-dom.gif # docs/demos/review-queue-Session-Creat-7ee30-eation-wizard-has-all-steps.gif # docs/demos/review-queue-Session-Creat-8345c--form-has-required-test-IDs-chromium-dom.gif # docs/demos/review-queue-Session-Creat-8345c--form-has-required-test-IDs.gif # docs/demos/rules-yaml-import-rules-ya-19648--state-has-explanatory-text-chromium-dom.gif # docs/demos/rules-yaml-import-rules-ya-19648--state-has-explanatory-text.gif # docs/demos/rules-yaml-import-rules-ya-3b89e-rt-duplicate-overwrite-mode-chromium-dom.gif # docs/demos/rules-yaml-import-rules-ya-3b89e-rt-duplicate-overwrite-mode.gif # docs/demos/rules-yaml-import-rules-ya-552d0-les-and-shows-preview-cards-chromium-dom.gif # docs/demos/rules-yaml-import-rules-ya-552d0-les-and-shows-preview-cards.gif # docs/demos/rules-yaml-import-rules-ya-62f98-ws-inline-validation-errors-chromium-dom.gif # docs/demos/rules-yaml-import-rules-ya-62f98-ws-inline-validation-errors.gif # docs/demos/rules-yaml-import-rules-ya-6a69d-d-rules-and-refreshes-table-chromium-dom.gif # docs/demos/rules-yaml-import-rules-ya-6a69d-d-rules-and-refreshes-table.gif # docs/demos/rules-yaml-import-rules-ya-7d21c--yaml-button-downloads-file-chromium-dom.gif # docs/demos/rules-yaml-import-rules-ya-7d21c--yaml-button-downloads-file.gif # docs/demos/rules-yaml-import-rules-ya-daed9--import-duplicate-skip-mode-chromium-dom.gif # docs/demos/rules-yaml-import-rules-ya-daed9--import-duplicate-skip-mode.gif # docs/demos/rules-yaml-import-rules-ya-fcaa2-port-modal-opens-and-closes-chromium-dom.gif # docs/demos/rules-yaml-import-rules-ya-fcaa2-port-modal-opens-and-closes.gif # docs/demos/session-create-directory-d-0e2cd-ory-session-type-in-payload-chromium-dom.gif # docs/demos/session-create-directory-d-0e2cd-ory-session-type-in-payload.gif # docs/demos/session-create-directory-d-11fc0--is-disabled-without-a-path-chromium-dom.gif # docs/demos/session-create-directory-d-11fc0--is-disabled-without-a-path.gif # docs/demos/session-create-directory-d-45cc2-ry-field-for-directory-mode-chromium-dom.gif # docs/demos/session-create-directory-d-45cc2-ry-field-for-directory-mode.gif # docs/demos/session-create-directory-d-7ec73-irectory-type-is-selectable-chromium-dom.gif # docs/demos/session-create-directory-d-7ec73-irectory-type-is-selectable.gif # docs/demos/session-create-directory-d-a9bd3--when-directory-is-selected-chromium-dom.gif # docs/demos/session-create-directory-d-a9bd3--when-directory-is-selected.gif # docs/demos/session-create-existing-wo-3a5aa-when-worktree-path-is-empty-chromium-dom.gif # docs/demos/session-create-existing-wo-3a5aa-when-worktree-path-is-empty.gif # docs/demos/session-create-existing-wo-6a4ba--for-existing-worktree-mode-chromium-dom.gif # docs/demos/session-create-existing-wo-6a4ba--for-existing-worktree-mode.gif # docs/demos/session-create-existing-wo-7131c-rktree-option-is-selectable-chromium-dom.gif # docs/demos/session-create-existing-wo-7131c-rktree-option-is-selectable.gif # docs/demos/session-create-existing-wo-79c0c-th-worktree-path-in-payload-chromium-dom.gif # docs/demos/session-create-existing-wo-79c0c-th-worktree-path-in-payload.gif # docs/demos/session-create-existing-wo-cde14-isting-worktree-is-selected-chromium-dom.gif # docs/demos/session-create-existing-wo-cde14-isting-worktree-is-selected.gif # docs/demos/session-create-existing-wo-dbe1a-isting-worktree-is-selected-chromium-dom.gif # docs/demos/session-create-existing-wo-dbe1a-isting-worktree-is-selected.gif # docs/demos/session-create-new-project-057be-irectory-hides-branch-field-chromium-dom.gif # docs/demos/session-create-new-project-057be-irectory-hides-branch-field.gif # docs/demos/session-create-new-project-1a3d0-parent-dir-and-project-name-chromium-dom.gif # docs/demos/session-create-new-project-1a3d0-parent-dir-and-project-name.gif # docs/demos/session-create-new-project-651a4-up-defaults-to-New-Worktree-chromium-dom.gif # docs/demos/session-create-new-project-651a4-up-defaults-to-New-Worktree.gif # docs/demos/session-create-new-project-789fd-and-project-name-are-filled-chromium-dom.gif # docs/demos/session-create-new-project-789fd-and-project-name-are-filled.gif # docs/demos/session-create-new-project-8f754-m-sends-correct-RPC-payload-chromium-dom.gif # docs/demos/session-create-new-project-8f754-m-sends-correct-RPC-payload.gif # docs/demos/session-create-new-project-90413-rectory-path-does-not-exist-chromium-dom.gif # docs/demos/session-create-new-project-90413-rectory-path-does-not-exist.gif # docs/demos/session-create-new-project-c4ade-it-without-creating-session-chromium-dom.gif # docs/demos/session-create-new-project-c4ade-it-without-creating-session.gif # docs/demos/session-create-new-project-d589d-s-visible-in-creation-panel-chromium-dom.gif # docs/demos/session-create-new-project-d589d-s-visible-in-creation-panel.gif # docs/demos/session-create-new-project-e22ba-hen-New-Project-is-selected-chromium-dom.gif # docs/demos/session-create-new-project-e22ba-hen-New-Project-is-selected.gif # docs/demos/session-create-new-project-ef382-dir-and-project-name-fields-chromium-dom.gif # docs/demos/session-create-new-project-ef382-dir-and-project-name-fields.gif # docs/demos/session-create-new-project-fb84d-t-with-createIfMissing-true-chromium-dom.gif # docs/demos/session-create-new-project-fb84d-t-with-createIfMissing-true.gif # docs/demos/session-create-new-worktre-0fc57-ee-is-the-default-selection-chromium-dom.gif # docs/demos/session-create-new-worktre-0fc57-ee-is-the-default-selection.gif # docs/demos/session-create-new-worktre-7f600-type-with-branch-in-payload-chromium-dom.gif # docs/demos/session-create-new-worktre-7f600-type-with-branch-in-payload.gif # docs/demos/session-create-new-worktre-9dd2f-ckbox-for-new-worktree-mode-chromium-dom.gif # docs/demos/session-create-new-worktre-9dd2f-ckbox-for-new-worktree-mode.gif # docs/demos/session-create-new-worktre-ce636-itle-as-branch-is-unchecked-chromium-dom.gif # docs/demos/session-create-new-worktre-ce636-itle-as-branch-is-unchecked.gif # docs/demos/session-create-new-worktre-ce772--title-as-branch-is-checked-chromium-dom.gif # docs/demos/session-create-new-worktre-ce772--title-as-branch-is-checked.gif # docs/demos/session-lifecycle-Session--10237-Session-status-filter-works-chromium-dom.gif # docs/demos/session-lifecycle-Session--10237-Session-status-filter-works.gif # docs/demos/session-lifecycle-Session--2a57f-ion-create-UI-is-accessible-chromium-dom.gif # docs/demos/session-lifecycle-Session--2a57f-ion-create-UI-is-accessible.gif # docs/demos/session-lifecycle-Session--7b493-ssion-management-page-loads-chromium-dom.gif # docs/demos/session-lifecycle-Session--7b493-ssion-management-page-loads.gif # docs/demos/session-lifecycle-Session--995da-paused-sessions-are-visible-chromium-dom.gif # docs/demos/session-lifecycle-Session--995da-paused-sessions-are-visible.gif # docs/demos/shell-tabs-shell-tabs-shel-39ca1-ellDialog-When-ctrlTPressed-chromium-dom.gif # docs/demos/shell-tabs-shell-tabs-shel-39ca1-ellDialog-When-ctrlTPressed.gif # docs/demos/shell-tabs-shell-tabs-shel-684a1-lTab-When-plusButtonClicked-chromium-dom.gif # docs/demos/shell-tabs-shell-tabs-shel-684a1-lTab-When-plusButtonClicked.gif # docs/demos/shell-tabs-shell-tabs-shel-70877-actionMenuSpawnShellClicked-chromium-dom.gif # docs/demos/shell-tabs-shell-tabs-shel-70877-actionMenuSpawnShellClicked.gif # docs/demos/shell-tabs-shell-tabs-shel-e3a96-ab-When-deleteButtonClicked-chromium-dom.gif # docs/demos/shell-tabs-shell-tabs-shel-e3a96-ab-When-deleteButtonClicked.gif # docs/demos/smoke-Smoke-Tests-home-page-loads-successfully-chromium-dom.gif # docs/demos/smoke-Smoke-Tests-home-page-loads-successfully.gif # docs/demos/smoke-Smoke-Tests-navigation-header-is-present-chromium-dom.gif # docs/demos/smoke-Smoke-Tests-navigation-header-is-present.gif # docs/demos/smoke-Smoke-Tests-review-queue-page-loads-successfully-chromium-dom.gif # docs/demos/smoke-Smoke-Tests-review-queue-page-loads-successfully.gif # docs/demos/terminal-mobile-overflow-m-50056--visible-on-mobile-viewport-chromium-dom.gif # docs/demos/terminal-mobile-overflow-m-50056--visible-on-mobile-viewport.gif # docs/demos/terminal-mobile-overflow-m-53f8a-le-without-opening-overflow-chromium-dom.gif # docs/demos/terminal-mobile-overflow-m-53f8a-le-without-opening-overflow.gif # docs/demos/terminal-mobile-overflow-m-64891--row-with-secondary-buttons-chromium-dom.gif # docs/demos/terminal-mobile-overflow-m-64891--row-with-secondary-buttons.gif # docs/demos/terminal-mobile-overflow-m-76599-Less-hides-the-overflow-row-chromium-dom.gif # docs/demos/terminal-mobile-overflow-m-76599-Less-hides-the-overflow-row.gif # docs/demos/terminal-resize-terminal-r-9b065-tays-connected-after-resize.gif # docs/demos/terminal-resize-terminal-r-e5784--after-resize-dom-renderer--chromium-dom.gif # docs/demos/theme-background-theme-bac-3d682-background-under-dark-theme-chromium-dom.gif # docs/demos/theme-background-theme-bac-3d682-background-under-dark-theme.gif # docs/demos/touch-targets-Touch-target-0c83a--keyboard-keys-are-≥44×44px-chromium-dom.gif # docs/demos/touch-targets-Touch-target-0c83a--keyboard-keys-are-≥44×44px.gif # docs/demos/touch-targets-Touch-target-31878-tton-is-≥44×44px-on-desktop-chromium-dom.gif # docs/demos/touch-targets-Touch-target-31878-tton-is-≥44×44px-on-desktop.gif # docs/demos/touch-targets-Touch-target-66543--session-button-is-≥44×44px-chromium-dom.gif # docs/demos/touch-targets-Touch-target-66543--session-button-is-≥44×44px.gif # docs/demos/touch-targets-Touch-target-b4371--actions-button-is-≥44×44px-chromium-dom.gif # docs/demos/touch-targets-Touch-target-b4371--actions-button-is-≥44×44px.gif # docs/demos/touch-targets-Touch-target-cbd53--toolbar-toggle-is-≥44×44px-chromium-dom.gif # docs/demos/touch-targets-Touch-target-cbd53--toolbar-toggle-is-≥44×44px.gif # docs/demos/touch-targets-Touch-target-e8172-om-nav-items-are-≥44px-tall-chromium-dom.gif # docs/demos/touch-targets-Touch-target-e8172-om-nav-items-are-≥44px-tall.gif # docs/demos/visual-regression-omnibar-open-chromium-dom.gif # docs/demos/visual-regression-omnibar-open.gif # docs/demos/visual-regression-session-list-empty-state-chromium-dom.gif # docs/demos/visual-regression-session-list-empty-state.gif # docs/demos/workspace-management-Works-debfe-e-information-is-accessible-chromium-dom.gif # docs/demos/workspace-management-Works-debfe-e-information-is-accessible.gif # docs/demos/workspace-management-Works-ee243-h---Review-queue-page-loads-chromium-dom.gif # docs/demos/workspace-management-Works-ee243-h---Review-queue-page-loads.gif # docs/registry/features/backend/ImportGitHubIssue.json # docs/registry/features/backend/ListGitHubIssues.json # docs/registry/features/backend/SearchGitHubRepos.json # docs/registry/features/backend/backlog/approve-plan.json # docs/registry/features/backend/backlog/archive-item.json # docs/registry/features/backend/backlog/attach-session.json # docs/registry/features/backend/backlog/cancel-triage.json # docs/registry/features/backend/backlog/create-item.json # docs/registry/features/backend/backlog/create-source.json # docs/registry/features/backend/backlog/delete-item.json # docs/registry/features/backend/backlog/delete-source.json # docs/registry/features/backend/backlog/get-item.json # docs/registry/features/backend/backlog/get-sync-history.json # docs/registry/features/backend/backlog/list-items.json # docs/registry/features/backend/backlog/list-sources.json # docs/registry/features/backend/backlog/override-verdict.json # docs/registry/features/backend/backlog/spawn-session.json # docs/registry/features/backend/backlog/suggest-next.json # docs/registry/features/backend/backlog/transition-status.json # docs/registry/features/backend/backlog/trigger-re-review.json # docs/registry/features/backend/backlog/trigger-sync.json # docs/registry/features/backend/backlog/trigger-triage.json # docs/registry/features/backend/backlog/update-item.json # docs/registry/features/backend/backlog/update-source.json # docs/registry/features/backend/session/run-one-shot.json # docs/registry/features/frontend/ui/backlog-board.json # docs/registry/features/frontend/ui/backlog-item-card.json # docs/registry/features/frontend/ui/backlog-item-detail.json # docs/registry/features/frontend/ui/backlog-item-form.json # docs/registry/features/frontend/ui/backlog-list-page.json # docs/tasks/completed/system-service-autostart.md # gen/proto/go/session/v1/backlog.pb.go # gen/proto/go/session/v1/session.pb.go # gen/proto/go/session/v1/sessionv1connect/backlog.connect.go # gen/proto/go/session/v1/types.pb.go # github/client.go # github/clone.go # github/etag_cache.go # github/http_client.go # github/keychain.go # github/repos.go # github/user_pr_cache.go # go.mod # go.sum # main.go # pkg/events/types.go # profiling/profiling.go # proto/session/v1/backlog.proto # proto/session/v1/session.proto # proto/session/v1/types.proto # scripts/build-tmux.sh # scripts/install-service.sh # scripts/setup-codesign.sh # server/adapters/instance_adapter.go # server/adapters/review_queue_adapter.go # server/adapters/review_queue_adapter_test.go # server/analytics/subscriber.go # server/analytics/subscriber_test.go # server/dependencies.go # server/dependencies_test.go # server/events/forward.go # server/features/backlog.go # server/mcp/server.go # server/mcp/server_integration_test.go # server/mcp/tools_backlog.go # server/mcp/tools_backlog_test.go # server/mcp/tools_github.go # server/mcp/tools_goal.go # server/mcp/tools_goal_test.go # server/mcp/tools_terminal.go # server/mcp/tools_terminal_test.go # server/push/subscriber.go # server/push/subscriber_test.go # server/review_queue_manager.go # server/review_queue_manager_test.go # server/server.go # server/server_integration_test.go # server/services/approval_handler.go # server/services/autonomous_orchestration_service.go # server/services/autonomous_orchestration_service_test.go # server/services/backlog_github_rpc_test.go # server/services/backlog_service.go # server/services/backlog_service_test.go # server/services/backlog_triage_harness_test.go # server/services/connectrpc_websocket.go # server/services/defaults_service.go # server/services/defaults_service_test.go # server/services/event_converter_test.go # server/services/feature_flag_service.go # server/services/file_service.go # server/services/file_service_test.go # server/services/github_user_service.go # server/services/hook_injector.go # server/services/hook_injector_test.go # server/services/hook_receivers.go # server/services/local_file_service.go # server/services/local_file_service_test.go # server/services/mcp_injector.go # server/services/mcp_injector_test.go # server/services/oneshot_test.go # server/services/path_completion_service.go # server/services/path_completion_service_test.go # server/services/push_service.go # server/services/search_service.go # server/services/session_service.go # server/services/session_service_create_test.go # server/services/session_service_shells.go # server/services/session_service_stream_terminal_test.go # server/services/session_service_test.go # server/services/terminal_service.go # server/services/unfinished_work_service.go # server/services/unfinished_work_test.go # server/services/workspace_service_test.go # session/actor.go # session/autonomous_driver.go # session/autonomous_driver_test.go # session/backlog.go # session/backlog_commands.go # session/backlog_commands_test.go # session/backlog_context.go # session/backlog_context_test.go # session/backlog_integration_test.go # session/backlog_lifecycle.go # session/backlog_lifecycle_test.go # session/backlog_plugin_github.go # session/backlog_plugin_github_prs.go # session/backlog_plugin_github_test.go # session/backlog_review.go # session/backlog_review_test.go # session/backlog_sync.go # session/backlog_sync_test.go # session/backlog_test.go # session/backlog_triage.go # session/backlog_triage_test.go # session/capture_test.go # session/circular_buffer.go # session/claude_command_builder.go # session/claude_controller.go # session/detection/approval.go # session/detection/detector_test.go # session/detection/pattern_set.go # session/detection/pattern_set_test.go # session/detection/proto_mapping.go # session/ent/backlogitem.go # session/ent/backlogitem/backlogitem.go # session/ent/backlogitem/where.go # session/ent/backlogitem_create.go # session/ent/backlogitem_query.go # session/ent/backlogitem_update.go # session/ent/backlogstatusevent.go # session/ent/backlogstatusevent/backlogstatusevent.go # session/ent/backlogstatusevent/where.go # session/ent/backlogstatusevent_create.go # session/ent/backlogstatusevent_update.go # session/ent/client.go # session/ent/ent.go # session/ent/hook/hook.go # session/ent/itemsession.go # session/ent/itemsession/itemsession.go # session/ent/itemsession/where.go # session/ent/itemsession_create.go # session/ent/itemsession_update.go # session/ent/migrate/schema.go # session/ent/mutation.go # session/ent/predicate/predicate.go # session/ent/runtime.go # session/ent/schema/backlog_item.go # session/ent/schema/backlog_status_event.go # session/ent/schema/item_session.go # session/ent/schema/session_goal.go # session/ent/sessiongoal.go # session/ent/sessiongoal/sessiongoal.go # session/ent/sessiongoal/where.go # session/ent/sessiongoal_create.go # session/ent/sessiongoal_update.go # session/ent/tx.go # session/ent_repository.go # session/ent_repository_backlog.go # session/ent_repository_backlog_test.go # session/external_tmux_streamer.go # session/git/util.go # session/git/worktree.go # session/git/worktree_creation_test.go # session/git/worktree_git.go # session/git/worktree_git_test.go # session/git/worktree_ops.go # session/git_worktree_manager.go # session/headless/caller.go # session/headless/client.go # session/headless/fake_runner.go # session/headless/features.go # session/headless/features_test.go # session/headless/integration_test.go # session/headless/pool_test.go # session/headless/runner.go # session/health.go # session/health_test.go # session/hibernation_sweeper_test.go # session/instance.go # session/instance_actor_setters.go # session/instance_approval.go # session/instance_checkpoint.go # session/instance_claude.go # session/instance_controller.go # session/instance_hibernate.go # session/instance_lifecycle_test.go # session/instance_serialization.go # session/instance_shells.go # session/instance_state.go # session/instance_terminal.go # session/instance_tmux.go # session/instance_tmux_test.go # session/instance_workspace.go # session/instance_workspace_test.go # session/instance_worktree.go # session/integration_test.go # session/memory/reader.go # session/mux/multiplexer.go # session/mux/testmain_test.go # session/orphan_sweep.go # session/orphan_sweep_test.go # session/pr_status_poller.go # session/pty_discovery.go # session/pty_discovery_test.go # session/repo_path.go # session/repository.go # session/review_queue_determiner.go # session/review_queue_determiner_test.go # session/review_queue_poller.go # session/review_queue_poller_test.go # session/review_state.go # session/session_driver.go # session/session_driver_test.go # session/startup_scanner_test.go # session/state_machine_test.go # session/status_mapping.go # session/status_mapping_test.go # session/storage.go # session/storage_backlog.go # session/storage_goal_test.go # session/storage_test.go # session/tmux/control_mode.go # session/tmux/server_registry.go # session/tmux/server_registry_integration_test.go # session/tmux/testmain_test.go # session/tmux/tmux.go # session/tmux/tmux_test.go # session/tmux_backend_test.go # session/tmux_process_manager.go # session/unfinished/gogit_vcs_reader.go # session/unfinished/gogit_vcs_reader_limits_test.go # session/unfinished/scanner.go # session/unfinished/scanner_test.go # session/worktree_pr_poller.go # telemetry/telemetry.go # tests/e2e/accessibility.spec.ts # tests/e2e/backlog-sources-settings.spec.ts # tests/e2e/backlog.spec.ts # tests/e2e/fixtures/clean-theme.json # tests/e2e/fixtures/cyberpunk77-theme.json # tests/e2e/fixtures/matrix-theme.json # tests/e2e/fixtures/wh40k-theme.json # tests/e2e/nav-navigation.spec.ts # tests/e2e/package-lock.json # tests/e2e/pages/BacklogPage.ts # tests/e2e/session-create-directory.spec.ts # tests/e2e/session-create-existing-worktree.spec.ts # tests/e2e/touch-targets.spec.ts # tests/e2e/unfinished-work.spec.ts # third_party/tmux~origin_main # tools/lint/cmd/linter/main.go # tools/scanner/backend/proto_scanner.go # web-app/.eslintrc.json # web-app/jest.config.js # web-app/jest.setup.js # web-app/lighthouserc.json # web-app/package-lock.json # web-app/package.json # web-app/pnpm-lock.yaml # web-app/src/__mocks__/styleMock.js # web-app/src/app/backlog/backlog.css.ts # web-app/src/app/backlog/board/page.tsx # web-app/src/app/backlog/page.tsx # web-app/src/app/config/ConfigPageContent.tsx # web-app/src/app/config/config.css.ts # web-app/src/app/files/page.tsx # web-app/src/app/insights/InsightsDashboard.tsx # web-app/src/app/insights/SessionDetailDrawer.css.ts # web-app/src/app/insights/SessionDetailDrawer.tsx # web-app/src/app/insights/SessionsTable.css.ts # web-app/src/app/insights/SessionsTable.tsx # web-app/src/app/page.tsx # web-app/src/app/rules/page.css.ts # web-app/src/app/rules/page.tsx # web-app/src/app/settings/backlog-sources/page.tsx # web-app/src/app/settings/features/page.tsx # web-app/src/app/settings/page.tsx # web-app/src/app/settings/settings.css.ts # web-app/src/app/unfinished/UnfinishedTab.css.ts # web-app/src/app/unfinished/UnfinishedTab.tsx # web-app/src/app/unfinished/page.tsx # web-app/src/components/backlog/BacklogBoard.css.ts # web-app/src/components/backlog/BacklogBoard.tsx # web-app/src/components/backlog/BacklogItemBadge.tsx # web-app/src/components/backlog/BacklogItemCard.css.ts # web-app/src/components/backlog/BacklogItemCard.tsx # web-app/src/components/backlog/BacklogItemDetail.css.ts # web-app/src/components/backlog/BacklogItemDetail.regression.test.tsx # web-app/src/components/backlog/BacklogItemDetail.tsx # web-app/src/components/backlog/BacklogItemForm.css.ts # web-app/src/components/backlog/BacklogItemForm.test.tsx # web-app/src/components/backlog/BacklogItemForm.tsx # web-app/src/components/backlog/BacklogItemPanel.css.ts # web-app/src/components/backlog/BacklogItemPanel.tsx # web-app/src/components/backlog/GateVerdictBox.css.ts # web-app/src/components/backlog/GateVerdictBox.test.tsx # web-app/src/components/backlog/GateVerdictBox.tsx # web-app/src/components/backlog/GitHubIssuePicker.css.ts # web-app/src/components/backlog/GitHubIssuePicker.tsx # web-app/src/components/backlog/SessionMonitor.css.ts # web-app/src/components/backlog/SessionMonitor.tsx # web-app/src/components/backlog/TriageReviewPanel.css.ts # web-app/src/components/backlog/TriageReviewPanel.test.tsx # web-app/src/components/backlog/TriageReviewPanel.tsx # web-app/src/components/files/LocalFileBrowser.css.ts # web-app/src/components/files/LocalFileBrowser.tsx # web-app/src/components/layout/DrawerNav.tsx # web-app/src/components/layout/Header.css.ts # web-app/src/components/layout/Header.tsx # web-app/src/components/layout/__tests__/DrawerNav.test.tsx # web-app/src/components/layout/__tests__/Header.test.tsx # web-app/src/components/pane/PaneHeader.tsx # web-app/src/components/pane/PaneSplitRenderer.tsx # web-app/src/components/rules/RulePreview.tsx # web-app/src/components/sessions/ApprovalAnalyticsPanel.css.ts # web-app/src/components/sessions/DetectionEventsPanel.tsx # web-app/src/components/sessions/FileContentViewer.css.ts # web-app/src/components/sessions/FileContentViewer.tsx # web-app/src/components/sessions/FileTree.css.ts # web-app/src/components/sessions/FileTree.tsx # web-app/src/components/sessions/FilesTab.css.ts # web-app/src/components/sessions/FilesTab.tsx # web-app/src/components/sessions/NewShellDialog.css.ts # web-app/src/components/sessions/NewShellDialog.tsx # web-app/src/components/sessions/Omnibar.tsx # web-app/src/components/sessions/OmnibarCreationPanel.tsx # web-app/src/components/sessions/QuickOpenPalette.css.ts # web-app/src/components/sessions/QuickOpenPalette.tsx # web-app/src/components/sessions/RecentFilesSection.css.ts # web-app/src/components/sessions/RecentFilesSection.tsx # web-app/src/components/sessions/ReviewQueuePanel.css.ts # web-app/src/components/sessions/ReviewQueuePanel.tsx # web-app/src/components/sessions/SessionActionsOverflow.tsx # web-app/src/components/sessions/SessionCard.tsx # web-app/src/components/sessions/SessionDetailView.tsx # web-app/src/components/sessions/SessionList.css.ts # web-app/src/components/sessions/SessionList.tsx # web-app/src/components/sessions/SessionRow.tsx # web-app/src/components/sessions/StatusBadge.tsx # web-app/src/components/sessions/SubStatusChip.tsx # web-app/src/components/sessions/TerminalOutput.tsx # web-app/src/components/sessions/VcsPanel.css.ts # web-app/src/components/sessions/VcsPanel.tsx # web-app/src/components/sessions/XtermTerminal.tsx # web-app/src/components/sessions/__tests__/FileTree.test.tsx # web-app/src/components/sessions/__tests__/NewShellDialog.test.tsx # web-app/src/components/sessions/__tests__/Omnibar.alias.test.tsx # web-app/src/components/sessions/__tests__/Omnibar.pathcompletion.test.tsx # web-app/src/components/sessions/__tests__/OmnibarCreationPanel.attach.test.tsx # web-app/src/components/sessions/__tests__/ReviewQueuePanel.test.tsx # web-app/src/components/sessions/__tests__/SessionActionsOverflow.test.tsx # web-app/src/components/sessions/__tests__/SessionCard.approval-suppression.test.tsx # web-app/src/components/sessions/__tests__/SessionCard.click.test.tsx # web-app/src/components/sessions/__tests__/SessionDetail.embedded.test.tsx # web-app/src/components/sessions/__tests__/StatusBadge.test.tsx # web-app/src/components/sessions/__tests__/TerminalOutput.enter-detection.test.tsx # web-app/src/components/sessions/__tests__/TerminalOutput.logstream.test.tsx # web-app/src/components/sessions/__tests__/TerminalOutput.reconnect.test.tsx # web-app/src/components/sessions/__tests__/TerminalOutput.toolbar-analytics.test.tsx # web-app/src/components/sessions/__tests__/TerminalOutput.upload.test.tsx # web-app/src/components/sessions/__tests__/TerminalOutputBug.test.tsx # web-app/src/components/sessions/__tests__/XtermTerminal.test.tsx # web-app/src/components/settings/GlobalDefaultsForm.css.ts # web-app/src/components/settings/GlobalDefaultsForm.tsx # web-app/src/components/shared/DiffRenderer.css.ts # web-app/src/components/shared/DiffRenderer.tsx # web-app/src/components/ui/NotificationPanel.css.ts # web-app/src/components/ui/RepoPathInput.tsx # web-app/src/components/unfinished/CommitPushModal.css.ts # web-app/src/components/unfinished/CommitPushModal.tsx # web-app/src/components/unfinished/UnfinishedItem.css.ts # web-app/src/components/unfinished/UnfinishedItemDetail.css.ts # web-app/src/components/unfinished/UnfinishedItemDetail.tsx # web-app/src/components/unfinished/WorktreeDiffModal.css.ts # web-app/src/components/workflows/WorkflowForm.tsx # web-app/src/gen/session/v1/backlog_pb.ts # web-app/src/gen/session/v1/session_pb.ts # web-app/src/gen/session/v1/types_pb.ts # web-app/src/lib/backlog/status.ts # web-app/src/lib/contexts/FeatureFlagsContext.tsx # web-app/src/lib/contexts/NotificationContext.tsx # web-app/src/lib/contexts/OmnibarContext.tsx # web-app/src/lib/contexts/SessionServiceContext.tsx # web-app/src/lib/features/features/unfinished-work.ts # web-app/src/lib/hooks/__tests__/useBrowserLogStream.test.ts # web-app/src/lib/hooks/__tests__/useSessionNotifications.test.ts # web-app/src/lib/hooks/__tests__/useSessionService.test.ts # web-app/src/lib/hooks/__tests__/useTerminalFlowControl.test.ts # web-app/src/lib/hooks/useBacklogService.ts # web-app/src/lib/hooks/useBrowserLogStream.ts # web-app/src/lib/hooks/useGitHubIssuePicker.ts # web-app/src/lib/hooks/useSessionNotifications.ts # web-app/src/lib/hooks/useSessionService.ts # web-app/src/lib/hooks/useTerminalFlowControl.ts # web-app/src/lib/hooks/useTerminalStream.ts # web-app/src/lib/hooks/useVcsStatus.ts # web-app/src/lib/hooks/useWorktreeSuggestions.ts # web-app/src/lib/nav-pages.ts # web-app/src/lib/omnibar/actions/dispatch.test.ts # web-app/src/lib/omnibar/actions/dispatch.ts # web-app/src/lib/omnibar/actions/types.ts # web-app/src/lib/omnibar/detector.test.ts # web-app/src/lib/omnibar/detectors/CommandDetector.ts # web-app/src/lib/omnibar/types.ts # web-app/src/lib/routes.ts # web-app/src/lib/store/__tests__/reviewQueueSlice.test.ts # web-app/src/lib/store/__tests__/sessionsSlice.test.ts # web-app/src/lib/store/store.ts # web-app/src/lib/utils/__tests__/deriveWorkingState.test.ts # web-app/src/lib/utils/deriveWorkingState.ts # web-app/src/lib/utils/issuePickerCache.ts # web-app/src/lib/utils/parseDiff.ts # web-app/src/styles/pane/paneHeader.css.ts # web-app/src/styles/theme-contract.css.ts # web-app/src/styles/theme.css.ts # web-app/tests/e2e/navigation.spec.ts
…nflict resolution
Resolving the origin/main merge (LFS history cutover left this branch far
behind main's history) required bulk-resolving hundreds of untouched-by-this-PR
conflicts to main's side. That correctly dropped stale duplicate content, but
also silently dropped a handful of pre-existing fixes/features that exist only
on this branch (inherited from an earlier, now-unreachable main state) with no
main-side equivalent:
- session/detection/proto_mapping.go: restore DetectedStatusToSubStatus,
which proto_mapping_test.go (auto-merged, unaffected by the bulk resolve)
already exercises.
- web-app/src/components/sessions/SubStatusChip.tsx: restore the
forward-compatible "render nothing" default case for unrecognized
SubStatus values instead of the throwing assertNever main's revision uses
— proto enums are forward-compatible and one unrecognized wire value must
not crash the sessions UI.
- TerminalOutput{,.reconnect,.enter-detection,.logstream,.toolbar-analytics,
.upload}.test.tsx and TerminalOutputBug.test.tsx: add the
requestFullResync/markResyncComplete/markPaneResponseReceived mock fields
useVisibilityResync (only present on this branch, not main) calls
unconditionally on unmount/session-id change, plus the XtermTerminal
mock's resize() stub the pre-sizing effect now calls.
Found by running the full frontend/backend test suites after the merge
rather than trusting the conflict resolution alone.
Contributor
✅ Registry ValidationTest Coverage: 19/179 features have
|
Contributor
Go Benchmarks (Tier 1) |
Contributor
E2E RPC Latency |
Contributor
UX Analysis
|
Contributor
📊 Feature E2E CoverageFeature coverage report unavailable
|
…e peer dep @xterm/xterm was bumped to 6.0.0 in an unrelated PR after PR #272 added @xterm/addon-canvas for the WebGL->Canvas fallback (ADR-001). Upstream removed the canvas renderer from the xterm.js monorepo entirely as of 6.0.0 (xtermjs/xterm.js#5105), so addon-canvas@0.7.0's peer dependency (^5.0.0) will never be updated -- pnpm install only warns, it doesn't fail, so CI stayed green with no signal either way. Verified ground truth instead of trusting the peer-dep warning: a real, unmocked CanvasAddon activates, resizes, writes, and disposes cleanly against a real, unmocked xterm 6 Terminal (jsdom has no real 2D canvas context, so a minimal fake one stands in, same workaround already used for @xterm/addon-serialize). Added a regression test proving this and documented the finding in ADR-001's addendum and inline in XtermTerminal.tsx, so a future xterm major bump gets caught instead of silently shipping a dead fallback behind a green peer-dep warning. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W3683CH7Fs9zYR2yP3Dpba
Contributor
🎬 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. |
Contributor
✅ Registry ValidationTest Coverage: 19/179 features have
|
Contributor
Frontend Terminal Throughput |
tstapler
marked this pull request as ready for review
July 28, 2026 01:32
tstapler
added a commit
that referenced
this pull request
Jul 28, 2026
…#272's merge commit 591059a already removed these from git and gitignored benchmarks/ (baselines are persisted via GitHub Actions cache instead) — but PR #272's branch predated that fix and still had them tracked, so merging it resurrected all four as tracked files again. .gitignore doesn't stop a merge from reintroducing already-tracked-on-one-side content, so this needs its own removal. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W3683CH7Fs9zYR2yP3Dpba
This was referenced Jul 29, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
fit()<->ResizeObserverfeedback loop: a WebGL cell-width measurement mismatch (actual vs. expected px/col) meantFitAddon.proposeDimensions()never reached a fixpoint, so every resize observation re-triggered another sub-pixel resize, flooding the server withTerminalResizeRPCs and pegging CPU.What Changed
XtermTerminal.tsx): only schedulesfit()whenproposeDimensions()reports integer cols/rows that differ from the currently-applied size AND repeat on two consecutive ticks — closes both the sub-cell-jitter loop and boundary-flapping near an exact cell-width boundary.useTerminalFlowControl.ts):resize()skips sendingTerminalResize(and the follow-upcurrentPaneRequest) when the incoming(cols, rows)equals the last pair actually sent — independent of the existing 200ms time throttle.force: truethird argument, with regression tests asserting that literal argument at each call site.Number.isFiniteguards againstproposeDimensions()returningInfinity), sofit()has a stable fixpoint even when the WebGL glyph metrics disagree with the DOM measurement.XtermTerminal.test.tsx,TerminalOutput.test.tsx, anduseTerminalFlowControl.test.ts(32 tests) simulating sub-cell jitter, boundary-flapping, WebGL mismatch escalation, and both force-bypass call sites — each assertingfit()/RPC/dispose fire at most the expected number of times, not once per observed frame.project_plans/terminal-resize-fit-loop/(requirements, research, architecture/adversarial review, plan, validation, pre-mortem).Test plan
npx jest XtermTerminal TerminalOutput useTerminalFlowControl— 3 suites, 32/32 passing[XtermTerminal]resize/fit log lines fired in an 8s watch window.Container resized...->Terminal dimensions BEFORE fit...->Sending resize to server: 172x37->Sampler confirmed resize, fit applied: 172 cols x 37 rows), then zero further lines over the next 8s.Originally opened as TylerStaplerAtFanatics/stapler-squad#191; moved here per request.