Keep this file readable for humans: do not hard-wrap prose lines; let editors wrap long lines visually. Keep headings, lists, tables, and code blocks structurally formatted. Keep these notes a reflection of the current implementation status. History records, such as merge, ports, and update notes, are not meant for this file. Each top-level section declares its active worktree branch.
nonemeans no active worktree owns that section, and subsections inherit the parent section's branch.
Local feature heads whose associated PRs are closed remain local rebuild inputs; a closed PR is not an inactive-branch marker.
The fork integration boundary is the attached base/main control branch, with its Windows worktree at E:\Projects\t3code.worktrees\base-main. It advances only by fast-forward to the selected upstream/main commit. Each worktree update squashes the assigned branch, including base/fork, on its old upstream base, then rebases it onto that control commit while preserving upstream tracking. The completed branch carries one combined customization commit, in addition to follow-up fixes and documentation, directly above the control commit, or equals it if no customizations remain. Customization commits stay on their assigned branches.
For diagnosing existing verification failures, obtain native control results through the main checkout's scripts/worktree-baseline.ts ensure command; agents must not install or run checks in the control worktree directly. If an earlier failure prevented the relevant test from running, use the helper's --test <repository-relative-file> option for separately cached evidence. The helper fingerprints the control commit, host/toolchain, temporary directory, and test selection, and serializes producers sharing the control checkout. A failure seen in a fork worktree is pre-existing only when the same failure appears in the corresponding same-host control check. An unexecuted control check is missing evidence, not a reason to change fork code. Matching environment failures remain recorded without changing base/main.
Worktree branch: none
AGENTS.md points implementation and evaluation work at this file. A task that only orchestrates worktree subagents follows its invoked skill instructions without loading unrelated documentation. Each feature or fix worktree keeps its branch-only contract in BRANCH_DETAILS.md, alongside accessory referenced documentation. These files are purposefully git-ignored through the repository's .git/info/exclude, not .gitignore. base/fork, and therefore main, consolidates BRANCH_DETAILS.md material here and tracks copies of the accessory documentation, but never tracks the branch-specific BRANCH_DETAILS.md files themselves. A newly created worktree restores its ignored files from base/fork: the branch's section here seeds BRANCH_DETAILS.md, and the accessory copies are copied in. A new accessory document joins that exclude block and gains a base/fork copy.
Recreate local main for each assembly. Feature and fix branches are read-only inputs; mirror FORK.md and AGENTS.md into base/fork as integration documentation changes. Rewind main to its latest upstream base, fast-forward to base/main, then apply all base/fork commits or fast-forward main to base/fork when equivalent. Cherry-pick each remaining worktree branch's commits onto main in turn. Complete its integration glue, focused validation, and affected FORK.md updates on main before applying the next branch. Reassess these notes against current branch implementations and documentation. Keep documentation with the glue it describes, in the cherry-pick commit for conflict resolutions or in the same follow-up integration commit for subsequent work.
The process ownership rule in AGENTS.md permits cleanup after recovering task ownership. Upstream's spawn-time PID requirement caused repeated approval requests for verified surviving children and detached helpers during parallel worktree runs.
Thread-subscription test fixtures supply the cold-storage readable-thread boundary as well as the upstream transcript methods. The WebSocket RPC factory keeps core and Version Control handler layers separate while serving the complete shared RPC group, schemas and middleware. This bounds TypeScript aggregate requirement inference without casts or manufactured service unions. Preserve inferred handler requirements when adding services; optional Context.Reference defaults must remain optional.
The repository-local orchestration skills divide responsibilities as follows:
$worktreesis the worker contract for exact-worktree execution, same-host control comparison, runtime leases, cross-host source transfer, Android and iOS isolation, and owned teardown.$spawn-worktreedispatches one worker to one absolute worktree path with only its branch task and required skills. It requires the worker to read that worktree's git-ignoredBRANCH_DETAILS.md, supervises runtime ownership without leaking orchestration context into the child prompt, and performs exact-worktree lease cleanup only after the worker is terminal.$spawn-worktreesinventories active non-mainworktrees and dispatches through$spawn-worktree, explicitly excluding thebase/maincontrol worktree and inactive or maintainer-owned worktrees from worker tasks.$update-worktreesquashes and rebases one assigned branch at the control boundary, assesses only the interaction between incoming upstream work and that branch's customizations, and adds adaptations and documentation as follow-ups on top.$update-worktreesfirst fetches upstream and fast-forwards a clean attachedbase/mainto the selectedupstream/mainboundary. Only after verifying that exact control state does it dispatch the individual branch updates; it does not mutate the other worktrees itself.$rebuild-mainrebuilds only localmainfrom the control boundary andbase/fork, then applies each remaining worktree branch with its integration glue andFORK.mdupdates before proceeding to the next. Feature and fix branches are read-only throughout this workflow;base/forkreceives the mirrored documentation commits.$babysit-worktreesdelegates each branch's pull-request comments through$babysit, then invokes$rebuild-mainwhen fixes were produced.$update-prsdelegates pull-request publication through$prwithout doing branch work in the orchestrator.$update-unattendedsequences the full maintenance run: update all worktrees, integrate them, push tracked branches, synchronize the Windows and Mac checkouts, and build the configured Windows, macOS, and Android artifacts. Its per-platform command recipes remain authoritative in that skill.
A branch section whose heading is marked (Currently Inactive) and that names a worktree branch describes a deferred branch. Its worktree stays in place, but worktree orchestration skips it until the mark is removed: it is not updated, its pull request is not checked or babysat, and $rebuild-main does not integrate it unless it is maintainer-owned. Maintainer-owned branches remain read-only integration inputs under the refresh and commit-selection rules in $rebuild-main. $spawn-worktrees enforces the exclusion; the other orchestration skills dispatch through it.
Every repository-local skill above includes matching agents/openai.yaml metadata for its user-facing name, description, and invocation policy. Keep that metadata paired with the corresponding SKILL.md when a skill is renamed, added, or removed.
The customization inventory retains 11 customization worktrees, including base/fork, plus the base/main control and the main assembly checkout. The SSH Android SDK and agent background capture maintainer inputs are registered as protected tracking branches without a checkout. Maintainer-owned entries remain inactive for branch maintenance but eligible for read-only integration into main. Technical review and CI do not establish external contribution eligibility or maintainer approval.
Primary files:
AGENTS.md.agents/skills/worktrees/SKILL.md.agents/skills/spawn-worktree/SKILL.md.agents/skills/spawn-worktrees/SKILL.md.agents/skills/update-worktree/SKILL.md.agents/skills/update-worktrees/SKILL.md.agents/skills/rebuild-main/SKILL.md.agents/skills/babysit-worktrees/SKILL.md.agents/skills/update-prs/SKILL.md.agents/skills/update-unattended/SKILL.md
A customization marked maintainer-owned / pull-only is excluded from local edits, squashing, rebasing, commits, pushes, PR title/description updates and automated review fixes. Only fetch/pull the maintainer's published branch. Its published source and PR link remain registered; a protected local tracking branch may be retained without a checkout. Do not start implementation, verification, review, commit or publication work on these branches. Only inspect or fetch/pull published maintainer source and maintain local ownership records unless the user explicitly changes this restriction. A new evidence comment requires an explicit user request and must not rewrite the original description. Check the ownership marker in the branch's ignored BRANCH_DETAILS.md and .piz/pr.md before any workflow; for a source registered without a checkout, use its records under <git-common-dir>/maintainer-protection/branches/. This restriction takes precedence over publication and update procedures above. Older implementation and verification details describe the local integration input until a maintainer revision is pulled; they are not proof of that revision. An unpublished rebuild supplied as a patch is not obtained by pulling the original PR branch. Synchronizing published history, including forced resynchronization after a maintainer rebase, is allowed. Integration fixes and validation belong on main; documentation commits belong on main and base/fork.
Local Git guards enforce commit and push protection on Windows and Mac across each clone's linked worktrees. The shared Git configuration includes <git-common-dir>/maintainer-protection/config; its repeated t3.maintainerOwnedBranch values are the protected branch registry, readable with git config --get-all t3.maintainerOwnedBranch. The guards reject commits, amendments and outgoing updates involving a protected branch as either source or destination, including force pushes and remote deletion. Existing Vite hooks still run. Incoming history synchronization remains allowed, including fetching rebased maintainer history and resetting the local branch to its published head. Keep this registry aligned with the ownership records above; agents must not bypass the guards to work on a maintainer-owned branch.
When tearing down a maintainer-owned worktree, remove its branch from the guard registry on every clone where protection was installed, alongside removing its ownership records and entire customization section from this file. Use git config --file <git-common-dir>/maintainer-protection/config --fixed-value --unset-all t3.maintainerOwnedBranch <branch> and verify that the branch is absent from the effective registry. Preserve the other protected branches and the retiring branch's remote counterpart on origin. If this is the last protected branch, first remove only the maintainer-protection/config include with git config --local --fixed-value --unset-all include.path maintainer-protection/config; verify that core.hooksPath resolves to the previous hooks, then remove the now-unused guard files. Never leave the guard hooks active with an empty registry.
Worktree branch: none
The installed Windows and Mac databases use the published fork migration IDs 1 through 99 in effect_sql_migrations. Preserve every published ID and migration body, including historical lineage, archive and Magi schema, when assembling main. Preserving schema does not restore retired features. Standalone branch migrations must not replace published main IDs. Read installed ledgers read-only and verify upgrades against isolated databases.
The shared ledger retains the published pre-V2 tail through 68, followed by this canonical main sequence:
| Main ID | Name | Source |
|---|---|---|
| 69 | OrchestrationV2 |
upstream 055_OrchestrationV2.ts |
| 70 | RemoveRedundantProjectionIndexes |
upstream 056_RemoveRedundantProjectionIndexes.ts |
| 71 | ThreadStorageLifecycle |
Conversation Data Savings 064_ThreadStorageLifecycle.ts, through published 071_ThreadStorageLifecycle.ts |
| 72 | MagiV2Projections |
Published 057_MagiProjections.ts, representing the standalone Magi schema |
| 73 | MagiV2ArmClearingAndParticipants |
main-only 073_MagiV2ArmClearingAndParticipants.ts |
| 74 | ScheduledTaskWebhooks |
upstream 057_ScheduledTaskWebhooks.ts |
| 75 | WebhookRelayDeliveries |
upstream 058_WebhookRelayDeliveries.ts |
| 76 | McpAppModelContext |
upstream 059_McpAppModelContext.ts |
| 77 | ThreadSnapshotWindowIndexes |
upstream 060_ThreadSnapshotWindowIndexes.ts |
| 78 | PullRequestReviewGuides |
upstream 061_PullRequestReviewGuides.ts |
| 79 | ThreadFailureLookupIndex | upstream 062_ThreadFailureLookupIndex.ts |
| 80 | DropUnusedV1Projections | upstream 063_DropUnusedV1Projections.ts |
| 81 | CanonicalizeThreadPullRequestLinks | upstream 064_CanonicalizeThreadPullRequestLinks.ts |
| 82 | CodexUsageNativeTurnIndex | upstream 065_CodexUsageNativeTurnIndex.ts |
| 83 | RepairClientSettlementTimestamps | upstream 066_RepairClientSettlementTimestamps.ts |
| 84 | WebhookDedupe | upstream 067_WebhookDedupe.ts |
| 85 | ShellStopRequestIndex | upstream 068_ShellStopRequestIndex.ts |
| 86 | MagiAgentOnly | final standalone Magi cleanup |
| 87 | ThreadMessageSearch | upstream 069_ThreadMessageSearch.ts |
| 88 | PeerLinks | upstream 070_PeerLinks.ts |
| 89 | PeerLinkFence | upstream 071_PeerLinkFence.ts |
| 90 | WaitingLinkRequestIndex | upstream 072_WaitingLinkRequestIndex.ts |
| 91 | ActiveTurnItemIndexes | upstream 073_ActiveTurnItemIndexes.ts |
| 92 | ExcludeAttachmentPlaceholdersFromSearch | upstream 074_ExcludeAttachmentPlaceholdersFromSearch.ts |
| 93 | SubagentDeliveryState | upstream 075_SubagentDeliveryState.ts |
| 94 | ProjectionProjectsPinArchive | upstream 076_ProjectionProjectsPinArchive.ts |
| 95 | DropMagiTables | base/fork 081_DropMagiTables.ts |
| 96 | RepairBooleanSettledOverride | upstream 077_RepairBooleanSettledOverride.ts |
| 97 | DropOrchestrationEventCorrelationIndex | upstream 078_DropOrchestrationEventCorrelationIndex.ts |
| 98 | DelegatedRecoveryIndexes | upstream 079_DelegatedRecoveryIndexes.ts |
| 99 | ScheduledTaskThreadMode | upstream 080_ScheduledTaskThreadMode.ts |
apps/server/src/persistence/Migrations.ts imports the source files under these IDs. Upstream ledger entries relocate to their canonical main IDs with their timestamps and existing schema work preserved. The normalizer removes exact divergent upstream markers, replays missing fork migrations in order, and records relocated markers in one transaction. Upstream preview ledgers are reconciled before that normalization. The startup guard refuses missing or mismatched V2 migrations instead of silently skipping schema.
Cold storage is already published at main 71, Magi schema at 72 and 73, agent cleanup at 86, table cleanup at 95 and boolean-settlement repair at 96. Upstream migration 80 appends at main 99, preserving the installed tail through 98. The frozen historical prompt helper uses Effect's Digest service while preserving its SHA-256 output. No published SQL body changes. Tests cover fresh databases, the published 98 upgrade, complete upstream 80 relocation, previews, rollback and repeated startup. seedUpstreamLedger prepares actual upstream schemas and ledgers for those controls.
Worktree branch: base/fork
The compatibility implementations and focused regression tests live in base/fork, so rebuilding main carries them forward before feature branches are integrated.
Historical V1 thread.created payloads with parentRelation.kind = "subagent" need no fork decoder. apps/server/src/persistence/OrchestrationEventStore.ts decodes only project events and version-2 thread events. The upstream legacy importer builds V2 conversations from projection tables, and compaction removes imported V1 rows without decoding their payloads.
apps/server/src/persistence/initializeV2Database.ts seeds a missing statev2.sqlite from a read-only state.sqlite connection through Node's asynchronous SQLite backup API. It publishes only a complete snapshot and preserves an existing V2 database. The copied legacy tables and migration ledger remain upstream-owned; retain the fork's installed client-profile compatibility without restoring a V1 event decoder or database-copy override.
The shipped web and desktop connection cache has already reached IndexedDB version 7, including archive-time thread-cache eviction. The current schema uses version 8 so those installed profiles also create upstream's query store and its environment-updated index. Preserve this version floor when integrating standalone branches. Requesting an older version prevents cache access even when the server and its SQLite database are healthy. IndexedDB opens lazily and recovers closed connections, while the desktop catalog can load independently through its secure bridge. Store creation and the version-7 archive-eviction hook stay inside the guarded open path; upgrading from version 7 preserves thread snapshots. Regression coverage exercises an installed-profile upgrade, query reads, thread-cache reads and catalog loading. Verify upgrades with an existing client profile as well as server data.
Focused regression coverage:
vp test run apps/web/src/connection/storage.test.tsWorktree branch: none
Rust toolchain selection for desktop artifact builds is machine-local setup, not mergeable branch state. The native resource monitor currently requires Rust 1.95.0, while the host's global default may remain on another version. Install the required toolchain alongside the global default, then persist directory overrides for the main checkout and the shared linked-worktree parent:
rustup toolchain install 1.95.0 --profile minimal
rustup override set --path /Users/luismiguelsousa/Sites/t3code 1.95.0
rustup override set --path /Users/luismiguelsousa/Sites/t3code.worktrees 1.95.0The main override applies only to the main checkout. The t3code.worktrees override applies to all current and future linked worktrees beneath that directory. These overrides persist across shell and application sessions, and every worktree reuses the one installed toolchain.
Re-evaluate both overrides whenever the project's minimum supported Rust version changes or the host's global Rust version changes. A directory override takes precedence over the global default, so a global update alone does not change the toolchain used in these directories. If the project needs a new pinned version, install it once and set both overrides again with that version. If the global default becomes suitable and a separate project pin is no longer wanted, remove both overrides:
rustup override unset --path /Users/luismiguelsousa/Sites/t3code
rustup override unset --path /Users/luismiguelsousa/Sites/t3code.worktreesAfter setting, updating, or removing the overrides, verify rustup show active-toolchain from the main checkout and from one representative linked worktree.
Worktree branch: none
Load $worktrees.
The fork retains upstream's worktree-local development state, stable preferred port selection, single-origin browser development, and Tailscale sharing while preserving the small amount of host-local runtime coordination still needed for desktop and mobile verification.
Expected behavior:
- A linked Git worktree defaults development state to its own ignored
<worktree>/.t3/userdata, even when the parent environment exportsT3CODE_HOME. An explicit--home-dirstill wins. The main checkout retains the implicit~/.t3/devdefault, and submodules are not treated as linked worktrees. - Worktrees derive a stable preferred port offset from their path. The dev runner advances the server and web ports together when either required port is occupied, and it skips web ports blocked by the Fetch standard. The printed
[dev-runner]ports are authoritative. - Browser
devanddev:webmodes are single-origin. They omit bakedVITE_HTTP_URLandVITE_WS_URLvalues and mark that intent explicitly so repository environment files cannot revive them. Vite proxies backend paths through the shared prefix list and usesresolveDevProxyTargetwithT3CODE_HOST: an unset host keeps upstream'slocalhostdefault, wildcard binds map to the matching loopback family, and concrete addresses remain concrete. The shared prefix list includes/mcpalongside/api,/ws,/oauthand/.well-known, preserving upstream's origin policy so OAuth metadata names the browser's public origin. vp run dev --sharepublishes the selected web port throughtailscale serve, builds pairing and development URLs from the tailnet origin, clears stale mappings before sharing, and removes the owned mapping when the runner exits. Its local proxy target useslocalhostso the operating system can select the Vite listener's IPv4 or IPv6 loopback address; focused fixtures assert the exact serve target instead of baking in one host's path or address semantics. Sharing is unsupported for desktop mode and has no effect for server-only mode.$test-t3-apprecognizes the older-worktree failure where Tailscale proxies127.0.0.1while Vite listens on::1: a healthy loopback origin plus a shared 502 triggers exact-mapping replacement with alocalhosttarget and bare-origin verification, not repeated application restarts or premature token consumption.- Development authentication accepts configured remote origins, scopes browser session cookie names by port, and gives startup pairing credentials a 24-hour lifetime so concurrent or remotely shared worktrees do not overwrite each other's browser sessions.
- The dev runner defaults Node HTTP headers to 64 KiB and preserves an explicitly configured
NODE_OPTIONSheader limit. Port-scoped worktree cookies still share a hostname and accumulate across ports; the default Node 16 KiB limit can reject those requests before Vite routes them. - Browser development leaves Vite HMR origin-derived so tailnet and LAN clients connect back to the page origin. Desktop development remains explicitly pinned to
127.0.0.1; its HMR configuration continues to honor an explicitVITE_DEV_SERVER_URL, including HTTPS and non-default ports. Server-only backend URLs and the default Vite listener also retain IPv4 loopback behavior. - Web servers have no runtime queue. Worktree-local state, path-derived preferred ports, and the actual ports printed by
dev-runnerlet independent worktrees run concurrently. Integrated web UI automation still holds the selected host's desktop lease, regardless of browser driver. - Verification workers record and remove their exact disposable state directories, temporary repositories, source snapshots, and linked worktrees after owned processes stop and leases are released. Worktree-owned Android AVDs and ordinary path-keyed Xcode DerivedData remain reusable.
- The fork extends upstream
$test-t3-appwith collaborative-preview routing for worktree web servers, including direct environment-port navigation when the preview has a verified route and--sharefallback for Windows or other tailnet consumers. - The dev runner loads environment files from its process working directory, so invoking main's current helper in an assigned worktree preserves that worktree's environment overrides.
- Each assigned worktree remains authoritative on its host. Typechecks and automated tests run directly there with the repository's normal host-native commands and need no verification lease or source snapshot. Failures are compared with the cached upstream-control result produced on the same host before they are treated as branch regressions.
Primary files:
packages/shared/src/devHome.tspackages/shared/src/devProxy.tsscripts/dev-runner.tsscripts/lib/dev-share.tsapps/web/vite.config.tsapps/server/src/auth/EnvironmentAuthPolicy.tsapps/server/src/auth/SessionStore.tsapps/server/src/config.ts.agents/skills/test-t3-app/SKILL.md
Focused regression coverage:
vp test run packages/shared/src/devProxy.test.tsWorktree branch: none
Mobile verification uses upstream's Device panel and the exact AgentDevice command and target arguments returned by device_open. Host-local mobile leases cover native helper calls, including cache reuse and --prepare, device interaction, Metro/backend lifetime, and teardown. Preparing before a pooled provider's device reservation still requires the preparation host's lease. Concurrent jobs select distinct devices and test their assigned checkout or exact-source remote snapshot; .agents/skills/worktrees/SKILL.md owns the lease and source-transfer contract.
scripts/mobile-native-client.ts retains upstream's installed-client checks, local/shared artifact caches, session installation, and iOS EAS fallback. The fork adds --worktree selection and Windows dependency normalization before fingerprinting, including cache hits. Session commands locate the target worktree's package-local or root-hoisted agent-device entrypoint after dependency preparation, while keeping the session owner's working directory. Completed artifacts live in the helper-managed ~/.cache/t3code/native-clients/artifacts cache; preserve it during task teardown. Android cache keys separate x86_64 and arm64-v8a. On a cache miss the helper retains upstream's Blacksmith workflow dispatch or join when the selected checkout's HEAD is published and GitHub access permits; the workflow's expected-fingerprint guard rejects native edits absent from that commit. Otherwise a Windows local build invokes scripts/worktree-android-build.ts assemble --abi <abi> without a device, verifies the fingerprint before publication, then installs the APK on the selected ADB serial. Failed assembly never publishes an APK left by an earlier build.
scripts/worktree-android-build.ts keeps its separate build --device <avd-name> path through the target checkout's Expo CLI. Both modes retain the ordered install, no-install clean prebuild, owned short CMake staging, hoisted dependency preparation, and one bounded Ninja retry. Artifact assembly calls Gradle directly with the requested ABI; direct emulator builds use Expo run:android --no-bundler. The clean prebuild retains upstream's Gradle/native cache handling without preserving generated app build output. Worktree-owned API 36 AVDs remain reusable and are opened through the Device panel.
On iOS, the helper reuses a compatible installed client or cached arm64 simulator artifact before a local Xcode or upstream EAS build. Local Xcode builds use scoped temporary DerivedData. The helper forces full capabilities and com.t3tools.t3code.dev through T3CODE_IOS_PERSONAL_TEAM=0; Metro must use that same mode. The separate Personal Team native/Metro workflow remains under Mobile Apple Development Team. The Device panel owns streaming and screenshots. Pair through the mobile skill's deep-link helper; this avoids keyboard-layout changes to a typed host URL. On Windows the returned AgentDevice command is upstream's .cmd shim, which cmd.exe runs by re-parsing the whole command line, so an unquoted & in a deep-link query ends the command early; no batch-file change can prevent that. The pairing helper and any &-bearing argument therefore run the agent-device-launcher.mjs beside the shim with node instead. Close owned AgentDevice sessions and Device panels before releasing leases, and remove temporary source worktrees afterward.
Upstream's native highlighting module replaces Shiki and removes the dependency that required an fbjni override. The Windows wrapper follows the target's native dependency policy without adding an ABI constraint, and native-client compatibility uses the target's Expo fingerprint.
Expo Widgets 58.0.20 supplies Windows drive-letter and UNC resolution through path.win32.isAbsolute and real-path handling for the layout-registry script entrypoint. Keep its upstream iOS widget patch; Windows builds need no fork resolver patch, entrypoint patch, or installed-package rewrite in scripts/worktree-android-build.ts.
Metro excludes the worktree's .t3 directory itself and its descendants with either path separator. The watcher must not enter runtime state, where atomic cache writes can remove a file between discovery and watch registration. Windows native preparation runs before the test backend and Metro start so their watchers cannot hold dependency directories open during installation.
Primary files:
.agents/skills/test-t3-mobile/SKILL.md.agents/skills/test-t3-mobile/scripts/pair-client.sh.agents/skills/worktrees/SKILL.mdscripts/mobile-native-client.tsscripts/worktree-runtime-slot.tsscripts/worktree-android-build.tsscripts/worktree-android-dependencies.tsscripts/worktree-android-avd.ts
Worktree branch: none
The verification suite can combine results from the Windows Desktop and the MacBook Pro. A platform requirement selects the host for that check; it does not by itself make the check unavailable or require a project-specific testing skill change.
Expected behavior:
- Run typechecks and automated tests directly in the assigned worktree on its current host. Obtain that host's native cached upstream-control result before changing code for a failure. Path, permission, process, filesystem, or locale failures that also appear in the same-host control manifest are upstream behavior for that environment, not fork regressions.
- Run iPhone Simulator checks on the MacBook Pro. When the initiating checkout is on another machine, connect through
ssh macbook-proand transfer its code, including uncommitted changes, as a task-owned source snapshot in an isolated Mac worktree. Build that snapshot rather than an unrelated pre-existing Mac checkout; no commit or push is required. When the suite is already running on the Mac against the intended local checkout or worktree, use it directly—do not SSH to the same machine or create a redundant copy. - Run Xcode, CocoaPods, Metro, the backend, and simulator-facing services on the Mac, using Mac paths and an explicit simulator UDID. Keep simulator-facing endpoints on
127.0.0.1; the Mac cannot reach its own Tailscale Serve or MagicDNS endpoint through self-hairpin routing. The selected Mac Device-panel host must be connected and its device tools available. - Run Android Emulator checks on the Windows Desktop. When the initiating checkout is on another machine, connect through
ssh windows-desktopand transfer its code, including uncommitted changes, as a task-owned source snapshot in an isolated Windows worktree. Build that snapshot rather than an unrelated pre-existing Windows checkout; no commit or push is required. When the suite is already running on Windows against the intended local checkout or worktree, use it directly—do not SSH to the same machine or create a redundant copy. - Headless Android verification is available over SSH through Windows Hypervisor Platform and ADB; the Android 16/API 36 x86_64 AVD has completed a full boot through the MacBook Pro-to-Windows SSH path. Run Gradle, Metro, the backend, and emulator-facing services on Windows with Windows paths. Use
10.0.2.2for emulator-to-host backend traffic and ADB port reversal for Metro. Drive the selected emulator through the Device panel's returned AgentDevice command, and capture evidence withdevice_screenshot. - Treat results as host-scoped evidence. A successful macOS permission assertion can complement but does not erase the expected Windows mode-reporting mismatch, and a mobile pass applies only to the exact local checkout or transferred snapshot that was tested.
- Runtime leases are machine-local. Acquire and release them on the host running desktop or mobile interaction, following
$worktrees; commands issued over SSH use the destination host's main and worktree paths. Non-interactive web checks need no lease, while integrated web UI automation uses the browser host's desktop lease.
This section is the shared routing policy for full verification runs. Keep platform availability and expected outcomes here instead of duplicating them in individual test skills.
Worktree branch: fix/ssh-device-host-commands
Main retains upstream's Windows-specific bootstrap failure diagnosis alongside the published stdin transport and compatible npm selection. The device guide keeps upstream's supported-host guidance and also requires a compatible npm and POSIX shell.
PR #18172 is maintainer-owned / pull-only, taken over by juliusmarminge. Do not edit, rebase, squash, commit, push or change the original PR title/description; only fetch/pull published maintainer changes. Keep it linked to this T3 thread. Published head: c616c4f0454375f8ab9a67049b6263db2157ec8c, based on upstream c6ef3b46ebed34fbefa1706674fe5748254cee18. This includes the maintainer merge and subsequent corrections; the separately supplied acceptance patch is not a substitute for this published input. Native acceptance requirements.
SSH bootstrap and device commands send their shell scripts through stdin to sh -s, so a Windows PowerShell login shell cannot reinterpret POSIX quoting, variables, or the Node version check. Command input is supplied inside that script with printf %b, escaping backslashes, carriage returns, and NUL bytes so quotes, line breaks, CRLF, NUL, and the absence of a trailing newline arrive byte-for-byte. Bootstrap opts into the Node version check with the named nodeBootstrap option. The stdin transport wraps the generated Node script, and the same SSH request retains keepErrorOutputTail. Optional command input is encoded inside the script instead of replacing that request stdin. Local SSH fixtures use the upstream HostProcess.Platform service; generated scripts still use the remote Node process. Main preserves upstream hub operations and host-local command cwd while transporting the command over stdin. Its fixture runs the transport with inherited PATH and its own HOME and keeps failure-tail assertions separate from native shell dispatch.
SSH device hosts use one npm invocation path for connection probes and pinned tool installation. Windows skips candidate-local package read, parse, engine-metadata, and bundled-semver failures and continues discovery. It checks adjacent npm before PATH candidates, validates npm's declared Node requirement with its bundled semver, preserves compatible sibling runtimes, and runs npm-cli.js through the selected executable. An unsupported sibling does not exclude a compatible bootstrap Node. The normalized PATH begins with the selected Node directory so npm's lifecycle children use the same runtime. Paths and arguments containing spaces remain intact. Non-interactive POSIX setup appends fallback tool directories so an existing Node/npm pair keeps priority. Bootstrap restores fallback-directory precedence if the selected Node is missing or older than 22, then applies the configured SDK and JVM paths so their tools keep priority. Ordinary device commands do not repeat that version probe. Unsupported-runtime errors include the detected version and executable path. Missing npm, launch failures, signals, and nonzero process exits retain distinct diagnostics, with a bounded stdout fallback when stderr is empty.
Primary files:
apps/server/src/device/sshDeviceScript.tsapps/server/src/device/SshDeviceHost.tsdocs/user/devices.md
Focused regression coverage:
vp test run apps/server/src/device/sshDeviceScript.test.ts apps/server/src/device/SshDeviceHost.test.tsRegression coverage executes bootstrap and device-command payloads through the native login shell, including PowerShell on Windows, and checks quoted arguments, exact stdin, and failure propagation. Native Windows subprocess coverage checks paths containing spaces, npm PATH fallback, probe and install dispatch, missing npm, and actual npm failure diagnostics. POSIX-only shell and lifecycle fixtures require a POSIX host; their bodies do not execute on Windows. The supported/old-Node PATH case is one it.effect.each table, as upstream's t3code/no-test-in-loop lint rule forbids declaring tests inside loops. The supported/old-Node PATH-selection and SDK/JVM precedence cases were additionally verified with isolated Git Bash shell fixtures on Windows. Over a real Mac-to-Windows SSH connection, the previous payloads failed the probe with a false missing-npm error and silently dropped a quoted device command; the current payloads return the probe result and deliver the argument and stdin byte-for-byte.
Worktree branch: takeover/17239-ssh-android-sdk
PR #24566 is open, authored by upstream maintainer juliusmarminge, and maintainer-owned / pull-only. Its published source is pingdotgg/t3code:takeover/17239-ssh-android-sdk, fetched through upstream, at 59fdabb266c155c56c63866c8fb0cc52f710f9b9. Windows and Mac retain a protected local branch tracking upstream/takeover/17239-ssh-android-sdk, without a checkout. Ownership records are in each clone's <git-common-dir>/maintainer-protection/branches/takeover--17239-ssh-android-sdk/BRANCH_DETAILS.md and pr.md. Keep the PR linked to T3 thread 28d8a68d-75d3-41e4-aee0-5bcf762324f8. Only inspect and fetch/pull published source; do not edit, squash, rebase, commit, push, run automated fixes or alter its PR metadata or comments. Explicit force-fetch and local reset to the published head are permitted after maintainer rebases. Integration and verification belong to a separately authorized main task.
The current diff against 909e4c948278afc2b3e1e665075fb1755a13f894 changes apps/server/src/device/sshDeviceScript.ts, its test file, and docs/user/devices.md, with 125 additions and five deletions. The shared remoteDeviceEnvironment selects ANDROID_HOME, then ANDROID_SDK_ROOT, then existing macOS/Linux default locations, and adds that SDK's tools to PATH for probing and later SSH commands. The probe consumes that selection rather than resolving another SDK. It requires executable regular Platform-Tools adb, Android Emulator, and latest Command-line Tools avdmanager, then successfully runs the selected SDK's absolute adb version. Missing or unusable tools report a component-specific Android reason while independent iOS detection remains intact. Platform-Tools alone, including physical-device-only setups, no longer enables Android capability; custom SDK installs must set one of the documented root variables.
Main combines the SDK selection with the SSH stdin transport and Node bootstrap option. SDK tests invoke the shared environment function, so they exercise the same setup used by ordinary device commands. The npm fixture supplies its own complete SDK, keeping npm failure assertions independent of SDK availability.
The maintainer rebuilt the change against current upstream, retained runnable-adb validation and replaced mocked table coverage with focused real-shell/filesystem regressions. Four added cases cover an adb-only SDK, a selected adb that exits nonzero, directories in place of tools, and SDK consistency with later SSH commands. Existing Java discovery and argument handling remain; lifecycle tests removed upstream are not restored. The published description reports ten focused tests and changed-files checks passing, plus 34 isolated Linux localhost SSH probes and both independent SDK-selection follow-ups using marker tool stubs. These are maintainer-published results, not fresh local verification. Real SDK/emulator boot, macOS/iOS execution and remote/relay/tunnel connections remain outside that evidence.
Worktree branch: base/fork
An open pairing page accepts each new pairing link delivered to it, so a desktop or preview host can pair again without a reload or a second window.
Expected behavior:
- The primary pairing route reads its initial credential lazily and watches for later URL-fragment changes while it remains mounted. Its effect uses React's
useEffectEventto call the current submission handler without reinstalling the listener on renders. Navigating an already-loaded/pairdocument to/pair#token=...claims each new token once, removes the secret fragment, and runs the normal pairing exchange without requiring a reload or a second desktop window. Multiple tokens received while an exchange is pending are serialized, and the submitting state remains active until every queued exchange settles.
Primary files:
apps/web/src/components/auth/PairingRouteSurface.logic.tsapps/web/src/components/auth/PairingRouteSurface.tsx
Focused regression coverage:
vp test run apps/web/src/components/auth/PairingRouteSurface.logic.test.tsWorktree branch: base/fork
Worktree development desktops start from their own Electron profile.
Upstream commit 3b5d476eb (fix(desktop): stop overwriting a custom dock icon on launch) owns preserving packaged macOS dock icons in apps/desktop/src/app/DesktopAppIdentity.ts by assigning the runtime PNG icon only to unpackaged apps. The branch composes its isolated desktop user-data path with that behavior; it does not override the upstream icon policy.
Upstream commit de34391427 (feat(orchestrator): introduce new orchestrator) owns Electron profile selection in apps/desktop/src/app/DesktopUserData.ts: packaged builds use the separate t3code-v2 profile, seeding Windows Local State from the earlier profile, while development keeps an existing T3 Code (Dev) profile. Both the Clerk bridge and app identity resolve the profile through that helper before Electron is ready. The branch adds only an explicit override ahead of that selection.
Expected behavior:
dev:desktopderivesT3CODE_DESKTOP_USER_DATA_DIR=<resolved base dir>/userdata/electronwhenever the runner has an explicit base directory. Desktop configuration resolves that override to an absolute path, andDesktopUserData.resolveUserDataPathreturns it before any legacy-profile probe or upstream profile selection, so the Clerk bridge's profile lock and app identity use the same isolated directory. This keeps an isolated worktree dev desktop from reusing an installed or earlier development profile whose incompatible IndexedDB schema can prevent the renderer from starting. Packaged/default startup remains unchanged when no override is supplied, and packaged macOS startup preserves the bundle or user-customized dock icon while unpackaged development still assigns the runtime PNG icon.
Primary files:
apps/desktop/src/app/DesktopUserData.tsapps/desktop/src/app/DesktopConfig.tsapps/desktop/src/app/DesktopEnvironment.tsscripts/dev-runner.ts
Focused regression coverage:
vp test run scripts/dev-runner.test.ts apps/desktop/src/app/DesktopAppIdentity.test.ts apps/desktop/src/app/DesktopEnvironment.test.ts apps/desktop/src/app/DesktopClerk.test.tsWorktree branch: none
The fork keeps host-local coordination for interactive desktop and mobile verification. Source checks run directly in each assigned worktree without a lease, while one deterministic script per host owns native verification of that host's upstream control branch. Shared orchestration changes stay on main and base/fork; standalone feature branches use the current main helpers against their own source. The native-client helper accepts --worktree, keeps fingerprinting and builds in that target, and resolves its Android preparation helpers beside itself.
Expected behavior:
scripts/worktree-runtime-slot.tsexposesmobilecapacity two anddesktopcapacity one on each host. Version 1-3 state migrates to the version 4 shape and obsolete web holders or requests are discarded. Command-line acquisition and release require a request id; interrupted acquisition cancels that request and releases a matching near-simultaneous holder.scripts/worktree-baseline.tscomputes host-native upstream-control install, typecheck, and bounded-test results once for an exact commit and host/toolchain fingerprint. It invokes pnpm through the Windows command processor on Windows and directly on macOS or Linux. Concurrent cache misses on one host coalesce behind one producer. The script alone operates that host's control worktree and writes logs and the manifest atomically under ignored.t3/research/worktree-baselines; interrupted and out-of-memory runs are not cached.- Baseline waiters leave a live producer's lock and reclamation directory untouched. Startup and failed-task fixtures drain their owned asynchronous work before removing temporary files.
- Android preparation resolves Expo and autolinking in a fresh Node process after dependency installation, so changing to a hoisted layout cannot retain the previous process's module-resolution cache.
- Staged macOS helper checkouts link their entire
.t3directory to the persistent main coordinator directory. Linking only the ledger file fails when the coordinator atomically replaces it. Teardown unlinks the helper's directory link and preserves the canonical coordinator. - Candidate workers run source checks normally in their assigned worktrees without a lease. Before changing code or tests for a failure, they compare its exact output with the same-host control manifest and logs. Shared host-specific and locale failures remain unchanged.
- Web servers have no runtime queue. Each worktree uses its own ignored
.t3state and reads the actual server and web ports from[dev-runner]. Integrated UI automation, including standalone browser fallbacks, uses the selected host's desktop lease. - Workers enter every ready desktop and mobile queue concurrently, run the first eligible acquisition, cancel their other requests, and re-enter the remaining queues after releasing the winner. Each worker holds one runtime lease at a time except while a mobile stream briefly uses a desktop renderer.
- Representative mobile verification races the Windows Android and macOS iOS hosts. Integrated web UI verification uses an available Browser renderer, explicitly pinned to its actual host and protected by that host's desktop lease. Record the renderer separately from the environment serving the application. Request-scoped cancellation safely releases a losing near-simultaneous acquisition. Queue age never triggers cancellation, replacement, reprioritization, or coordinated handoff.
- The host-local capacities permit two Android and two iOS verifications simultaneously across the fleet. Desktop permits one interaction block per renderer host at a time.
node "<main-worktree>/scripts/mobile-native-client.ts" ensure android <adb-serial> --worktree "<assigned-worktree>"is the Windows operator entrypoint. It prepares hoisted dependencies before fingerprinting, reuses an already prepared layout, and delegates cache-miss APK assembly toscripts/worktree-android-build.ts assemble --abi <abi>. The wrapper retains its separatebuild --device <avd-name>mode for direct emulator builds. The wrapper orders the normal install and no-install Expo clean prebuild beforescripts/worktree-android-dependencies.ts, then patches the generated app Gradle file to use Android Gradle'sbuildStagingDirectoryat<worktree-drive>:\.t3code-android-cxx\<worktree-hash>. That short path prevents app-level CMake object paths from crossing Windows' 260-character boundary; a canonical-worktree ownership marker keeps every staging cache isolated. The same generated block passes-DCMAKE_OBJECT_PATH_MAX=250to the app's CMake, and a markedsubprojectsblock the wrapper appends to the generated rootbuild.gradlegives everycom.android.librarymodule its own staging subdirectory and the same argument, because libraries such as React Native Reanimated configure separate CMake projects beneath the worktree'snode_modules. Both blocks are rewritten after every clean prebuild, so no generated file needs hand editing. AGP passes 1024, so CMake otherwise names objects for sources outside a module's CMake directory, such as React Native Gesture Handler 3's../shared/shadowNodes, after their mangled absolute path, and Ninja rejects the resulting build-relative path as longer than 260 characters regardless of the staging root. At CMake's Windows default of 250, CMake hashes those names to fit beneath the short staging root. Retire the app and library arguments when AGP stops overriding the limit or the long object paths leave the app's autolinked and the libraries' own CMake projects. The wrapper uses the target checkout's Expo and React Native autolinking results to discover native source directories and validates their paths. Artifact assembly invokes Gradle directly with the requested ABI; direct emulator builds resolve Expo from the target'sapps/mobileand invoke it without the bundler. Neither path re-enters Vite+ between dependency preparation and the native build. On the exact Ninja dirty-manifest failure it waits for the failed build to exit, removes only generated.cxx, CMake intermediates, and the contents of that worktree's marked staging directory, revalidates, and retries once. The retry keeps the canonical worktree path rather than substituting a drive whose removal can corrupt an active Gradle build.scripts/worktree-android-dependencies.tsremoves stale package-level dependency links only inside the selected Windows worktree and performs the frozen worktree-local hoisted install. The ordinary pnpm content store remains tool-managed and no virtual store or native staging directory is shared between worktrees. Windows Android verification keeps its disposable backend state and seeded project outside Metro's watched worktree because atomic cache replacement can otherwise terminate Metro's fallback file watcher on a transient path.scripts/worktree-android-avd.tslazily provisions one persistent API 36 AVD per canonical Windows worktree. It invokes Windows batch SDK tools through an explicit command shell, refuses to install a missing system image without authorization, records ownership, disables Quick Boot snapshot load/save, and supports verified removal before the owning Git worktree is deleted.- macOS creates a deterministic temporary linked worktree for Windows-originated source, including uncommitted changes. Concurrent transfers use request-specific refs and bundle files, fetch with
--no-write-fetch-head, and verify the exact expectedHEADand dirty-state manifest before requesting runtime capacity. A local Xcode build uses scoped temporary DerivedData; completed simulator artifacts remain in the helper-managed cache. - Each iOS verification selects a distinct simulator through the Device panel and retains its exact AgentDevice target arguments. The native-client helper owns native compatibility and build/install.
- Mobile host races materialize exact source state on both candidates before acquisition. The first eligible host to acquire wins; the losing request exits before runtime work starts.
Primary files:
.agents/skills/spawn-worktree/SKILL.md.agents/skills/worktrees/SKILL.md.agents/skills/test-t3-app/SKILL.md.agents/skills/test-t3-mobile/SKILL.mdscripts/worktree-runtime-slot.tsscripts/worktree-runtime-slot.test.tsscripts/worktree-baseline.tsscripts/worktree-baseline.test.tsscripts/worktree-android-build.tsscripts/worktree-android-build.test.tsscripts/worktree-android-dependencies.tsscripts/worktree-android-dependencies.test.tsscripts/worktree-android-avd.tsscripts/worktree-android-avd.test.ts
Focused regression coverage:
vp test run scripts/worktree-runtime-slot.test.ts scripts/worktree-baseline.test.ts scripts/worktree-android-build.test.ts scripts/worktree-android-dependencies.test.ts scripts/worktree-android-avd.test.ts scripts/mobile-native-client.test.tsUse these commands from the repository root when producing local installable artifacts for this customized branch.
Upstream's scripts/lib/cli-external-packages.ts owns detection of plain package-path headers and older #region markers. The fork needs no separate detector or detector regression; it retains the local artifact commands and certificate-signing behavior in scripts/build-desktop-artifact.ts and scripts/sign-local-macos.ts.
Build a macOS arm64 DMG using the same desktop artifact path used for this branch:
pnpm run dist:desktop:dmg:arm64Build a local macOS arm64 DMG, then hand the install step to Terminal.app so it can finish after the running T3 Code app quits:
scripts/install-desktop-dmg-from-t3.zshThe macOS dist:desktop:dmg commands use --local-signing to sign the application and its helpers with an Apple Development certificate during packaging, before creating the DMG and updater ZIP. The build selects the sole valid Apple Development identity, or the exact certificate SHA-1 specified by T3CODE_MACOS_SIGNING_IDENTITY in the environment or root .env. Missing or ambiguous identities fail the build. Keep that identity consistent across builds so macOS retains Keychain and protected-folder permissions. These local builds do not require notarization or the release passkey provisioning profile; the separate --signed release pipeline and Windows builds retain their existing behavior. An SSH session may be unable to access the login keychain even when the same signing operation works in the logged-in macOS session.
The macOS handoff selects the newest arm64 DMG under release, launches a separately titled Terminal tab, and mounts the image read-only in a task-owned temporary directory. Before quitting the installed app, it requires a valid certificate signature and checks compatibility with an existing certificate-signed installation. It replaces only /Applications/T3 Code (Alpha).app, preserves and verifies the build's signature, detaches the image, relaunches the app, and closes the owned Terminal window. It never repairs or ad-hoc signs an application. Moving from the previous ad-hoc builds may require one final authorization round. Stale mount cleanup is limited to the script's t3-code-dmg.* temporary directories.
Build a local Windows x64 installer, then hand the install step to a temporary per-user scheduled task so it can finish after the running T3 Code app and its terminal process tree quit:
pnpm run dist:desktop:win:x64
scripts/install-desktop-exe-from-t3.ps1The Windows handoff selects the newest x64 installer under release and starts one interactive-user scheduled task at the caller's current elevation level so installation survives shutdown of the originating T3 Code terminal and can close an elevated desktop process. Before closing the app it verifies the desktop process by PID, start time, and executable path to prevent PID-reuse mistakes. It waits for every process using that exact executable path, force-stops only those matching processes after the graceful deadline, runs the selected installer silently, and unregisters the one-time task. The task writes a temporary transcript, and a failed update attempts to restart the exact previous executable.
Build the installable Android preview APK locally, avoiding the EAS cloud worker queue, then install it directly over USB:
cd apps/mobile
JAVA_HOME=/opt/homebrew/opt/openjdk@17/libexec/openjdk.jdk/Contents/Home ANDROID_HOME=/opt/homebrew/share/android-commandlinetools ANDROID_SDK_ROOT=/opt/homebrew/share/android-commandlinetools PATH="/opt/homebrew/opt/openjdk@17/bin:/opt/homebrew/share/android-commandlinetools/platform-tools:$PATH" EAS_SKIP_AUTO_FINGERPRINT=1 EAS_BUILD_DISABLE_EXPO_DOCTOR_STEP=1 pnpm dlx eas-cli@latest build --profile preview -p android --local --output ./build/android/t3-code-preview.apk
adb install -r ./build/android/t3-code-preview.apkUpload the local APK to EAS when a shareable install link is needed:
cd apps/mobile
pnpm dlx eas-cli@latest upload -p android --build-path ./build/android/t3-code-preview.apk --non-interactiveThis branch carries local conversation-rendering and orchestration changes that are not assumed to exist upstream. Keep this file current when changing local behavior so future upstream updates can preserve the intended UX, and so these patches can be removed when upstream covers the same behavior.
Worktree branch: fix/subagent-parent-lifecycle
PR #20072 is maintainer-owned / pull-only. The local branch is synchronized to the complete published head deafe758466ed888b0eb89f8a514c5a088008a9e, based on upstream c4d59502684f8b712617ecf4f2a4e31fa104dd4b. Do not edit, commit, push, run review fixes or change the original PR. Its link and tracking remain in place.
The published implementation includes the maintainer's corrections for independent descendant choices, delayed reverse operations and cascade ownership after a parent action is superseded. Source sequence and ancestor identity travel through durable effects, and accepted receipts prevent replays from redispatching completed child commands. Main preserves the newer SQLite reader path, named projection helpers and cold-archive/purge effects beside the cascade. Upstream app-owned child-ID lookup remains available beside the cascade query, and side-chat deletion plans each carry their own cascade eligibility. The published supersession checks retain source-sequence and ancestor ownership before executing delayed effects. Side-chat deletion keeps upstream's per-thread plan and unused-thread guard; each plan carries its own descendant-cascade eligibility. Managed runtime ceilings still inspect the combined plan before commit.
Archive, restore and delete cascade through existing native and app-owned subagent lineage, stopping at independent forks. Restore reverses archives owned by that parent cascade; independent archives and newer descendant choices remain intact, including across superseded Undo and delayed replay. A durable after-commit outbox executes ordinary child commands through the existing ThreadManagementService, preserving race, replay and cleanup semantics. Production corrections add durable replay acknowledgments and enforce the initiating caller's ceilings. Child lookup is project-scoped. Creation-time inheritance, UI changes and Codex approval ordering are outside this fix.
Worktree branch: fix/subagent-creation-lifecycle
PR #20074 is maintainer-owned / pull-only. The local branch is synchronized to the complete published head c5e91fb122924fd9b2b29cc5d53a02be003435d6, based on upstream 071239804e375078dd3fae3b14704b4def57e048. Do not edit, commit, push, run review fixes or change the original PR. Its link and tracking remain in place.
The published implementation includes the maintainer's self-parent deadlock correction and current native branch-correction coverage. Main preserves the parent-before-child locking and lifecycle inheritance around newer provider normalization.
Native child creation inherits the parent's persisted archive/delete state under parent-before-child shared locking. Archive inheritance also clears a stale child archive time when the persisted parent has been restored. Fork lineage stays independent, and fresh delegation under an archived or deleted parent is rejected. These creation-time guarantees are independent of the descendant cascade implementation.
Worktree branch: feat/archive-settings-ux
The native grouped Archive list uses upstream's cross-platform scroll wrapper and shared AppBar refresh menu. Successful permanent parent deletion also discards its side-chat drafts. Archive resolves family support when the confirmed operation executes, so capabilities arriving while confirmation is open are respected. Archive retains its Settings layout, standalone search, tag-filtered snapshots and scoped action reservations.
The standalone source is 11a74ebf0b080bcbd1bbbf4ccf25b275ab731c42, based on pinned upstream ae142932d07c0596cf0497dd28cfee07ae7c0722. PR #12708 remains closed and unmerged. The web panel retains upstream Settings scopes and native rows share the minute clock. Permanent deletion clears retained panel/preview state, and unarchive invalidates stale Archive Undo. Native Archive itself has no Undo; Settle retains its upstream Undo. Archive reservations use collision-safe keys and ordinary scoped identities for dismissal.
The Archive panel follows the shared Settings sections, cards and rows. Native Archive uses the shared Settings row layout in virtualized project sections, retaining swipe actions, long-press menus and header controls.
Expected behavior:
- Archived conversations are grouped by project, and each project group is collapsed by default.
- In global Settings scope, the Archive panel fetches archived thread snapshots from all configured environments, not only environments that currently have active projects, so archived-only workspaces remain visible, while active rows returned in those snapshots remain excluded from archive content and empty-state counts. Partial environment failures remain visible above successfully loaded project groups with their error and a keyboard- and screen-reader-accessible retry action. A total failure does not also render a misleading empty archive state, while a search miss over successfully loaded archives still shows the no-match state beside a partial-failure alert, and retry reloads every configured environment. Web environment, project, and checkout Settings scopes narrow fetched environments and filter both archived project shells and threads before grouping, empty-state counts, search, and bulk actions.
- Web project headers show environment labels whenever multiple environments are configured and keep a sole remote environment labeled, while a sole primary environment remains implicit. Their
ProjectFaviconreceives the complete archived snapshot project record with environment identity through itsprojectprop, so the saved title, icon override, favicon path, and workspace root stay together and configured emoji or Lucide icons and automatic fallback icons match current upstream project lists. Native project sections render the snapshot favicon or configured project icon and pair visible environment labels with the environment's machine-kind glyph. Native header controls can filter the archive to all environments or one configured environment, and each environment option carries the same machine-kind symbol as upstream's shared settings environment filter. Upstream's compact iOS mail-search toolbar (used at iPhone widths) drops menu item icons, so the icons show only where the regular header menu renders; the branch deliberately leaves upstreamAppBarunchanged. The Archive screen keeps its ownAppBarmenu combining archive search, environment, sort, and refresh controls. Upstream'suseSettingsEnvironmentMenusupplies the broader Settings filter, so Archive retains its archive-specific environment selection. - Native Settings retains upstream's concern-based section order and workspace/split navigation.
SettingsRouteScreen.tsxexposesArchivein the final unheaded section after Maintenance, Labs and About, and Settings search indexes its archive/restore terms.ConnectionsshowsEnvironmentsand adds account controls when T3 Connect is configured.apps/mobile/src/Stack.tsxregisters the customized Archive through the v5 native-navigation screen builder in the header-owning settings content stack and retains the legacySettingsWaitlistalias toSettingsAuthin the outer auth stack. - Web Settings search includes
Archived threads; selecting that result opens the customized Archive panel and focuses its persistent archive search field, regardless of whether the archive is loading, empty, filtered, or populated. - Native
apps/mobile/src/features/archive/ArchivedThreadsScreen.tsxuses upstreamAppBar,SettingsScreenContent, a non-collapsable root, andLegendList.AppBarandSettingsScreenContentsupply the settings frame. The Archive screen mounts the non-collapsable root, list, pull-to-refresh control, and Android header refresh action. The customization retains inline archive search, environment filtering, all four Archived/Created sort choices in the shared header menu, and inline project sort controls without remounting the list during search. - Native initial loading and action busy states use upstream
AppActivityIndicator; Android uses its MD3 implementation and iOS keeps the native indicator. - The page includes a search box that filters archived thread titles across all projects case-insensitively. Each term must appear in the title as written (no fuzzy subsequence matching), because matching-scoped bulk actions act on exactly those results. Multi-word searches match any term, rank exact phrase matches first, rank titles matching every term ahead of partial term matches, and rank partial matches by distinct matched-token count before the earliest token position. Phrase and all-token scores remain bounded within their relevance tiers so a long-title position penalty cannot demote a stronger match below a weaker tier. Search auto-opens matching project groups while active. Native incremental search updates the existing list without remounting it for every keystroke, preserving scroll position and transient row state.
- Expanded project groups expose
ArchivedandCreatedsort controls; selecting either toggles ascending/descending order for the conversations inside each group, withArchiveddescending as the default. - Native project-section ordering follows the selected archive sort field and direction. Archived snapshots carry V2 thread shells whose timestamps are decoded
DateTimevalues, so both surfaces present them through upstream'spresentThreadShell(directly on web, throughscopeThreadShellon native) and sort and display the resulting ISO strings without an invalid-timestamp fallback. - Native read-only rows retain their rounded layout without swipe or long-press actions, and project action menus are disabled. The Archive route checks each target environment's task permission before row or project confirmation. Its action executor checks that permission again before dispatch and preserves the failed result and suppressed-alert failure message for bulk summaries. Native row and bulk actions share collision-safe per-thread reservations and action-executor identity keys, reserve bulk targets before confirmation, expose busy state only after confirmation, disable overlapping swipe/menu controls while reserved, and distinguish rows skipped because the same thread action is already in progress from commands that actually fail. In
apps/mobile/src/features/home/useThreadListActions.ts, reservation keys remain JSON tuples while lifecycle dismissal usesscopedThreadKeyto match the identity registered by both live rows andArchivedThreadsScreen.tsx. This keeps exit animations ahead of mutations without weakening action deduplication. - Web row and project actions in
apps/web/src/components/settings/ArchivedThreadsPanel.tsxdispatch thethreadEnvironment.unarchiveandthreadEnvironment.deletecommands through upstreamuseOrchestrationCommand, so the Archive panel owns archived-snapshot refresh timing without extending the shareduseThreadActionsinterface. Row and project restores recheck task permission before invalidating the affected thread's Archive Undo and dispatch using its scoped thread identity, leaving other action kinds and environments untouched. Cancelled project restores and restores denied after permission is revoked retain Undo. Archive row and project controls subscribe to each target environment's task permission; retained callbacks and project actions recheck that grant before opening confirmations. Read-only connections can still browse, search, and sort. Successful row actions refresh the affected environment immediately. Project actions reserve collision-safe per-thread locks before confirmation, expose busy state only for threads owned by actions that have started after confirmation, disable overlapping controls while mutations run, give explicit feedback for rejected duplicates, and refresh the affected environment once after the bulk attempt instead of between concurrent mutations. Successful raw deletes use the samepermanentlyDiscardComposerDraftoperation as live-thread deletion, releasing image and file/video uploads before clearing every draft reference, and clear the thread's persisted terminal UI state and saved preview sessions, without coupling Archive actions to the rest of the live-thread delete lifecycle. - Web and native rows use their shared Settings typography and spacing. Titles wrap, and labeled relative Archived/Created ages remain visible below them. Web row actions stay visible beside the content and stack at narrow widths; native retains swipe actions and the standard long-press context menu. The web panel refreshes ages once a minute because they never show seconds. Native rows retain the upstream grouped-card surface and pass it explicitly to the shared
ThreadSwipeable. - Archived conversations can be deleted directly from the Archive panel without unarchiving first. Web delete actions respect the shared
confirmThreadDeleteclient setting, while native keeps its standard guarded delete flow through the shared themedDialog. - Restoring an archived thread that has since been deleted fails instead of succeeding. This is upstream V2 behavior:
dispatchThreadMutationinapps/server/src/orchestration-v2/Orchestrator.tsrejects every thread mutation on a deleted thread after command receipts are checked, so the branch carries no server changes. - Archived-row context-menu action IDs and presentation metadata come from shared Archive settings logic. Unarchive uses the archive-restore icon, Delete uses the trash icon and destructive styling, and
separatorBeforedistinguishes permanent deletion from restoration in both the web fallback and Electron native menu. - Project group context menus expose
unarchive allanddelete allactions. While search is active, those bulk actions apply to the visible matching archived conversations and use matching-specific menu labels; otherwise they apply to all archived conversations in the project. Delete confirmations respectconfirmThreadDeleteon web and remain explicitly guarded on native; unarchive bulk actions remain guarded on both surfaces, and partial failures surface as not-fully-completed feedback instead of implying every archived thread failed. - Web single and project delete confirmations use the shared themed dialog's destructive variant, while project unarchive confirmations keep the default variant.
- Shared archive search ranking, timestamps, sort state, and action locks live in
packages/client-runtime/src/state/archivedThreadList.ts. Platform-specific grouping and project bulk-action concurrency remain inapps/web/src/components/settings/ArchiveSettings.logic.tson web andapps/mobile/src/features/archive/archivedThreadList.tson native so Archive behavior stays covered without growing the React components. Web project groups retain the complete archived-snapshot project shell plus environment identity so the customized header can read shell metadata directly without a reduced field projection. Project groups expose and reuse collision-safe keys so project ids containing separator characters do not collapse expansion state or React row identity. Bulk actions stop scheduling new work after thrown failures, wait for active workers to settle, preserve the completed success/failure/skipped outcome counts, show incomplete-operation feedback, and surface the underlying exception messages instead of only a generic aggregate error. The Archive surfaces refresh only the affected environment after bulk unarchive/delete attempts, even when the action runner throws. Native bulk actions suppress per-row alerts but collect each command failure message through the action executor'sonFailureoption and append the distinct messages to the aggregate summary. - The user guide documents Archive as a reversible thread-lifecycle action, covers the web, desktop, and mobile controls and safeguards, and explains that search scopes project bulk actions to visible matches. The guide states that Unarchive returns a thread to the live list with its pre-archive settled, snoozed, or pinned state; because snooze placement is derived from the current time, a snooze that ended while archived does not resume. The guide and internal glossary distinguish settled threads, which remain live in the thread list's
Settledsection and return to the active list when un-settled or new work begins, from archived threads, which leave the thread list for Archive until restored or deleted. The glossary also distinguishes Archive from permanent deletion, and the documentation index links the guide.
Primary files:
apps/web/src/components/settings/ArchivedThreadsPanel.tsxapps/web/src/components/settings/ArchiveSettings.browser.test.tsxapps/web/src/components/settings/ArchiveSettings.logic.tsapps/web/src/components/settings/ArchiveSettings.logic.test.tsapps/web/src/components/settings/SettingsPanels.tsxapps/web/src/lib/composerDraftUploads.tsapps/web/src/lib/composerDraftUploads.test.tsapps/web/src/lib/composerDraftUploads.store.test.tsapps/mobile/src/features/archive/ArchivedThreadsRouteScreen.tsxapps/mobile/src/features/archive/ArchivedThreadsScreen.tsxapps/mobile/src/features/archive/archivedThreadList.tsapps/mobile/src/features/home/useThreadListActions.tsdocs/user/archive.mddocs/internals/glossary.mddocs/README.md
Archive lists retain their current standalone presentation. Descendant lifecycle propagation is a backend concern; no obsolete parent-child list filtering is restored.
Settings search and all Settings scopes reach the dedicated Archive component. Keep that extracted component as the sole web Archive list when cold-storage integration changes upstream SettingsPanels; its reservations own refresh and Undo timing through the complete server command.
Shared lifecycle fixtures expose permanent draft discard and archive-image cleanup through one mock, so one setup cannot replace the other.
Worktree branch: feat/conversation-data-savings
Cold storage retains upstream pending-question cache safeguards and query persistence. Mobile cache writes retain upstream removal ordering and invalidation. Snapshot fixtures share the runtime-driven availability predicate while preserving socket-first delivery. The main connection cache remains at IndexedDB version 8, including the installed version-7 upgrade. The standalone migration 81 is already represented by published main migration 71 and is not registered twice.
The standalone source is 5f6c22b84562470da58155fac1a12edb0c905eb0, based on pinned upstream ae142932d07c0596cf0497dd28cfee07ae7c0722. Cold-read leases, deferred paging and command-output reads retain upstream behavior. Frozen historical link prompts refresh after a later authored reply while current prompts stay cold. PR #12702 remains closed and unmerged.
Archived conversations move out of the active database (userdata/statev2.sqlite) into gzip chunks in userdata/archivev2.sqlite, and come back whenever something reads or changes them. apps/server/src/orchestration-v2/ThreadColdStorage.ts owns every transition and runs each one under the thread's ThreadCommandExecutor lock, so it is serialized with command dispatch for that thread.
Pre-cold-storage binaries cannot read histories already moved into archivev2.sqlite. Unarchive needed conversations with a cold-capable version before downgrading; docs/user/background-service.md says so. The pre-V2 branch stored V1 bundles in archive.sqlite; this code never opens that file, and no recovery path exists for those bundles.
- What moves: the thread's run, attempt, node, provider-turn, runtime-request, message, plan, turn-item, turn-item-position, checkpoint and context-handoff projection rows; its event stream (including copied V1 events) except the
thread.*lifecycle events and the newest event; the V1 rows of a migrated conversation that V2 no longer reads once its transcript is imported (messages and legacy command receipts); and its attachment files, including pages published by thehtml_rendertool and captured MCP App documents. Provider logs and their numeric rotations are deleted, never stored; upstream'sterminal.cleanupalready deletes terminal history on archive. - What stays hot: the thread shell row, its retained
thread.*events. Upstream pruning may remove old lifecycle events and receipts, and replay boundaries then require authoritative snapshots, the newest event of the stream (so later events keep a uniquestream_version), V2 command receipts, the V1 thread, pull-request and session rows (shell import and repair, and the settings migration, read them), provider sessions and bindings, subagent links and context transfers. - V1 rows: upstream
063_DropUnusedV1Projectionsremoves activities, plans, turns and approvals before runtime. Old bundle chunks for exactly those four tables are decoded for integrity before obsolete rows are discarded; other unknown tables and corrupt obsolete rows still fail restoration and preserve the bundle.apps/server/src/orchestration-v2/legacy/LegacyV1ThreadTables.tslists the moved V1 tables beside the importer, and the cold move runs the importer's transcript hydration first, so an archived pre-V2 conversation leavesstatev2.sqlitelike any other. Upstream removedV1ImportBoundary.test.ts; main preserves that deletion. - Archived shells derive activity fields (latest run, latest user message, latest user-authored message, item counts, status) from child rows. Frozen summaries also retain
latestRunBackgroundWake, so cold conversations preserve the upstream background-wake status without restoring history. Before moving rows, the service freezes the computed shell intothread_archive_manifests.shell_json, andProjectionStore.readShellSnapshot/getThreadShelloverlay those derived fields for cold threads.storageCleanup.tstherefore keeps the same inactivity clock for archived worktrees, and the Archive list keeps its rows unchanged. - Eligibility: a thread stays hot (
kept-hot) while it has a non-terminal run, while a fork reads from it (an undeleted fork, or a deleted fork not yet purged, through any chain of forks), or while it is a subagent child of a live parent (result recovery reads its runs). The finalmovingtransaction rechecks runs and references, so a run or fork that appeared while the bundle was written keeps the thread hot. - Leases:
withHot(threadId, use)runs a read or command against hot rows and holds an in-memory lease while it runs. A hot thread takes its lease without the thread lock; a cold thread, a move in progress, or an unarchive goes through the lock, so it waits for any move and restores first. A move or purge starts only when no lease is held (otherwise its effect requeues itself afterRECOLD_DELAY_MS) and reserves the thread against new leases while it runs; both checks are synchronous steps, so a lease and a move or purge never overlap. Nested leases on a hot thread never take the lock. Force stop restores cold histories before acquiring the thread lock and retains its lease through settlement and bounded provider release, so moved run rows cannot falsely report that the target was superseded. - Archive: the
thread.archivehandler inOrchestrator.tsqueues a durablethread.cold-archiveeffect after the provider detaches andterminal.cleanup; per-thread effects run in order. The effect hydrates any pending V1 transcript first and writes the complete bundle in commits of about 2 MB. A main-only transaction then rechecks eligibility and marks the manifestmoving, which hands the thread's reads and shell overlay to the bundle; the hot rows go afterwards in commits of at most 500 rows, then its attachment files and provider logs, and only then is it markedcold, so an interrupted cleanup resumes frommoving. Attached WAL databases do not commit atomically together, so the bundle is always complete before rows go. Bundle chunks are deleted in short commits too, each counting its deleted rows inside the same transaction, header last. Every archive and purge starts on a fresh event-loop turn and yields between commits, so convergence of a large archive never holds the loop for one whole conversation. - Restore:
ThreadManagementServiceruns reads (snapshots, paged history, records with fields, the WebSocket thread subscription's snapshot or replay throughwithThreadReadable) and dispatched commands (one lease per thread the command reads) insidewithHot. A restore first checks the bundle header and version, failing the read and keeping the manifest when the bundle is missing or newer. It marks the manifestmoving(so an interrupted restore is finished as a move by startup reconcile), writes attachment files outside any transaction, then inserts rows one page of chunks per commit withINSERT OR IGNOREagainst the current columns (newer hot rows win, older bundles survive compatible migrations), and in a final commit marks the manifestrestoredand queues a delayed re-cold effect (RECOLD_DELAY_MS, two minutes). Readers keep waiting until that commit. Clients therefore never see a cold thread as missing, and the client's not-found parking and retry backoff need no cold-storage handling. - Attachments:
AssetAccessasksensureAttachmentHotbefore signing or serving an attachment; when the file is missing it finds the bundle chunk by file name (indexedkind) and restores that conversation. A file a concurrent move removes right after that check gets one more restore before the request counts as not found. Inline tool-output image signing and serving also take awithHotlease around the stored turn-item read, so screenshots remain available after archive and after a re-cold between signing and serving.server.tsmergesThreadColdStorage.layerWithReconcileinto the runtime for this, so routes resolve the live service. - Unarchive: dispatching
thread.unarchiverestores the thread and cancels any queued re-cold so later effects for the active thread are not held behind it. After the command, failed or not,scheduleArchivequeues a cold-archive effect off the command's path: it drops the bundle (in short commits) and manifest of the unarchived thread, or moves a still-archived thread back. Free pages are reclaimed by that effect's compaction, not on the unarchive path. Deletion skips restore entirely, and shell-only record reads (getThreadRecords(id, [])) never restore. - Delete:
planThreadDeletion(shared bythread.deleteand project removal, including the CLI) ends with athread.storage-purgeeffect. While a fork still reads the deleted thread (checked after the purge reserves the thread) the purge waits; the fork's own purge queues the source's purge again, and startup reconcile retries it. A fork's purge also queues a cold-archive for an archived source it kept hot. Otherwise it removes attachment files listed in the bundle and the messages, provider logs, the cold bundle, the moved tables (including the V1 rows), provider threads, bindings, subagent links, launch workflows andprovider_session_runtimerows, and provider sessions with no binding left whose owner is this thread or already purged, rediscovered on every attempt so an interrupted purge cannot strand one. It keeps the deleted shell row, itsthread.*events, V2 command receipts and outbox rows, whichstorageCleanup.tsworktree-on-delete cleanup and projection replay depend on. - Startup: building
ThreadColdStorage.layerWithReconcilestartsreconcile, parked until activation (forkParked), so it always runs against the live service; startup code outside the orchestration layer would resolve the keep-hot default instead. It queues cold-archive effects for archived threads without a manifest, leftrestored, stoppedmoving, orkept-hot(the blocker may be gone), and for unarchived threads that still have a manifest; and purges for deleted threads not yetpurged. It skips threads with unsettled lifecycle effects and uses one identity per startup, so work whose earlier effect failed for good runs again. Upgraded databases converge without migration-time queueing. - Compaction never runs a full
VACUUMon the server connection; that rewrites the whole file synchronously. Space is reclaimed withincremental_vacuumin steps ofRECLAIM_CHUNK_PAGES, one event-loop turn each, whenever the lifecycle queue has no due work (a backlog reuses free pages meanwhile; delayed re-colds do not hold it back). This needsauto_vacuum = INCREMENTAL, which SQLite only changes through a full rewrite, so the branch preserves upstream's empty-database guard and sets it where a rewrite already happens or the file is new:persistence/Sqlite.tssets it before creating a fresh database, andinitializeV2Database.tsmakes the one-time V1 to V2 copy withVACUUM INTOfrom an incremental-mode source connection in a worker thread (replacing upstream's main-threadbackup), so the copy is compacted, incremental, and off the event loop. A database already on V2 inNONEmode is never converted: freed pages are reused but the file does not shrink. Upstream sets main-databasesynchronous = NORMAL; archive storage retains its default FULL synchronization. The shared checkpoint worker includes attached archive WALs, refreshes late-enabled WAL state, and defers only SQLite BUSY primary and extended categories until its next existing check. Other schema-refresh errors retain the writer fallback. Checkpoints retain their thresholds, idle handling and shutdown ownership. - Standalone migration
081_ThreadStorageLifecyclefollows upstream080_ScheduledTaskThreadModeand createsthread_archive_manifests. The bundle tables live inarchivev2.sqliteand are created by the service. Previously published fork databases retain their existing schema and IDs. apps/server/scripts/t3-sqlite-state.tsderives both database paths from the shared server configuration and accepts--database archivefor bundle queries. Archive and restore behavior should use application commands rather than direct bundle writes.ThreadColdStorageis aContext.Referencewhose default keeps everything hot;server.tsprovidesThreadColdStorage.layerWithReconcilenext toResourceCleanupService.layerand merges it into the runtime, so thread management, the effect executor and asset routes resolve the live service.ThreadColdStorage.layeris the service without startup reconciliation, for tests.docs/internals/overview.md("Archived conversation storage") records the cross-component constraints.
Transcript export uses the leased getThreadSnapshot before building upstream's complete transcript, retaining UTF-8 header/row accounting and the typed size-limit error. Each lazy search-stream pull takes its own read lease. CommandOutputHub restores only its initial persisted lookup; consuming the output stream holds no storage lease. Upstream DatabaseStorage owns periodic main-database reclamation, while lifecycle compaction also reclaims the archive after due work drains; broader ownership consolidation remains deferred.
McpAppRequests.resolveApp reads the raw stored item through ThreadManagementService.withThreadReadable, preserving its captured app reference while restoring a cold conversation and leasing the lookup. Model-context updates can therefore resolve a cold app without starting or requiring a provider session. Cold-storage coverage verifies restoration and persisted model context with the session service unavailable.
MCP App model-context rows stay hot during archive and are removed by permanent storage purge with a thread-scoped predicate, alongside the other purge-only rows. The purge test verifies that archive retains the context and deletion removes it.
Known limits: commands that bypass ThreadManagementService and dispatch straight to the orchestrator on an archived thread (pull-request link sync, internal workers) see the cold thread's shell only; they touch thread payload fields, not conversation rows. A kept-hot thread is re-evaluated when it is archived again, when the last fork keeping it hot is purged, or at the next startup.
apps/server/src/orchestration-v2/ThreadColdStorage.test.ts covers move, shell overlay, read restore and re-cold, unarchive followed by the bundle drop, stream versions after cold-time events, the V1 rows of a migrated conversation, eligibility, purge, reconcile (including retries after failed effects and kept-hot re-evaluation), startup convergence when the live service is built, compaction in bounded steps that let the event loop run, resuming an interrupted moving move, an interrupted restore finished as a move, a move and a purge deferred by a lease, a fork admitted during a move, the reference recheck before moving, a missing bundle, cold attachment restore, HTML tool-page bytes and turn-item round trips, deferred fork-source purges and the archived fork source moved cold after its last fork, leftover attachment files, shared provider sessions, and bundle restores across column changes. apps/server/src/persistence/initializeV2Database.test.ts covers the V1 copy's auto-vacuum mode for each source mode. ThreadManagementService.test.ts covers the read, command, delete and unarchive gates.
Persisted web and mobile thread details are fast-paint caches, not an archive source of truth, and archived details are never cached (shouldPersistThread in packages/client-runtime/src/state/threads.ts). A thread.archived detail event, an authoritative archived detail snapshot (socket or HTTP), successful local archive acknowledgement, or shell removal evicts the persisted detail through packages/client-runtime/src/state/threadCache.ts. The archive command in packages/client-runtime/src/state/threadCommands.ts (archiveThreadAndEvictCache) evicts after the interruptible server command and keeps the handoff masked from cancellation; it holds the thread's cache state across dispatch and skips the eviction when a revival (an authoritative unarchive) moved the generation meanwhile. Per-thread cache generations, eviction tombstones, and write locks prevent queued or finalizer writes (V2's runCachePersistence queue carries the generation) from recreating the snapshot. A failed disk removal keeps a body-free tombstone even with no subscriber retaining the state, until a later removal succeeds or the thread is revived. Shell additions and thread.unarchived events revive the cache while invalidating only writes captured during the tombstoned generation. thread.archived and thread.unarchived are per-item boundaries in the detail event batches so these effects run around them. Web IndexedDB migration version 6 and mobile SQLite migration version 2 each clear legacy thread-detail caches once.
The five-minute ThreadResumeCache retains the archive generation and eviction listener after its detail subscription closes; eviction clears the warm body and blocks stale republishing, while body-free deletion tombstones are retained. Detail subscriptions check resume-cache ownership, so an obsolete subscription cannot clear its successor's cache or tombstone. Shell synchronization (packages/client-runtime/src/state/shell.ts) diffs authoritative snapshots: removed threads are evicted (failed removals stay pending and retry while the thread stays absent) and added threads are revived; the set diff is skipped when the thread ids are unchanged and nothing is pending. On each authoritative snapshot it also lists the saved details (EnvironmentCacheStore.listThreadIds, implemented by the web IndexedDB and mobile SQLite stores; IndexedDB listing uses the shared connection handle and retries once after a silent close) and evicts those whose thread is not in the active list and has no live cache state, so details stranded by a failed removal before a restart, or with an empty shell cache, are still removed. Shell resubscription retains upstream persisted cursor resume and origin age. When its first cached replay completes without a fresh snapshot, it reconciles saved details against the resulting full or partial shell, retaining membership capability checks. Cold storage keeps lifecycle events hot so resume still observes archive and unarchive.
A thread that an applied shell item removed from the active list (thread.removed, or an archive-location update) and that is active again at the end of the same batch gets restoreCachedThread, which always bumps its generation, so a late archive acknowledgement for it skips eviction. Reconciliation of saved details skips only threads with an open detail subscription (retainLiveCachedThread), not warm or command retainers.
The server also coalesces shell events per thread within a window (coalesceShellApplicationEvents in ws.ts), so an archive and an unarchive can arrive as one active update with no removal. Each applied authoritative active observation (an active thread.updated newer than the snapshot, or an authoritative snapshot listing the thread) records its sequence in the thread's cache state without touching the persistence generation; every archive eviction passes the archive's sequence (the acknowledgement's receipt sequence, the detail stream's thread.archived event sequence, or an archived detail snapshot's sequence) and, under the eviction lock, skips eviction when a newer active observation exists; a thread evicted by a sequenced archive is revived when a later active observation arrives, even after its detail closed. Deletions and shell-membership removals pass no sequence and are never revived this way (shell removals revive through the membership paths instead). An eviction retried on a tombstone never weakens its marker: an unsequenced one stays unsequenced and otherwise the greater archive sequence is kept, so a late older acknowledgement cannot make a newer archive or a deletion revivable. Archive receipts, detail sequences and shell sequences all come from orchestration_events.sequence. An authoritative snapshot lower than the applied shell snapshot means the server's sequence space was replaced: it advances a per-environment epoch, drops recorded active observations, and marks old eviction sequences older than any new one, so a thread the new snapshot lists as active is revived; an archive acknowledgement dispatched before the reset, or a detail archive eviction applied before it and still waiting on the cache permit, skips its eviction, since its sequence is not comparable. Known limit: a reset that lands while an admitted eviction is already removing the saved detail can revive that tombstone early, which costs a cache miss until the next save.
Coverage: packages/client-runtime/src/state/threads-sync.test.ts, threads-atoms.test.ts, shell-sync.test.ts, threadCommands.archive.test.ts, apps/web/src/connection/storage.test.ts, and the mobile storage/database tests. Protocol-only tests build sessions through packages/client-runtime/src/rpc/testUtils/rpcSession.ts.
The web Archive panel tracks each in-flight unarchive by environment-scoped thread key, because unarchive restores the conversation before the server acknowledges the command: apps/web/src/components/settings/ArchivedThreadsPanel.tsx disables that row's button and context menu and shows Unarchiving. Mobile needs no branch state: upstream's swipe actions (withThreadDismissal in apps/mobile/src/features/home/thread-dismissal.ts) collapse the archived row until the command settles, and the action executor ignores repeated taps for the same thread. Upstream's failed-unarchive path shows an error but leaves the collapsed row hidden until the Archive screen refreshes; that is upstream behavior, unchanged by this branch.
On mobile, threads on a settlement-capable server offer Settle instead of Archive, so archiving from a phone goes through another client or an agent's thread-lifecycle MCP tool; the mobile Archive screen still unarchives and permanently deletes cold threads. Main retains the Archive customization's shared AppActivityIndicator for its initial and action loading states.
Unsent composer drafts remain local when their thread is archived. Archive releases only the draft's image uploads (releaseArchivedComposerDraftUploads in apps/web/src/lib/composerDraftUploads.ts): images keep their bytes in the draft and upload again when it reopens, while a draft file's upload can be its only byte copy after a reload (file: null with uploadedAttachmentId), so file uploads, completed or in flight, are kept; the pending-upload sweep bounds leftovers. Discarding a draft still releases everything. apps/web/src/hooks/useThreadActions.ts calls the archive release after a successful local archive acknowledgement, and apps/web/src/threadRemovalCleanupObserver.tsx (mounted once in AppRoot.tsx, covering web and desktop) runs one reconciliation per shell change and, for each thread an authoritative live shell stopped listing (archives by another client), releases those uploads and clears previewStateStore and previewMiniPlayerStore state. It uses apps/web/src/authoritativeThreadLifecycle.ts, which keeps the last authoritative per-environment baseline while a shell synchronizes and discards it when the environment leaves the catalog.
The ThreadStorageLifecycle schema is already published at main migration 71. Published fork migrations 35, 41 and 47 created pre-V2 tables under the same names, so migration 71 retains those tables under legacy_v1_* names before creating the V2 schema. Its SQL body and every other published migration remain frozen. The standalone branch's migration 81 is not registered again. Pre-V2 archive.sqlite bundles have no V2 restoration path; unarchive needed histories with a compatible pre-V2 build before upgrading.
The one-time V1 database copy runs VACUUM INTO in a worker thread and preserves its complete ledger. The main migration normalizer then reconciles upstream markers and applies the unapplied published fork tail. New upstream migration 80 runs at main 99, after the installed ledger through 98.
The connection cache opens at IndexedDB version 8 so installed version-7 profiles acquire upstream's query store and environment-updated index. The guarded version-7 archive-eviction boundary remains unchanged. Upstream pending-question safeguards remain in force. Partial shell snapshots do not establish archive membership: capability-gated membership checks validate omitted saved and visible threads without warming cold history, and cache-generation checks protect revived details from stale removal retries.
Web uses the Archive customization's dedicated ArchivedThreadsPanel, whose scoped snapshots, row reservations and refresh ownership cover the full server command. Native uses its customized Settings layout and shared action reservations. Their unarchive ownership supersedes the upstream panel's separate in-flight state. Archive releases recreatable image uploads while retaining surviving file uploads; permanent deletion releases all uploads and clears every matching draft record. These behaviors retain independent fixture modules and shared lifecycle mocks expose both operations.
Cold archive, storage purge and descendant lifecycle cascade share the outbox and worker. Preserve all three effect schemas, replay-safe entries and executor cases. Side-chat deletion plans retain their own descendant-cascade eligibility and the combined plan still obeys caller ceilings. App-owned child-ID lookup hydrates legacy transcripts, preserves typed errors and leaves cold histories cold; lifecycle cascades retain their hot-lease and restoration boundaries.
Cold attachments include extracted-document sidecars. Removal deduplicates overlapping candidates and retains sidecars when their primary file is absent. Provider-log cleanup uses upstream's SHA-256 names for long thread IDs and removes numeric rotations without claiming ownership of historical pre-hash files.
Worktree branch: feat/sidebar-v2-archive-controls
Archive controls retain upstream row markup, a single shared WorkingIndicator label and the upstream status palette.
The standalone source is 85ea2f04e798ceb5cee4ff6b27a395bc0369996b, based on pinned upstream ae142932d07c0596cf0497dd28cfee07ae7c0722. Archive all excludes hidden side chats using upstream listed-lineage rules, including unloaded targets. The navigation fixture preserves real UI-state exports beside its hook stub. Upstream owns Undo in its footer independently of the shelves and search state. The archive controls retain upstream multiline folder wrappers, provider placement and metadata while preserving writable controls, permission checks and read-only timestamps. PR #12703 remains closed and unmerged. The customization uses upstream's extracted row/header modules. Browser fixtures own their mounts and router disposal, and the archive-action factory remains hook-free. Undo still waits for the restored live shell before navigation.
The default sidebar preserves archive as a separate lifecycle from settle without changing upstream's archive rule. Settled rows expose adjacent un-settle and archive buttons on hover or keyboard focus. Archive eligibility requires upstream's threadRuntimeCanArchive(thread.runtime) and the target environment's AuthOrchestrationOperateScope everywhere: the settled-row button, root context menus in the default sidebar and chat header, the default sidebar's multi-select Archive (N), and Archive all. Upstream's archive command detaches the provider session and stops leftover background work, so the branch adds no background-work guard. The row archive button stays visible but disabled while upstream blocks archiving, with upstream's "Cannot archive while the provider is active." copy. Read-only connections omit settled-row lifecycle controls and keep their status timestamps visible on hover. Archive all subscribes to grants for every settled environment and counts only writable, idle threads. Shared archive coordination checks all pending targets before confirmation and again after it; upstream's archive command rechecks the grant at each mutation. Selected-thread and root menus retain upstream's permission checks. Nested subagent rows continue to omit root lifecycle actions. The legacy sidebar and threadActionMenu.logic.ts remain upstream-owned and unmodified. The shared useThreadActions.ts hook carries focused Undo synchronization alongside main archive and delete cleanup.
The collapsible Settled shelf header includes an Archive all action alongside its expansion control. It applies to the complete settled partition in the current project scope, including rows behind settled-tail pagination and pinned threads that upstream classifies as settled, and remains available when the list begins with settled conversations. Individual default-sidebar and chat-header actions, the default sidebar's selected-thread action, and Archive all honor the shared archive-confirmation setting, use the existing archive command and archived-snapshot refresh path, preserve already archived results if a later bulk mutation or post-archive navigation fails, remove each archived row from any active selection as soon as it archives (so a row restored by Undo and reselected mid-batch keeps its selection), and report failures without implying that completed archive work was rolled back. These actions share one process-wide reservation pool keyed by the environment-scoped thread identity and coordinate their targets from confirmation through mutation. Each flow synchronously queues every target behind that thread's current holder in start order, so waits only point at older flows and cannot form a cycle; an uncontested flow still starts synchronously. A queued thread is omitted when its own predecessor archived it successfully or intentionally skipped it after a live eligibility re-check, and retried when the predecessor canceled or failed, instead of racing or silently dropping the later request. A flow's first own completion of a thread detaches that thread's queue from later lookups, even when a waiter queued behind it holds the entry, so a fresh request for it (for example after Undo while the rest of the batch runs) starts a new queue immediately, while flows already queued behind it still omit it through the completion they awaited. Inherited and repeated completions and final cleanup remove only this flow's own entry, so they never drop a newer queue. Completed archives and intentional eligibility skips are published to waiters even if a later mutation or navigation throws. Bulk flows re-check upstream's runtime rule after coordination and confirmation, while Archive all additionally re-checks settled-partition membership from the live thread shell (still settled and not snoozed) within the rendered project scope before each mutation, so a stale or unmounted sidebar cannot archive a thread that left the shelf. Entries that started a run or were un-settled while a flow waited are skipped without aborting the remaining confirmed batch, and the user is warned about the skipped entries.
SidebarArchiveControls.tsx owns the fork-specific settled-row controls and the Archive all button, while SidebarArchiveControls.logic.ts owns sidebar-only filtering and the live settled-membership rule over upstream's runtime check. threadArchive.logic.ts owns outcomes and their user notices (getArchiveOutcomeNotices), the skip-aware archiveEligibleThreadEntries batch helper (distinct from upstream's archiveSelectedThreadEntries, which the legacy sidebar keeps), and process-wide reservation coordination. createThreadArchiveActions.ts owns shared confirmation, coordination orchestration, toast delivery for those notices, individual archive actions, and selection cleanup for the default sidebar and chat header. useSidebarArchiveActions.ts adds selected-thread live rechecks and all-settled membership policy to that shared lifecycle. Sidebar.tsx retains the archive integration points in the upstream-owned row and list surfaces without changing their module exports. The upstream buildThreadActionMenuItems builder owns root-menu composition, including the archive entry, its label, position, and isRunning disabled state. Sidebar.tsx and useThreadActionMenu.ts route the menu's archive choice through createThreadArchiveActions.ts. Upstream auto-settle settings remain available in the same menus. Successful archives retain upstream's per-thread Undo notices, including when invoked by a bulk flow. Undo waits for the restored live shell before returning to the archived thread when archiving moved the reader to a draft; the fork's outcome reporting still distinguishes an archive failure from a later navigation failure.
Upstream's shared CollapsibleSectionHeader (apps/web/src/components/ui/collapsible-section-header.tsx) owns the shelf header look and accepts no className; its accessory renders inside the toggle button. The fork adds a trailing slot that, only when supplied, wraps the toggle in a flex row and renders the slot outside the button, so Archive all is never a nested button. Other callers without trailing keep upstream's DOM. The sidebar's SidebarSectionHeader passes Archive all through that slot.
Shared lifecycle-button classes keep settle, un-settle, and archive affordances aligned with the upstream row surface tokens while upstream snooze and wake controls retain their own shelf semantics. The fork-owned settled-row component preserves upstream's styled un-settle tooltip and uses the same tooltip primitive for the archive button's enabled and disabled status copy instead of a native title attribute. The slim settled status slot mirrors upstream's focus-visible crossfade so its timestamp yields to both lifecycle buttons instead of remaining underneath them, while Woke stays visible and the controls move into flow beside it. Disabled archive controls remain focusable, expose aria-disabled, retain pointer targeting for the styled explanation tooltip, stop row or shelf propagation, and never dispatch the archive action. Their cursor and hover tone communicate the unavailable state. An in-flight Archive all uses aria-disabled and aria-busy rather than disabled, so it keeps keyboard focus, and the hook's in-flight guard ignores repeat activations; it remains mounted after every archivable row has left the list, and the sidebar keeps the Settled header in its list, and holds back its empty-state copy, while the batch runs even when every partition is empty. The surrounding row structure, including row-level tooltip wrapping outside the archive control, row sizing, settled pin markers, pinned-thread sorting and dragging, filtering, un-settled-thread re-entry ordering, and decorative environment/provider status semantics, remains upstream-owned. Upstream title-search mode uses separate navigation-only result rows and temporarily replaces the normal lifecycle list; clearing search restores the settled shelf header and row archive controls.
Branch tests follow the upstream standard of exercising logic and observable behavior rather than static markup or wiring. threadArchive.logic.test.ts covers batch outcomes, outcome notices, and reservation coordination (waiting, omission, retry, sibling queueing, start-order queueing that cannot deadlock, release on completion, detaching a completed queue from fresh requests while earlier waiters still omit it, and publication on failure) without polling: the coordinator reserves and starts uncontested runs synchronously, and later starts are awaited through explicit signals. SidebarArchiveControls.logic.test.ts covers the Archive all live re-check against a stale rendered settled set across a newly started run, an un-settled, a snoozed, and a vanished thread, plus the project-scope bound and mixed-permission settled-thread filtering. SidebarArchiveControls.browser.test.tsx renders SidebarArchiveAllButton in the shared header's trailing slot in a real browser with React DOM and asserts that it stays outside the toggle, dispatches without toggling, and keeps both button identities, with the busy button still focusable, through an in-flight batch with no archivable rows left until completion. The Settled header's retention in the sidebar's inline list builder, row layout, and tooltip copy have no dedicated tests. docs/user/thread-sidebar.md documents settled-row archive and Archive all in its settle section.
Primary files:
apps/web/src/components/Sidebar.tsxapps/web/src/components/SidebarArchiveControls.tsxapps/web/src/components/SidebarArchiveControls.logic.tsapps/web/src/components/SidebarArchiveControls.logic.test.tsapps/web/src/components/SidebarArchiveControls.browser.test.tsxapps/web/src/components/threadArchive.logic.tsapps/web/src/components/threadArchive.logic.test.tsapps/web/src/components/ui/collapsible-section-header.tsxapps/web/src/hooks/useSidebarArchiveActions.tsapps/web/src/hooks/useThreadActionMenu.tsapps/web/src/hooks/createThreadArchiveActions.tsapps/web/src/hooks/createThreadArchiveActions.test.tsdocs/user/thread-sidebar.md
On main, every sidebar and chat-header archive, including bulk and Archive all, goes through useThreadActions.archiveThread, so cache eviction and recreatable draft-upload release apply before server cold storage. The Archive panel uses its own row reservations; both paths reach the same lifecycle commands and preserve subagent cascading. The real-route Undo fixture keeps the composer store real while isolating transport and presentation, so delayed shell delivery and route guards remain authoritative with the integrated cleanup helpers.
The upstream thread-action permission and Undo fixtures mock both integrated draft-cleanup exports: archive releases regenerable uploads, while permanent deletion discards the draft. Their tests retain the upstream permission and Undo behaviors through the integrated archive path.
The current branch preserves archive Undo synchronization: an unarchive receipt is followed by waitForThreadShell before returning to the conversation. Its real-router regression uses a live environment shell so the upstream missing-thread guard remains authoritative during delayed restoration. Integration retains image-only archive cleanup and permanent-delete draft cleanup, with grants and runtime eligibility rechecked by the shared archive coordinator.
Archive all collection and mutation-time revalidation use upstream parent-aware tag visibility, including unloaded targets. A tag change while confirmation or a reservation is pending can remove a confirmed target; it never admits a newly visible, unconfirmed target.
Worktree branch: feat/cross-environment-project-actions
Remote action groups use upstream's shared AppBar project menu on both iOS and Android. Destination permissions, terminal ownership and completion-time focus remain shared with web and desktop.
The standalone source is 8a991a7c8d03eae5bec7b29c1913d28d81933557, based on pinned upstream ae142932d07c0596cf0497dd28cfee07ae7c0722. Close callbacks retain their original panel owner. Completion-time focus follows the current side-chat host when navigation changes hosting during a pending launch. The configurable thread-details card retains its Scripts visibility gate and supplies the destination project and thread context to the relocated action control. No PR exists. Destination terminal routing uses upstream's extracted terminal-controls hook, and native draft headers retain destination availability checks.
Project-action terminal identity derives from the destination project ID; each RPC receives the destination environment separately. Launch and split explicitly focus the destination terminal. Native terminal selection after shell exit retains destination project metadata and that same project identity.
The normal project actions menu includes actions defined for the same logical project in other connected environments. Local actions appear first, followed by separated groups such as From MacBookPro, then the existing From t3.json group. Remote actions execute through their destination environment and project checkout with destination permissions and terminal context; results and errors identify that destination, and failures never fall back locally. Read-only destinations disable their actions, disconnected environments are omitted, and missing destination checkouts produce an explicit error. This feature is independent of Version Control and uses the existing project/environment grouping.
Shared web/desktop controls and the shared native AppBar menu expose the feature on iOS and Android. Retained web proof verifies destination launch, split and typing across distinct Windows environments and checkouts. Retained native iOS proof verifies execution on the Mac destination and terminal selection after shell exit. Historical browser evidence verifies menu ordering, destination permissions and routing, terminal focus, split/new/close controls and local execution. The exact pending-navigation race remains established by focused unit coverage. Native iOS proof predates this rebase. Broader connection modes remain outside that proof. Fixed verification ports and exact evidence remain in the ignored branch contract.
Worktree branch: feat/version-control-panel-work
Azure panel routing preserves decoded organization and repository names, literal .git repository names, SSH URI paths and explicit repository arguments. Refused or unsupported routes remain typed failures rather than falling back to a different repository.
Panel failures use schema-backed Effect errors. Temporary index operations provide only GIT_INDEX_FILE; the Git driver owns inherited host environment and defaults. Conversation rows and panel indicators share WorkingIndicator, its single status label and upstream semantic colors.
The standalone source is ef32f7bbf1a4b79529ffac0f114f1bd2317052eb, based on pinned upstream ae142932d07c0596cf0497dd28cfee07ae7c0722. Cursor Origin listings retain their provider-specific repository and avatar contract in the panel. Selected-file commits reject an active merge before allocating a temporary index; the complete staged merge remains committable. The all-remotes interval uses upstream IntervalSecondsField. Both Version Control roots suppress history swipes while preserving horizontal scrolling. Test-only follow-ups retain NodeServices and branch-aware watcher fixtures, then cover forced publication from a coalesced refresh without another Git read. Working card, folder and multiline folder rows share WorkingIndicator; compact folder icons stay 12px and cards stay 16px. PR #12704 remains closed and unmerged. Version Control exists only in web/desktop. Main retains both project-action destinations and environment-owned terminal targets. Saved targets preserve their optional project-owned thread identity. Target resolution keeps destination permissions, cwd and focus across launch, split, activation and close. Upstream terminal focus requests resolve that same destination for launch, split, activation and close. Comparison-base lookups use the stable callback without restoring a passive ref-copy effect. The removed environment-actions menu is not restored. The standalone branch carries no terminal-surface targets or destination-routed project-script saves; that menu was their only producer. Main should drop its Version Control-only copies at the next integration while keeping what Cross-environment Project Actions needs. The panel is one list of actionable items below a narrow toolbar holding Fetch; there are no environment headers or toggles, no collapsible sections and no Remotes section. The branch/status/ahead-behind summary, loading state and environment Run actions menu are removed with their supporting snapshot fields and clients. Add, remove and single-remote fetch remain at the RPC and service boundary without a web entry point.
The web and desktop Version Control panel includes a singleton right-panel surface, live VCS status watcher, actionable panel model, selected-file commit/stash flow, branch/commit/stash/fetch actions, compare-base semantics, and Version Control panel RPC/contracts. On web, a logical project shared across environments mounts one panel controller per connected environment, nested so SourceControlPanelView.tsx merges every environment's items into one list with the active environment's working tree first. A local branch tracking the same remote branch (by normalized remote URL) at the same local tip and remote-tracking tip in several environments, reported through the optional VcsRef.sha, shows once and loads its details only in its owning environment, the active one when it holds the branch; working trees, stashes and unpublished branches are never merged. Its fetch and pull also run each peer's own sync, and peers fetch the branch after a push; forced syncs, publish and diverged choices stay with the owner. Like conversation sidebar rows, an item held by other environments shows their machine icons at its bottom left and lists every environment in its hover card; items only in the active environment show neither. Branch rows and cards use icons for current (check), default (house), upstream (globe) and worktree (the app's worktree icon). A failed environment shows its own labelled notice without hiding peers. buildSourceControlEnvironmentOption in apps/web/src/components/ChatView.sourceControl.ts is the shared constructor for the environment option consumed by both toolbar routing and the federated Source Control panel. Snapshots, status subscriptions, fetches, diffs, editor launches, and Git mutations stay routed through each instance's environment and cwd. A normal ahead-branch push starts immediately while snapshots of the other connected environments are read alongside it. Only after the push succeeds, a peer whose current branch has a clean working tree, no local commits ahead of its upstream, and tracks the same normalized remote branch is fetched, rechecked, and fast-forwarded. Peers that changed locally, changed upstreams, disconnected, or failed are left alone, and peer failures cannot misreport the completed source push as failed. Servers advertise the panel through the optional sourceControlPanel environment capability; web availability and the federated peer list require it, so older servers never receive vcs.panel.* requests. Disconnected environments are omitted, failures remain isolated to their per-panel retry state, and only the active instance may update active-thread source-control metadata or open its standalone File surface. SourceControlEnvironmentPanel.tsx names this active-only capability activeThreadRef; the active panel uses it for File-surface openings and PublishRepositoryDialog link routing, while foreign panels receive null. The shared apps/web/src/rightPanelStore.ts, RightPanelTabs.tsx, and ChatView.sourceControl.ts integration keeps singleton Source Control and multi-tab pull-request surfaces as peers: normalizeSourceControlRightPanelPresence applies repository availability to both live and retained right-panel presence, removes only Source Control, preserves pull-request and File tabs, and falls back to the first remaining visible surface when an unavailable Source Control tab was active. apps/web/src/components/rightPanelSurfaceActions.ts is the canonical add-surface descriptor for availability, activation, shortcut, empty-state and menu order, and disabled presentation (short hints in the empty-state launcher, full reasons in the add menu); RightPanelTabs.tsx derives both launcher action lists from the same inputs and uses the same actions for keyboard activation. Version Control uses shortcut V, appears directly after Diff in both the empty-panel launcher and the compact add-surface menu. The thread details panel's Changes row is a split control: the row still opens the Diff surface, and its dropdown offers Changes or Version Control, which ChatView wires to addSourceControlSurface only while the surface is available (the choice is disabled otherwise). Browser mute-menu behavior remains isolated in apps/web/src/components/rightPanelBrowserTabState.ts, so background preview mini-player lifecycle changes and right-panel browser-surface reconciliation must leave the Source Control surface open, visible, active, and present exactly once.
The shared DiffPanel file tree is in-panel navigation: selecting a tree entry reveals that file inside the diff. The explicit filename primary action is routed through apps/web/src/diffFileActions.ts and reuses the thread's default right-panel File surface. Persisted attachment tabs retain their attachment identity and payload when the file-tab migration runs for existing installs. Source Control file previews for a foreign sibling worktree retain their explicit cwd, so the same relative path in two worktrees remains available in separate File tabs.
Web source-control rows use the rich-tooltip presentation and timing shared with the default web sidebar in apps/web/src/components/Sidebar.tsx, including its ThreadHoverCardPopup glass card from apps/web/src/components/ThreadHoverCard.tsx; the branch defines no tooltip variant of its own. Working tree, file, branch, commit, and stash cards expose their full paths or refs and relevant status, timestamp, identity, URL, and line-change details without covering the row. Clickable tree and file rows use the pointer cursor alongside their keyboard button semantics. File cards use trigger-scoped virtual anchors while retaining a common panel-aligned left edge. Nested action buttons keep their terse label tooltips and preserve the parent rich card only for the nested trigger in that same row, so unrelated tooltips elsewhere are unaffected.
VCS status ignores internal .git/ watcher events before refreshing local status. Background activity keeps lightweight current-upstream status refreshes separate from the Version Control panel's broader fetch-all-remotes work. apps/server/src/utils/CanonicalPath.ts owns native-first canonicalization for existing paths. GitVcsDriverCore.ts uses it for Git common-directory cache identity, GitManager.ts uses the same path identity for status caches and worktree comparisons, and VcsStatusBroadcaster.ts keys direct and sibling-worktree watchers on it so one directory never holds two watchers. The portable filesystem path and then the original input remain fallbacks, so Windows long and 8.3 aliases for a repository and its linked worktrees share cache and worktree identity without making missing paths fatal. The normal Git interval retains the shared performance, balanced, and battery-saver values of 15 seconds, 30 seconds, and disabled, while the panel-specific all-remotes interval uses one minute, five minutes, and disabled respectively. Automatic panel fetches run only while a panel retains Git-ref demand and the owning environment's shared lock, low-power, battery, visibility, and activity policy allows the work. A host lock alone does not pause them while a foreground client retains that cwd's demand, so remote use of a locked machine keeps remote refs fresh; apps/server/src/sourceControl/SourceControlPanelFetchPolicy.ts owns that panel-only exception and leaves the shared policy unchanged. SourceControlPanelService.fetchAllRemotes applies the policy gate and refreshes VCS status after a fetch that ran, and every other successful panel mutation forks its status refresh from the service, so apps/server/src/ws.ts panel handlers only decode, call one service method, and map errors. Opening the web panel refreshes its local snapshot immediately. On subsequent web focus, an enabled panel interval first makes the interval-aware, policy-gated fetch request and then refreshes the local snapshot once; a zero interval skips the network request and refreshes locally immediately. Panel fetch-all requests use --no-auto-gc so periodic refreshes cannot repeatedly restart failed repacks. Automatic and explicit panel fetches share the existing non-interactive Git credential environment, so background refreshes cannot open credential dialogs. Explicit Fetch uses the same safeguard and remains available to connections with source-control write access, bypassing the interval cache. The previous omitted custom default migrates through the new balanced default, with its conservative cadence preserved by the panel-specific value; an explicitly persisted five-minute upstream-status override remains an override instead of being reclassified by value. Settings search indexes the individual Source Control controls: stable writing controls anchor to their rows, while Git intervals route to Maintenance's Background activity section and discovery-dependent provider-avatar controls route to the stable Source Control section.
apps/server/src/sourceControl/SourceControlPanelService.ts uses GitWorkflowService.localStatus for panel snapshots. The shared VCS status stream retains its normal PR behavior; panel-only data removal does not change full Git status or mutation status checks. Actionable rows discover the PR of each local branch with a remote-tracking ref, either its upstream or a same-named branch on any configured remote (covering a push without -u or an upstream unset after publishing), through the same cached GitManager.branchPullRequest lookup as the conversation sidebar, including head-repository validation and in-flight request reuse. Up to eight lookups run at once, alongside the sibling-worktree reads. Once a branch has an answer, a lookup still pending after one second finishes in the background while the snapshot keeps that answer. Each snapshot waits at most two seconds per sibling worktree and otherwise shows its last known changes; a late read that changes them force-publishes the root status so the panel re-reads. Discovery does not scan or cap repository-wide PR lists. Automatic GitHub discovery uses the shared API repository locator, preserving a configured GitHub default repository and fork-parent selection instead of binding searches to the push fork at origin. The returned PR repository identifies the configured remote whose base branch is compared; only open PRs with a local branch behind that target produce rows. Each such row carries the PR and reuses the sidebar badge beside the ahead/behind counters, including its status tooltip, in-app navigation, and modifier-click behavior. While a web or desktop item action runs and its snapshot refreshes, its hover controls are replaced by an always-visible action label using the conversation sidebar's Working indicator. Labels identify the operation, such as Committing, Stashing, Undoing, or Rebasing, and remain scoped to the affected item. Provider-backed change-request and commit-avatar lookups for Actionable rows remain best-effort in the panel service. Provider/auth/CLI failures must not fail the whole panel snapshot or hide git-derived actionable branch rows. These background reads share the provider-and-host cooldown used by pull-request services. Panel reads and GitHub adapter requests use the same normalized host key, including an explicit non-default enterprise port, so a pause applies to the matching endpoint without suppressing another port on that host. The first classified rate-limit response records the provider reset time or fallback backoff, and later panel reads skip the provider until that pause expires while retaining Git-derived content and avatar fallbacks. Provider adapters retain their provider-specific raw-state normalization, while resolved Git pull requests and panel VcsStatusChangeRequest schemas in packages/contracts/src/git.ts share the canonical ChangeRequestState from packages/contracts/src/sourceControl.ts.
Forgejo/Gitea change requests participate in Actionable branch discovery across configured remotes. Its provider returns no commit avatar, so panel rows retain their avatar fallback. GitCafe also returns no commit avatar and is excluded from avatar preferences. Its panel listing accepts an absent head selector for repository-wide history while preserving supplied head identity, source repository, pagination, and limit constraints.
GitHub head-specific discovery uses the upstream GitHubSourceControlProvider batched GraphQL path and default repository selection. Repository-wide panel history and commit avatars use the same provider's authenticated GraphQL and REST transport, resolving the repository from the selected remote and retaining the provider context's API host, including non-default enterprise ports. The removed GitHubCli modules are not restored.
Repository-convention message generation uses one repository-context policy reader for generic Git actions and panel commit/stash actions. Panel commit and stash generation applies the registered checkout project's writer and writing-style overrides, falling back to the main checkout's project for unregistered sibling worktrees. Both generation requests carry that owning project id into the shared Git message service. Both paths resolve the effective writer from the current provider snapshot and that project's provider enablement, falling back to the configured text-generation model when the dedicated writer is disabled or not usable. They read recent commit subjects and AGENTS.md; Claude writers also read CLAUDE.md.
Version Control and source-control provider failures should preserve structured causes when normalized for panel RPC errors. GitLab, GitHub, Forgejo/Gitea, Azure DevOps, and Bitbucket provider paths should keep provider-specific not-found/auth/missing-CLI details without collapsing structured process failures into generic strings. Web panels derive mutation availability from their destination connection's source-control write grant. Read-only connections retain snapshot, diff, branch, and stash inspection; mutation buttons, menus, and confirmation entry points are disabled. Web mutations reconcile VCS status and an authoritative panel snapshot after both success and failure before preserving a mutation error, so conflict-producing or partially applied operations remain visible. Web Source Control mutation confirmations use the shared themed LocalApi dialog with the destructive variant and never fall back to a native window.confirm; commit branch-name entry likewise uses a themed Dialog and Input instead of native window.prompt. Shift-click forced sync confirms first and names the side it overwrites: force push and publish replace the remote branch, and a reset to the upstream discards local commits, plus uncommitted changes when that branch is checked out. Diverged rows keep the explicit dialog choice, and a forced sync that would only fetch skips the prompt; forcedBranchSyncConfirmation in SourceControlPanel.logic.ts owns that decision. Azure DevOps commit-avatar lookups route through the organization encoded by the repository remote and use the stable Commits Get API version.
Panel mutations that can change refs invalidate both shared ref-cache layers in finalizers: apps/server/src/sourceControl/SourceControlPanelActions.ts and SourceControlPanelService.ts call the non-failing GitVcsDriver.invalidateRefs boundary for the shared server listRefs snapshot, while matching commands in packages/client-runtime/src/state/vcs.ts invalidate shared client ref state through onSettled. This applies after successful, failed, interrupted, or partially applied commit, branch, fetch, and remote mutations. Fetch-all is the one result-aware case: onSettled receives the command exit, and a false result (the server's background policy skipped the fetch) leaves client ref state alone. Working-tree staging/unstaging/discard, stash operations, diffs, comparisons, and other read/display-only operations remain excluded. Cache invalidation supplements the existing authoritative panel/status reconciliation and never replaces or masks the original mutation result.
Thread source-control metadata update failures should surface on the thread without overwriting unrelated thread errors, and successful source-control updates should clear only the source-control metadata error for that thread. apps/web/src/components/ChatView.sourceControl.ts resolves the visible banner with local thread errors ahead of source-control metadata errors and the V2 runtime's persisted lastError behind them; the banner keeps upstream's usage_limit error class only when the session error is the one shown. Dismissing a banner clears only its owning local or source-control error; persisted session errors are masked for the current UI session, so a lower-priority error remains available after the dismissed banner is gone. Metadata writes go through the V2 thread.metadata.update command, which has no expected-branch compare-and-swap, so the client keeps no branch-observation guard: writes are serialized per thread and only the newest request enqueued for a thread applies. A superseded request skips its write, and a request that finishes after a newer one was enqueued resolves as stale, so neither its success nor its failure changes the thread's metadata error. Grouped-project navigation retargets an open singleton Source Control surface to the active draft/thread environment and effective repository cwd, while metadata errors remain scoped to the originating environment/thread key and are pruned when that context is no longer retained.
Selected-file commits seed their temporary index from a copy of the real index reset with read-tree --reset HEAD, keeping the copy's older timestamp so racily clean files are still rechecked; a stat-less read-tree HEAD index, the fallback, made git commit and every hook command rehash the whole checkout, about five minutes for this repository on Windows. Selected commits reconcile the real index in an interruption-safe success finalizer before temporary-index cleanup. Generation and commit execution remain cancellable; cleanup cancellation must not leave a completed commit staged again. If reconciliation fails after the commit is durable, report that the commit exists, stop before push, and provide a shell-quoted, bounded index recovery command rather than inviting another commit. A stash pop whose apply succeeded but removal failed reports that distinction and warns against reapplying.
Persisted right-panel state is at storage version 16. Upstream's v14 removed the Agents surface; the branch's tuple file-surface ids, preserved attachment tabs, and environment-owned terminal targets migrate at v15 so stores already written at upstream v14 still run the branch migration. migratePersistedRightPanelState is version-independent and covers both.
Required edge cases: the current default branch remains a valid default compare ref and retains that stable base in its own branch details, status-derived default branch names such as develop are preferred over hardcoded main/master guesses, compare-history pagination queries the selected comparison range, branch pull/fetch parsing handles slashful remotes and remote-looking local branch names without treating slashless local upstreams as remote refs, fetch-before-sync refreshes the authoritative snapshot before choosing the same action's push/pull/diverged result and does not fetch an unchanged branch twice, the panel and composer share live Git status with a per-environment checkout fallback remembered for the current session, known non-Git projects disable the Source Control action and hide any retained surface while status reloads, and unseen checkouts assume Git until VCS status resolves, diverged normal merge sync is available only for the current branch, checked-out branch worktree paths fall back from porcelain worktree output to branch-format placeholders without failing on older Git versions, sibling worktree watchers run only while a panel holds git-refs demand for that cwd and keep root Actionable rows live while skipping stale/prunable worktree paths, working-tree refreshes that race an authoritative full snapshot cannot retain pre-mutation branch/remote/stash data, failed full snapshots release the in-flight full-refresh barrier so later working-tree refreshes can remain incremental, queued web refreshes still drain when the active refresh fails or is interrupted, duplicate web actions with the same key are synchronously suppressed while the first mutation remains in flight, web stash actions carry the selected immutable SHA beside the positional ref and reject shifted or missing selections, requests without a SHA keep acting on the positional ref, a running web stash action disables actions on every stash, app-owned stash mutations serialize by canonical Git common directory across linked worktrees, branch sync and undo operations for checked-out branches target the owning worktree cwd, upstream remote identity keeps local-to-local tracking separate from remote pairing and sync, local and remote deletion preserve the selected identity when names collide and reject a missing target without crossing into the other kind, checkout and deletion remain rejected by both the client and server for branches checked out in a worktree whose directory exists, a local branch still registered to a worktree whose directory is gone (prunable in git worktree list --porcelain) is pruned through the shared workflow before the delete, local branch deletion runs through upstream's GitWorkflowService.deleteLocalBranch after localBranchDeleteRefusal refuses a branch Git still lists in a non-prunable worktree (naming its path, even when forced) and a non-forced delete of a branch not merged into its resolvable upstream or else HEAD (with a surface-neutral force-delete hint; web appends its Shift-click hint), cwd-scoped working-tree enrichment avoids cross-worktree file-detail reuse, a fallback File surface uses its own cwd and reveal metadata, selected-file commits omit pathspecs after staging, commit-hook output only enriches a failed Git result and never interrupts a still-running commit, merge refs are passed after --, tracked discard restore failures surface instead of being swallowed, fallback rename parsing preserves original paths, empty working-tree diffs use the full-file fallback only for paths Git currently lists as untracked in panel-cwd-relative form, Review patches disable user-configured diff rendering, merged staged-plus-unstaged row stats are summed, late-month relative dates do not fall through to 0 years ago.
Working-tree snapshots keep every changed path visible but defer expensive untracked-file stats and temporary-index rename detection to a batched enrichment RPC. Web queues every eligible file in the snapshot, including collapsed and offscreen working trees, so totals are independent of row rendering. Keep this work separate from the initial snapshot and retain bounded batches. Git diff paths are converted from repository-relative to panel-cwd-relative before joining status rows, so panels opened below the repository root retain their line counts.
Version Control creates no terminal or project-script destination. Main retains the separate cross-environment project's actionTarget routing, destination permissions and script-save helpers; removing those with the standalone Version Control cleanup would disable active project actions. Layout version 16 drops obsolete Version Control target records while preserving actionTarget records, selected splits and tabs. Core and Version Control RPC handlers have separately inferred layers with the same authorization and instrumentation middleware. Browser tests prebundle the panel virtualizer so dependency discovery does not reload an active test page. Published PR-link migrations retain their IDs and SQL/data work; their key helper import uses upstream's frozen migration-local normalizer.
Pinned upstream authorization decodes Duration-valued Background activity patches a second time, so saving an interval override fails and leaves its previous value intact. The panel does not change that boundary; profile-only saves still succeed.
SOURCE_CONTROL.md contains the detailed implementation requirements.
Primary reference:
SOURCE_CONTROL.md
When rebuilding, retain Version Control's shortcut V after Diff, cwd-aware File tabs, persisted panel layout and destination-grant checks. Keep the core and Version Control RPC handler layers independently inferred.
The main held-PR refresh fixture supplies BackgroundPolicy subscription and demand methods, so its status stream reaches the first snapshot before testing the blocked lookup.
Version Control patch readers keep blank context lines and short gitlink summaries independent of Git settings. Commit and reflog readers suppress signature diagnostics; generated commit and stash messages use repository-wide, color-free diffs even when the panel opens below the repository root. Ref fingerprints use Effect Digest.
Worktree branch: none
This branch points the mobile Expo/EAS project at the local quicksaver owner instead of upstream's pingdotgg owner so installable internal mobile builds can be produced without requiring access to the upstream Expo organization.
Expected behavior:
apps/mobile/app.config.tsusesowner: "quicksaver"for EAS project ownership.apps/mobile/app.config.tsuses EAS project idc65ac46d-6488-49af-b61e-ab9bef78f96e.apps/mobile/app.config.tsuses OTA updates URLhttps://u.expo.dev/c65ac46d-6488-49af-b61e-ab9bef78f96e, matching the local EAS project id.
Upstream update rule:
If upstream changes the mobile EAS project metadata, preserve the local quicksaver owner, project id, and matching OTA updates URL unless this branch intentionally switches back to the upstream Expo organization or to a new local EAS project. Re-check this triplet before resolving conflicts in apps/mobile/app.config.ts, because mixing upstream and fork values can make local builds fail authorization or route OTA updates to the wrong Expo project.
Primary file:
apps/mobile/app.config.ts
Worktree branch: none
This branch points local iOS development signing at Apple team 6JGX8M7Z3L instead of upstream's T3 Tools team. This is local-development glue, independent of the Version Control panel customization and the Expo/EAS project ownership above.
Expected behavior:
apps/mobile/app.config.tsusesios.appleTeamId: "6JGX8M7Z3L"so Expo prebuild/run commands and generated Xcode projects select the fork owner's Apple team.- Personal Team development builds set
T3CODE_IOS_PERSONAL_TEAM=1and useT3CODE_IOS_PERSONAL_TEAM_BUNDLE_ID=com.quicksaver.t3code.devso the local bundle id does not collide with upstream's registered identifiers. - The existing Personal Team build mode omits unsupported app-group, widget, share-extension, push, and native Sign in with Apple capabilities; this reduction is local-development glue and must not change full-capability EAS/release builds.
- Physical-device builds use a valid Apple Development certificate, its private key, and an Xcode-managed development provisioning profile for team
6JGX8M7Z3L. - Simulator builds use Xcode's ad-hoc
Sign to Run Locallyidentity and do not exercise physical-device certificates or provisioning profiles. Reduced-capability Personal Team simulator builds use the explicitios:devworkflow below.
The cached simulator helper in scripts/mobile-native-client.ts follows upstream's full-capability policy, forces T3CODE_IOS_PERSONAL_TEAM=0, and expects com.t3tools.t3code.dev. Use that mode for its Metro process as well. The development-simulator EAS profile in apps/mobile/eas.json remains upstream-owned; the fork's owner, project id, OTA URL, and Apple team remain in apps/mobile/app.config.ts. Personal Team flags belong to the explicit local workflow below, without changing cached simulator or EAS/release capabilities. scripts/mobile-native-client.test.ts loads the real app configuration to protect the helper's fixed identity and widget policy without a native build.
Apply the Personal Team values to both Metro and the explicit local native build. Expo serves the development manifest from Metro, so starting Metro without these values would report the full-capability configuration to JavaScript even though the native binary was built without App Groups and extensions.
In one terminal:
cd apps/mobile
T3CODE_IOS_PERSONAL_TEAM=1 T3CODE_IOS_PERSONAL_TEAM_BUNDLE_ID=com.quicksaver.t3code.dev vp run dev:clientThen build/run from another terminal with the same values:
cd apps/mobile
T3CODE_IOS_PERSONAL_TEAM=1 T3CODE_IOS_PERSONAL_TEAM_BUNDLE_ID=com.quicksaver.t3code.dev vp run ios:devUpstream update rule:
If upstream changes the mobile Apple team id or Personal Team build path, preserve team 6JGX8M7Z3L, the com.quicksaver.t3code.dev local development bundle id, and the reduced-capability Personal Team behavior unless the fork intentionally moves to another Apple team. Keep this ownership override separate from Version Control panel documentation and behavior.
Primary file:
apps/mobile/app.config.ts
Mobile configuration tests preserve the fork signing team independently of upstream hosted app-link associations. Upstream hosted domains authorize upstream-signed apps; a locally signed fork is not added to those associations by its configuration.
Worktree branch: fix/restore-preview-background-screenshots
Only human Browser Screenshot capture uses this customization. Historical native proof remains revision-specific; current-head source review, human approval and native freshness acceptance remain outstanding.
Worktree: C:/Users/Quicksaver/.t3/worktrees/t3code/t3-ea8af355. Published source 068596e60020aa5b71a34f3e2ea196b8a9643ac1 is directly above pinned upstream ae142932d07c0596cf0497dd28cfee07ae7c0722. Human native Screenshot freshness and rendering cleanup are maintained in open PR #22335. Required CI checks and same-head source checks pass. The wider desktop suite retains one Windows path assertion reproduced in the pinned upstream control. The PR template headings were corrected and acknowledged, but current-head source review remains paused and human approval remains outstanding. Native acceptance on Electron 44.10.1 remains unverified. Main distinguishes still-image leases, which unthrottle the guest, from agent leases, which only request presentation. Finishing the last human/recording/PiP consumer restores guest throttling even while an agent capture continues; each remaining consumer retains its own main-window frame lease.
Historical complete-app human Screenshot UI comparisons used genuine imported history on Windows, macOS and Linux. Windows BEFORE failed hidden, overlapping-hidden and display-off-hidden captures; AFTER saved fresh PNGs. Both macOS revisions saved fresh PNGs in the measured cases, including locked with the display off. Linux Docker/Xvfb with Openbox and software rendering improved from 5/8 to 8/8 fresh PNGs. Linux physical display sleep/lock, hardware GPU, Wayland and installed AppImage behavior remain unverified. The Windows/macOS proof, Linux proof and PR retain the measured results and limits.
This replacement follows the maintainer's re-landing direction. Original PR #19931 remains merged, closed, linked and untouched. Open PR #21534 covers agent/CDP screenshots and explicitly excludes this human Screenshot path.
Worktree branch: terminal-preview-19/background-agent-snapshots
The capture composition preserves upstream's shown-tab reporting and idle-tab capacity release. Human still-image consumers and agent presentation leases remain separate, and the removed browser test module stays removed. Current queue tests cover preparation, renderer acknowledgement and cancellation beside the new tab visibility state.
PR #21534 is open, authored by upstream maintainer juliusmarminge, and maintainer-owned / pull-only. Its published source is pingdotgg/t3code:terminal-preview-19/background-agent-snapshots, fetched through upstream, at 59ab685ba20991988d2f8e520bf408e70830c825. Windows and Mac retain a protected local branch tracking upstream/terminal-preview-19/background-agent-snapshots, without a checkout. Ownership records are in each clone's <git-common-dir>/maintainer-protection/branches/terminal-preview-19--background-agent-snapshots/BRANCH_DETAILS.md and pr.md. Only inspect and fetch/pull published source; do not edit, squash, rebase, commit, push, run automated fixes or alter its PR metadata or comments. Explicit force-fetch and local reset to the published head are permitted after maintainer rebases.
Agent snapshots of a desktop preview tab time out while the T3 window is minimized, hidden, covered or never painted; the unanswered screenshot then holds the server capture lock, so later calls on that tab time out too. While a page-level screenshot is pending, the PR holds a main-window frame-capture lease, has the renderer commit the guest's in-viewport position and acknowledge it, and serially requests discarded capturePage(undefined, { stayHidden: true }) copies until CDP's screenshot settles, all under an eight-second deadline.
Main ports the pinned PR around upstream's DesktopBrowserHost screenshot queue, focus holds and painting state. The optional preparation hook receives the native sender, so cancellation releases its renderer request and frame lease while the queue still waits for any screenshot already owned by Chromium. A per-relay abort cancels preparation on release or detach. Only the capture-ready IPC channel and a private IPC method are added; the obsolete recording-frame channel stays removed. Existing queue and navigated-tab tests cover preparation, renderer acknowledgement, cleanup and relay cancellation. The removed browser test file is not restored. Published concurrent-capture fixtures follow the current per-guest queue: renderer preparation begins when a request owns its turn, and a timed-out request releases presentation while its native CDP completion still owns serialization. Existing queue cases cover preparation forwarding and release cancellation. Native acceptance on Electron 44.10.0 remains unverified. Earlier Windows/macOS evidence on Electron 44.9.3 is historical and does not validate this assembled source; packaged-app, Linux, tunnel and mobile-viewer behavior remain unverified.
Worktree branch: feat/detachable-panels
Worktree: E:/Projects/t3code.worktrees/detachable-panels. Its local, unpublished source is 3db5148746e66b3ddfb6bf74a491e45b1d0ccd17, directly above base/main at ae142932d07c0596cf0497dd28cfee07ae7c0722. Tracking remains unset; no upstream PR is planned.
Desktop users can move a right-panel tab (terminal, browser, device, Files, file, diff, pull requests) into its own OS window with Open in new window beside the panel's maximize control or from the tab context menu. Detached windows are same-origin popups rendered by the main renderer through React portals, so they share its stores and WebSocket connection; the existing RightPanelSurfaceContent renders unchanged inside them. The panel keeps a placeholder tab with Show window and Re-attach. Web and mobile do not offer detaching.
Expected behavior:
- A conversation owns its detached windows. They show, without taking focus, while that conversation is rendered and hide otherwise; Settings opens over the conversation and keeps them visible, while a new draft or the pull requests page hides them. A conversation's windows open the first time it is shown in a session and stay open, hidden when not owned.
- A user close of a detached window follows its tab's close path, including the terminal confirmation, shown in that window. Quitting, closing or reloading the main window, a renderer crash and conversation switches never close the tab. A closed detached tab reopens detached at its last bounds through reopen-closed-view.
- Detached tabs and their bounds persist in their own localStorage key and restore at validated bounds. Archiving a conversation drops its windows; deleting removes its surfaces, which prunes them.
- Detached native input callbacks ignore destroyed windows before reading their contents.
- Only panel-scoped shortcuts work in a detached window. App menu accelerators are ignored there except editing roles and the macOS Quit, minimize, hide and full screen roles. Confirmations, copies and the pull-request copy shortcut started from a detached window use that window. A confirmation whose window hides or closes is cancelled so it cannot block the app-wide queue.
- Activating a detached surface (tab click, open, agent reveal, even of the already active tab) brings its window back without focus. Navigating inside a detached file or Files window moves the window to the replacement surface; opens from the main window never do.
- A detached browser's webview is hosted at app-shell level and portaled into its window, so conversation switches never reload it; detach and re-attach reload it once. The floating mini player and detaching are exclusive.
- While a detached window is shown, the main renderer stays unthrottled, because detached content runs on its timers and frames.
Primary files:
apps/desktop/src/window/DetachedPanelWindows.tsapps/desktop/src/ipc/methods/detachedPanels.tsapps/web/src/detachedPanels/apps/web/src/components/ui/portalContainer.tsx
Window-aware code that detached documents rely on: Base UI portals take a PortalContainerProvider container; observeResize keeps one observer per window; the Ghostty terminal surface uses its mount's window, document and navigator; the @pierre/diffs diffs-container element is defined in each detached window's registry with styles rebuilt in that realm, because the library registers it in the main window only.
Focused regression coverage:
vp test run apps/desktop/src/window/DetachedPanelWindows.test.ts apps/desktop/src/preview/Manager.test.ts apps/web/src/detachedPanels apps/web/src/rightPanelStore.test.ts apps/web/src/confirmDialog.test.ts apps/web/src/previewMiniPlayerStore.test.ts apps/web/src/hooks/useCopyToClipboard.test.ts apps/web/src/lib/editableFocus.browser.test.ts apps/web/src/components/files/fileEditorDismissal.browser.test.tsxMain resolves each detached Version Control view's repository independently of the active panel tab. Detached terminal new, split, close and focus actions retain the cross-environment action's destination, even when another tab is active. File opens retain both their checkout cwd and the replaced surface identity, so a detached explorer follows navigation without losing sibling-checkout routing.
Worktree runtime verification covered Windows and macOS on the reviewed branch. The detached device panel, Linux behavior, macOS menu shortcuts and minimized Dock tiles remain unverified. macOS full screen retains the system's space-switching behavior described in the user guide.
Worktree branch: none
Magi weighted consensus no longer has fork code. It runs entirely as the $magi skill system in ~/.codex/skills (magi, magi-arbitrator-*, magi-config-* and the personality-* lenses), whose scripts/magi.ts keeps each run in a per-machine store and dispatches participants as ordinary T3 delegated tasks. With its branch retired, keep registering the published Magi migrations when assembling main, with bodies taken from the previous main tip: IDs 72 MagiV2Projections, 73 MagiV2ArmClearingAndParticipants and 86 MagiAgentOnly, as listed under Published Database Compatibility. Installed ledgers already record them, and fresh databases still need the same sequence. Base/fork's 081_DropMagiTables.ts follows upstream migration 80 ScheduledTaskThreadMode in this standalone branch and removes those tables. Its published assembled-main ID remains 95. Migration 86 keeps its published prompt conversion in Migrations/magiArbitratorPrompt.ts, including the frozen default text, so replay does not depend on the retired Magi runtime or current contracts.
After every upstream update, recheck that system for incompatibilities or stale instructions introduced by the update: orchestrator_capabilities targets and options, async delegate_task dispatch and completion waking, fresh delegates for each review and confirming turn with prior findings and objections carried in the brief, and participants' ability to run magi.ts respond and write its run directory. Adjust the skills or the script in ~/.codex when any of these changed.
When updating from upstream, keep these local behaviors unless upstream has an equivalent implementation:
- Version Control remains a web/desktop singleton beside pull-request, File, Device, and preview/browser state. Preserve its web/desktop merged multi-environment list with deduplicated tracked branches and spread fetch/pull, coordinated clean-peer fast-forward after push, stat-preserving selected-commit index, cwd-correct File routing, context-keyed state, subscription-acknowledged metadata queue, request-scoped errors, process-shared caches, and transport-safe error wrapping unless
upstream/mainis equivalent; useSOURCE_CONTROL.mdas the source of truth. - Version Control idle-power safeguards retain native-first canonical path identity, exact 15-second, 30-second, or disabled Git status intervals, one-minute, five-minute, or disabled all-remotes intervals, shared lock and power/visibility/activity gating, ignored
.gitchurn, batched ignored-path classification, batched snapshot-wide enrichment, and explicit Fetch. - Version Control checked-out branch labels preserve worktree paths through porcelain-first parsing and old-Git fallbacks; sync and undo target the owning checkout, while checkout and deletion stay disabled for branches owned by an existing worktree; deletion prunes stale registrations whose worktree directory is gone.
- Thread source-control metadata update failures remain visible without clearing unrelated thread errors.
- Mobile EAS owner, project id, and OTA updates URL remain pointed at the same local Expo project used for installable preview builds unless deliberately changed.
- Mobile iOS development signing remains pointed at Apple team
6JGX8M7Z3Lunless deliberately changed. - Source Control default branch detection honors the status-reported default branch before falling back to
mainormaster. - Pending-task edit/submit helpers keep edited queued tasks from being resurrected after deletion/delivery, keep edit-session ownership from racing across reopen/exit, persist unsendable cleared edits instead of sending stale text after restart, and avoid reusing stale queued workspace metadata when a pending task is retargeted.
- Source-control metadata writes are serialized per thread and only the newest enqueued request applies, so stale Git-action results cannot overwrite newer branch/worktree metadata; V2's
thread.metadata.updatehas no expected-branch compare-and-swap. - Desktop and mobile verification retain host-local capacities and request-scoped cross-host racing. Web servers remain unconstrained, while integrated web UI automation uses one desktop slot on its browser host.
- Persisted generation-aware Git ref caching and mutation invalidation, interruption-safe preview listener acknowledgements, listener-specific projections, exact known-server polling cadence, incremental Version Control snapshots, and common-directory fetch deduplication retain their branch ownership unless upstream is equivalent.
- Core projection migrations preserve published ids 33 through 68, ensure lineage before the id-34 root backfill, normalize only exact divergent markers before canonical replay, and register upstream and branch migrations after 68 as listed under Published Database Compatibility. All migrations use
effect_sql_migrations; migration 60 removes the abandoned experimental Magi ledger after restoring any missing canonical Magi rows. - Worktree-local dev state, single-origin browser proxying, Tailscale sharing, and browser-safe port selection remain integrated with the fork's IPv4 desktop/server paths, explicit desktop HMR URL handling, and desktop/mobile runtime coordination.
- Mobile verification uses the Device panel, exact AgentDevice target arguments, deep-link pairing, and host-local leases through cache preparation and teardown. Windows cache misses retain the wrapper's short-path and dependency-order safeguards for ABI-specific assembly; direct Expo emulator builds remain available. Cached iOS simulator clients keep upstream's bundle id and full capabilities; Personal Team local builds keep their matching native/Metro configuration.
- Preview cleanup follows authoritative archive/delete/unarchive and generation-aware shell lifecycle signals, while background mini-player presentation remains independent from the singleton Source Control surface.
- An open pairing page accepts each new pairing link delivered to it and serializes tokens that arrive while an exchange is pending.
- Archive remains distinct from settle and root-only in the default Sidebar and chat header, using upstream's
threadRuntimeCanArchiveeligibility. One process-wide coordinator holds reservations from confirmation through mutation;Archive allcovers the complete settled scope, including paged and pinned-settled rows. Waiters receive completed successes and eligibility skips while failed, cancelled, and unattempted work stays retryable. - Mobile Git checkout failures remain visible and retryable, while interrupt-only outcomes stay silent.
If NotReadableError is reported during preview captures, alert the user to check whether the retired patch on origin/fix/preview-recording-unreadable-start at 9d684819d592 would address it.
These local patches can be removed when upstream provides the equivalent, superseding, or overriding behavior. Retiring a customization removes its entire section and cross-references from this file.
When retiring the local changes, remove the corresponding tests; expect upstream behavior to be tested by upstream incoming tests as well; we do not test or concern ourselves with validating upstream.