projmux 0.15.3 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,97 @@
1
+ # Release
2
+
3
+ This document describes how a projmux version is released. For the npm
4
+ package layout, publish order, and trusted publishing, see
5
+ [npm-distribution.md](npm-distribution.md). For the PR title rules that feed
6
+ the release notes, see [pr-guideline.md](pr-guideline.md).
7
+
8
+ ## Stable releases through release-please
9
+
10
+ `release-please-action` watches `main`, accumulates Conventional Commit
11
+ subjects, and opens or refreshes a "chore(main): release X.Y.Z" PR. That PR
12
+ contains the version bump (`internal/version/version.go` and
13
+ `.release-please-manifest.json`), the `CHANGELOG.md` updates, and the release
14
+ notes.
15
+
16
+ Merging the release PR creates the GitHub Release with auto-generated notes as
17
+ a **draft** (`draft` in `release-please-config.json`).
18
+
19
+ ## Tag creation
20
+
21
+ The same package config sets `force-tag-creation`, so release-please creates
22
+ `refs/tags/vX.Y.Z` during its release pass, before it computes the next release
23
+ PR. That tag creation triggers the tag workflow through the release-please PAT.
24
+
25
+ Do not move tag creation into a post-action workflow step. release-please-action
26
+ creates releases before pull requests, and a delayed tag makes the same run
27
+ treat the just-released commits as unreleased.
28
+
29
+ ## The tag workflow
30
+
31
+ `.github/workflows/release.yml` triggers on the tag push. It runs the shipped
32
+ E2E shard/suite matrix behind the fail-closed `Release E2E Tests` aggregate,
33
+ then builds the linux/darwin × amd64/arm64 matrix and uploads tarballs to the
34
+ drafted release (`gh release upload --clobber`). `Build Release` depends on the
35
+ aggregate rather than on individual shards.
36
+
37
+ Do not add hardcoded notes back to that workflow. release-please owns the
38
+ notes.
39
+
40
+ ## Publish ordering
41
+
42
+ The release becomes visible only in the final `publish-release` job, after
43
+ `publish-npm` succeeds. That ordering is the contract:
44
+
45
+ - Users never see a GitHub release for a version npm cannot install yet.
46
+ - A failed npm publish leaves the release drafted and the workflow red instead
47
+ of shipping a half-published version.
48
+
49
+ See [npm-distribution.md](npm-distribution.md#publish-order) for the npm side
50
+ of that order.
51
+
52
+ ## Release candidates
53
+
54
+ Release candidates are cut **outside** release-please, by the
55
+ `workflow_dispatch`-only `.github/workflows/release-rc.yml`. It takes an
56
+ `X.Y.Z-rc.N` version, drafts a GitHub Release marked `--prerelease` against
57
+ `main` HEAD, then pushes `vX.Y.Z-rc.N` with the release-please PAT, so the same
58
+ `release.yml` builds, uploads, and publishes it.
59
+
60
+ Running it is a deliberate manual act: npm publishes cannot be recalled.
61
+
62
+ ### Do not configure prerelease in release-please
63
+
64
+ Do **not** add `prerelease` or `prerelease-type` to `release-please-config.json`
65
+ to get release candidates. release-please gates the prerelease flag on
66
+ `config.prerelease && (version.preRelease || version.major === 0)`. This
67
+ repository is `0.x`, so the key would stamp *stable* releases as prereleases,
68
+ `releases/latest` would stop resolving, and default-channel updates would go
69
+ silently dead.
70
+
71
+ ### The rc path never touches the manifest
72
+
73
+ The rc path writes exactly one ref: the tag. It never touches
74
+ `.release-please-manifest.json`, so release-please keeps measuring the next
75
+ stable release from the previous *stable* release.
76
+
77
+ release-please picks that boundary by matching the manifest value against a
78
+ tag, with no prerelease filter on that path. An rc holding the manifest slot
79
+ would silently trim the next stable release notes and its `compare/` link.
80
+
81
+ ## Channel branching in release.yml
82
+
83
+ `release.yml` branches on the tag only where the channel is decided:
84
+
85
+ - A prerelease tag publishes npm with `--tag rc` and keeps `--prerelease` when
86
+ the release is undrafted.
87
+ - A stable tag runs exactly as before.
88
+
89
+ Job names, order, and the `publish-npm` → `publish-release` gating are
90
+ identical for both. `test/release_workflow_contract_test.py` executes both step
91
+ scripts against a stable and an rc tag to hold that split.
92
+
93
+ ## Commit subjects
94
+
95
+ Non-Conventional commit subjects on `main` are silently skipped by
96
+ release-please. Keep PR titles strict. Squash merge ensures the PR title is the
97
+ only subject that lands.
@@ -1,7 +1,7 @@
1
1
  # Replacement completion and restorability
2
2
 
3
3
  Three layers of this application hold an execution image, and every consumer of
4
- `make install`, `npm install`, or a provider generation upgrade believes one
4
+ `make install`, `npm install`, or a provider upgrade believes one
5
5
  sentence about all three at once. That sentence is false in a different way on
6
6
  each layer. This document fixes what is guaranteed, what is not, and how a
7
7
  report can be recovered from the tokens the diagnosis prints.
@@ -13,7 +13,7 @@ the `projmux doctor` replacement table:
13
13
  | --- | --- | --- |
14
14
  | `L1` | `installed-executable-image` | the file the installed path publishes |
15
15
  | `L2` | `long-lived-projmux-processes` | every running child of that file |
16
- | `L3` | `provider-sessions-and-generations` | provider sessions and the managed generation pool |
16
+ | `L3` | `provider-sessions-and-generations` | provider sessions, whose app-server lifetime the upstream `codex app-server daemon` owns |
17
17
 
18
18
  `projmux doctor` renders the table on an unfiltered run and under
19
19
  `projmux doctor --section replacement`. It is read-only in the same sense as
@@ -44,22 +44,14 @@ image accepts no new work on any of the three layers*.
44
44
  replace it and is left alone — see *The L2 replacement policy* below. A
45
45
  replacement that has not finished within the drain cutoff is reported as
46
46
  `replacement-cutoff-reached` and is still not ended.
47
- - `L3` cannot enter `draining` without a qualified version pair. Both doors into
48
- a generation switch require a measured receipt for exactly the pair they are
49
- about: managed activation refuses before it writes the journal, and a handover
50
- resume refuses before it drives any effect — the one exception being a
51
- generation nothing is bound to, which the planner has always been allowed to
52
- retire because a receipt proves a thread survives a cross-version resume and
53
- such a generation has no thread to carry. Each refusal names the action that
54
- clears it, and that action is now runnable: `scripts/test-generation-pool-qualification.sh`
55
- measures a declared pair in isolation and
56
- `projmux agent app-server upgrade qualify --receipt <absolute-json>` installs
57
- the receipt it writes where both doors read it. **What is not guaranteed is
58
- the measurement's honesty.** The gate checks that the receipt's coverage
59
- counters can have come from a run — an acceptance claim with no observation
60
- behind it is refused as forged — but a determined forger who writes a coverage
61
- count of one is not contradicted by anything else in the receipt. The gate
62
- makes the receipt state its coverage; it does not attest to it.
47
+ - `L3` is not replaced by an install at all, and this application no longer
48
+ switches an app-server generation: the endpoint lifetime belongs to the
49
+ upstream `codex app-server daemon`, and the private generation pool that once
50
+ owned it — with its upgrade, qualification, and handover commands — is gone.
51
+ What the row still reports is the Registry against provider evidence: a
52
+ `Running` Agent that provider evidence contradicts, and a `Running` Agent that
53
+ nothing can confirm. Absence of both is not evidence of a restore route, so
54
+ restoration stays `unknown`.
63
55
 
64
56
  **Non-Guarantee.** Explicitly outside this contract:
65
57
 
@@ -68,13 +60,14 @@ image accepts no new work on any of the three layers*.
68
60
  `unsupported-platform` with the `unknown` verdict on both axes. This contract
69
61
  does not build a substitute observation for that platform; it states the gap.
70
62
  - The provider's own upstream seamless-upgrade contract. `L3` reasons about the
71
- pool this application manages, never about what the provider promises.
63
+ Registry and the provider evidence it can reach, never about what the provider
64
+ promises.
72
65
  - An externally owned (`unmanaged`) app-server. Its state is not this
73
66
  application's to observe or restore.
74
67
  - Automatic classification of which changes form one invariant. See C-2.
75
68
 
76
69
  **Scope.** In space: one machine's projmux installation, the long-lived
77
- processes it owns, and the generation pool it manages. In time: from the return
70
+ processes it owns, and the provider sessions its Registry records. In time: from the return
78
71
  of one `install` or `update` to the next. **Expiry: a verdict expires when the
79
72
  installed binary's SHA changes.** A table printed before an install describes an
80
73
  installation that no longer exists.
@@ -85,7 +78,7 @@ installation that no longer exists.
85
78
  | --- | --- | --- |
86
79
  | `L1` | this reader's own executable link carries the kernel's `(deleted)` suffix | none for the binary. No copy of the replaced image is retained, and the recovery text the install prints on failure is config convergence, never a rollback. Restorability is `not-restorable` on every supported path. |
87
80
  | `L2` | the whole-fleet vintage census, the newest `install-residue.jsonl` record whose own census observed anything, and the last `install-replacement.json` pass | end and relaunch the residual process through the routes this application already ships. The drain cutoff bounds the wait, and reaching it changes the row rather than the fleet, so a failed replacement leaves every process exactly where a successful one would have found it. |
88
- | `L3` | generation-pool status, its qualification result, and the Running-versus-live-session census | produce a receipt for the pair with `scripts/test-generation-pool-qualification.sh <old> <new> <output-dir>` and install it with `projmux agent app-server upgrade qualify --receipt <absolute-json>`. **The producer is the recovery route**: a pool that can be qualified can be entered, and one that cannot is refused before it enters. The 2026-09-07 measurement — no route back once `draining` was entered without a verdict — held because nothing read a produced receipt; the entry paths now refuse rather than reach that state. |
81
+ | `L3` | the Running-versus-live-session census | there is no projmux-owned generation switch to undo. A `Running` Agent whose provider session is gone is rebound with `projmux agent resume <ref>`, which moves a retired endpoint reference onto the daemon endpoint; a session nothing can confirm has no route this application can establish. |
89
82
 
90
83
  **Enforcement.** `TestDoctorReplacementLayerVerdictsAreFixedByInputCombination`
91
84
  fixes every input combination to its two verdicts and its token.
@@ -93,15 +86,8 @@ fixes every input combination to its two verdicts and its token.
93
86
  to name the evidence that was missing.
94
87
  `TestDoctorReplacementDarwinReportsUnsupportedPlatformForImageAndProcessLayers`
95
88
  holds the platform branch.
96
- `TestDoctorReplacementQualificationMissingMakesGenerationPoolNotRestorable`
97
- holds the `L3` mapping and
98
- `TestDoctorReplacementL3RestorationMovesWhenTheQualificationLaneOpens` holds the
99
- transition across it. For the `L3` guarantee above,
100
- `TestActivateManagedCurrentRefusesEveryUnqualifiedRequestBeforeDraining` and
101
- `TestResumeRefusesAnUnqualifiedPairBeforeAnyHandoverEffect` hold the two doors,
102
- `TestResumeStillRetiresAVacantGenerationWithoutAReceipt` holds the vacancy
103
- exception, and `TestQualificationGateRefusesEvidenceCountersNoObservationBacks`
104
- holds the forgery refusal. For the `L2` guarantee above,
89
+ `TestDoctorReplacementCensusSeparatesRegistryRunningFromLiveProviderSession`
90
+ holds the `L3` census and its mismatch token. For the `L2` guarantee above,
105
91
  `TestBrokerRuntimeDrainsWhenItsOwnImageWasReplaced` holds the vintage entry
106
92
  condition and that a runtime on the installed image is not drained by it,
107
93
  `TestReplacementRolePoliciesMatchTheContractDocument` and
@@ -250,19 +236,14 @@ the token constants in the code equal, in both directions.
250
236
  | `L2` | `install-residue-recorded` | `not-replaced` | `restorable` | no live child was observable, and the newest ledger record whose own census observed anything says that install left residue |
251
237
  | `L2` | `install-residue-clean` | `replaced` | `restorable` | no live child was observable, and the newest ledger record whose own census observed anything says that install left none |
252
238
  | `L2` | `no-observed-processes` | `unknown` | `unknown` | neither the live census nor any ledger record observed anything. Every reading is silent; none says the fleet is clean |
253
- | `L3` | `generation-pool-unobserved` | `unknown` | `unknown` | no pool diagnosis was read |
254
- | `L3` | `registry-running-provider-session-dead` | pool-derived | `not-restorable` | provider evidence contradicts a Registry `Running` Agent |
255
- | `L3` | `qualification-missing` | pool-derived | `not-restorable` | the pool is installed and carries no qualification result. A pool in this state predates the entry gates or had its receipt removed; the row's `pool.action` names the producer that clears it |
256
- | `L3` | `generation-pool-blocked` | pool-derived | `not-restorable` | the pool diagnosis is blocked |
257
- | `L3` | `generation-handover-required` | pool-derived | `restorable` | the pool has a pending operation with a handover route |
258
- | `L3` | `registry-running-session-unobserved` | pool-derived | `unknown` | Running Agents rest on the Registry alone, with no provider handle to check |
259
- | `L3` | `generation-pool-not-installed` | `not-replaced` | `unknown` | no generation journal exists. Absence of a journal is absence of evidence about a restore route, not evidence of one |
260
- | `L3` | `generation-pool-ready` | pool-derived | `restorable` | the pool is installed, qualified, and settled |
261
-
262
- `pool-derived` means the replacement axis follows the pool rather than the
263
- token: `replaced` once a generation is draining, handover-pending, or retired —
264
- because such a generation accepts no new admission, which is exactly the
265
- sentence C-1's Assumption makes — and `not-replaced` otherwise.
239
+ | `L3` | `registry-unobserved` | `unknown` | `unknown` | no Registry was read, so nothing about the provider sessions was observed |
240
+ | `L3` | `registry-running-provider-session-dead` | `not-replaced` | `not-restorable` | provider evidence contradicts a Registry `Running` Agent |
241
+ | `L3` | `registry-running-session-unobserved` | `not-replaced` | `unknown` | Running Agents rest on the Registry alone, with no provider handle to check |
242
+ | `L3` | `provider-sessions-uncontradicted` | `not-replaced` | `unknown` | the Registry was read and no Running Agent is contradicted or unconfirmed. Nothing contradicts the sessions; nothing establishes a restore route for them either |
243
+
244
+ The `L3` replacement axis is `not-replaced` on every reading that reached the
245
+ Registry: an install replaces no provider session, and this application owns no
246
+ app-server generation switch that could replace one.
266
247
 
267
248
  ## Process role vocabulary
268
249
 
@@ -423,15 +404,50 @@ absence is stated by this table's `unsupported-platform` row.
423
404
 
424
405
  `projmux internal install-replace` runs as a step of `make install`, immediately
425
406
  before the residue census so the census measures the fleet the pass left. It
426
- takes the census, splits the residual processes by disposition, dials the
427
- published broker runtime for this state domain, waits a bounded moment, and
407
+ takes the census, splits the residual processes by disposition, discovers the
408
+ published generation-scoped broker endpoints in this state domain, and dials
409
+ those whose PID hints select this executable's residual processes. PID is only
410
+ a selection hint; the existing ownership and credential checks still authorize
411
+ the connection. The legacy default endpoint key locates the directory and is
412
+ not assumed to be the published runtime. The pass waits a bounded moment and
428
413
  writes `install-replacement.json`.
429
414
 
415
+ A welcome from a current-image runtime cannot stand in for a residual target.
416
+ A drain/closing refusal confirms reachability; if the exact socket disappears
417
+ during a failed request, its disappearance proves that target is already gone. The runtime writes `drain-required`
418
+ before an idle drain closes its connection. Completion follows the original
419
+ socket identities and ownership-checked runtime IDs captured for accepted
420
+ targets, so an absent unrelated socket or a successor published at the same
421
+ path cannot distort the drained count. Runtime IDs distinguish successors even
422
+ when the filesystem reuses the original socket's inode. An unreadable or
423
+ untrusted successor record alone does not prove completion.
424
+
430
425
  It **starts nothing** — a replacement pass that launched what it was sent to
431
426
  replace would leave more behind than it found, so it dials and never ensures.
432
- It **signals nothing**: the only request it makes is a socket handshake. And it
433
- never fails an install: it runs after the install has already succeeded, and
434
- everything it could not do is on the record it writes.
427
+ It **signals nothing**: the only request it makes is a socket handshake.
428
+ An unreachable target makes `internal install-replace` exit 1. `make install`
429
+ still runs the residue census, then fails because replacement did not finish.
430
+ The binary publication and config convergence have already completed; this
431
+ failure does not roll them back. Accepted drains that are still carrying work
432
+ remain successful, as do complete, no-target, and unsupported-platform passes.
433
+ Their output is unchanged.
434
+
435
+ On an unreachable result, stderr retains the count and refusal and adds the
436
+ remaining drain targets' role, pid, and mapped executable revision, followed by
437
+ the impact and next action. Targets are rechecked after the refusal; a vanished
438
+ or unreadable target is not invented, and an unavailable build revision is
439
+ `unknown`. These identities are transient terminal output only:
440
+ `install-replacement.json` and the residue ledger retain counts and tokens.
441
+ The replacement record adds `failureStage` on an unsuccessful request:
442
+ `discovery` means target selection, record, or socket validation failed;
443
+ `dial` means the local socket connection failed; `handshake` means the greeting
444
+ or response failed or did not accept a drain. `refusal` keeps its existing broker
445
+ token, when one is available. A current-image welcome has no refusal token.
446
+ Successful, pending, no-target, and unsupported records omit `failureStage`.
447
+ The diagnostic states that later Codex Agents may lack control while the old
448
+ broker remains. Let existing work finish, check that the named processes exit
449
+ naturally, and retry `make install`. If they remain, the operator reviews the
450
+ targets before deciding on termination; the install does not signal them.
435
451
 
436
452
  Its outcome vocabulary is closed, and it reaches the `L2` row as
437
453
  `replacement.outcome`:
@@ -567,13 +583,6 @@ reconstruction.
567
583
  | `replacement.drained` | `L2` | counter |
568
584
  | `replacement.reported` | `L2` | counter |
569
585
  | `registry.observed` | `L3` | `true` / `false` |
570
- | `pool.status` | `L3` | pool status token, or `unobserved` when no pool diagnosis was read |
571
- | `pool.reason` | `L3` | pool reason token |
572
- | `pool.action` | `L3` | pool action token |
573
- | `pool.generations.live` | `L3` | counter |
574
- | `pool.generations.draining` | `L3` | counter |
575
- | `qualification.verdict` | `L3` | qualification verdict token |
576
- | `qualification.reason` | `L3` | qualification reason token |
577
586
  | `sessions.running` | `L3` | counter |
578
587
  | `sessions.live` | `L3` | counter |
579
588
  | `sessions.dead` | `L3` | counter |
@@ -591,6 +600,5 @@ The replacement policy above is the one thing on this page that acts, and it
591
600
  acts through a strictly narrower door. `TestReplacementPathEndsNoProcess` holds
592
601
  the same guard over the policy table and the install pass: they carry no
593
602
  termination, no signalling, and no restart either. **The whole of the action is
594
- a socket handshake, and the runtime decides.** `L1` atomicity and the `L3`
595
- qualification gate are separate work on separate doors, and no change on this
596
- path may alter their behavior.
603
+ a socket handshake, and the runtime decides.** `L1` atomicity is separate work
604
+ on a separate door, and no change on this path may alter its behavior.
@@ -3,8 +3,8 @@
3
3
  Phase 0 provides the read-only attribution contract consumed by the Resource
4
4
  Inspector shipped in Phase 1. `projmux resources`, the client-scoped
5
5
  `resource-inspector` popup, the statusbar range, and `Resources:Open` all keep
6
- the snapshot in memory only for the interactive process lifetime; it remains
7
- outside Session State.
6
+ the snapshot in memory only for the interactive process lifetime; it is never
7
+ persisted.
8
8
 
9
9
  ## Identity and inventory
10
10
 
@@ -1,80 +1,39 @@
1
- # Session Restore
1
+ # Project Startup
2
2
 
3
- Session snapshots are explicit desired-state inputs for one Project. They are
4
- not tmux replay scripts and they are not Registry backups. Snapshot save keeps
5
- the existing v1 schema and storage behavior. In snapshot save's runtime id
6
- duplicate check, a Window or Pane recorded as `MissingRuntime`/`RuntimeUnbound`
7
- does not claim its retained runtime id, so a tmux id reused after a server
8
- restart does not refuse the save; two live Windows or two live Panes sharing an
9
- id are still refused.
3
+ A closed Project starts from its Registry desired state. projmux keeps no other
4
+ saved Project state: the Registry (`registry.json`) is the only input.
10
5
 
11
- ```sh
12
- projmux get snapshots [--session <snapshot-session>]
13
- projmux create snapshot
14
- projmux restore snapshot --session <snapshot-session> [--project <ref> | -p <ref>] --dry-run
15
- projmux restore snapshot --session <snapshot-session> [--project <ref> | -p <ref>] --yes [--client /dev/pts/N]
16
- projmux delete snapshot --session <snapshot-session>
17
- ```
18
-
19
- Restore requires an exact snapshot and an exact, closed target Project. The
20
- dry-run validates both inputs and prints replacement, deletion, preserved-UID,
21
- and lost-conversation-pointer counts with zero Registry, tmux, and snapshot writes.
22
- Snapshot Project/Window/Pane metadata is checked against the exact target owner
23
- chain. A UID held by another root, a cross-kind UID reuse, a Project mismatch,
24
- or conflicting owner metadata refuses before commit. The ordinary Project-open
25
- trust authorization must also approve the exact target root before the Registry
26
- transaction begins.
27
-
28
- After `--yes`, one atomic Registry transaction replaces only the target
29
- Project's descendant Window/Pane/Agent graph and its descendant name
30
- reservations. The Project UID, root, trust metadata, unrelated Projects,
31
- ControlSessions, and source snapshot bytes are preserved. Metadata-bearing
32
- snapshots reuse their exact target-subtree UIDs and preserve a surviving
33
- final-v2 Agent or shell anchor plus a surviving direct default shell.
34
- Metadata-free legacy snapshots reuse target descendants positionally, select
35
- the first valid Window-local Pane as the role-agnostic anchor, select the first
36
- direct shell as the optional default, and mint identities only for missing
37
- items. An Agent-only Window is valid with an empty default. Repeating the same
38
- projection is a Registry zero-diff.
39
-
40
- Resource metadata records the Registry schema that produced it. A v3 snapshot
41
- is projected through the same root-wide same-kind duplicate-group and
42
- destination-closure rule as Registry migration: affected resources receive
43
- their exact UID names, unique names outside the closure are preserved, and no
44
- numeric suffix is minted. A current-v4 snapshot containing a root-wide
45
- same-kind collision is rejected as damaged before trust authorization,
46
- Registry/tmux/provider mutation, or any snapshot write.
47
-
48
- The committed Registry is then converged by the ordinary Project materializer.
49
- For a restored offline Agent-anchor Window, snapshot materialization visibly plans a
50
- lazy default shell, creates the Window from that shell, and stages the Agent on
51
- its retained anchor Pane UID. A successful repeat writes neither Registry nor
52
- topology. Snapshot Agent recipes use the canonical provider launch/resume path,
53
- including their existing fresh-conversation fallback. Stored startup
54
- commands are not directly executed by snapshot restore. A runtime item refusal
55
- does not roll the Registry back: desired state and the source snapshot remain
56
- available for another `Continue project`, and the refusal is reported as an
57
- item notice. If an explicit client is supplied, the final observable step is
58
- `switch-client -c` to the Project's declared session even when the background
59
- continuation has no inherited `TMUX` variable.
60
-
61
- ## Project startup
62
-
63
- A closed Project has exactly two actions:
6
+ A registered closed Project has exactly two actions. A root that is not a
7
+ registered Project is not asked: it opens fresh, which registers it.
64
8
 
65
9
  - `Continue project` opens the current Registry desired state with the ordinary
66
10
  materializer. A retained graph keeps its Project, Window, Pane, and Agent
67
11
  UIDs. A zero-Window Project keeps its Project UID and atomically receives one
68
12
  new canonical Window and shell UID before materialization.
69
- - `Recreate Project` atomically replaces the same-root graph with a new Project
13
+ - `Clear layout and open` clears the Project's saved Window and Agent layout
14
+ and opens it again; the folder, its files, `.projmux/config.toml`, and trust
15
+ stay. It atomically replaces the same-root graph with a new Project
70
16
  UID and one new canonical Window/shell UID chain, after a confirmation naming
71
17
  the exact old Project UID and its Window/Pane/Agent counts. Declining returns
72
18
  to the startup rows and writes nothing. It does not archive or retain the old
73
- generation.
19
+ generation. Its new Window's first Pane follows the saved launch default
20
+ (`tmux-ai-split-mode`), exactly as a Window created from the UI does: the
21
+ choice is made before anything is cleared. The first open of an unregistered
22
+ root, which resolves to the same fresh start, behaves the same.
74
23
 
75
24
  Esc/cancel returns to Projects; it is not an action row. Picker failure falls
76
25
  back to the non-destructive `Continue project` action.
77
26
 
27
+ `Continue project` needs a registered Project. On a root that is not a
28
+ registered Project it refuses with zero Registry writes and points to
29
+ `Clear layout and open` (`continue project unavailable: <root> is not a registered
30
+ Project; choose Clear layout and open`). It never falls back to Fresh on its own.
31
+
32
+ projmux does not save Project state on its own at quit or on a timer.
33
+ `projmux quit` offers only `Quit projmux` and `Cancel`. The hidden `internal tmux autosave-session-state`
34
+ route is kept only so status lines rendered by older installs keep working, and
35
+ it does nothing.
36
+
78
37
  Continue resumes an Agent's exact recorded conversation after interrupted,
79
38
  killed, abnormal, unknown, or unrecorded termination. Intentional and normal
80
39
  termination remain excluded. A recorded receipt must agree on the Agent and
@@ -87,8 +46,8 @@ A missing, blank, malformed, or mismatched conversation ref, a disabled provider
87
46
  a missing workspace, or a resume preparation failure skips the Agent with a
88
47
  reason. Continue never substitutes a new conversation. Shells and other
89
48
  recoverable Agents still converge; an unrecoverable Agent that is itself a
90
- Window's required anchor keeps the existing Window refusal. Explicit snapshot
91
- restore and `agent resume` retain their separate authority.
49
+ Window's required anchor keeps the existing Window refusal. `agent resume`
50
+ retains its separate authority.
92
51
 
93
52
  After Continue commits, its startup summary shows the resumed and skipped Agent
94
53
  totals and `projmux diagnostics log --component topology`. The same counts are
@@ -110,21 +69,28 @@ errors. Dry-run writes no execution event; failed or rolled-back execution
110
69
  records an error with zero committed counts. Journal and display failures are
111
70
  best effort and never change the topology result.
112
71
 
113
- `Recreate Project` never deletes or overwrites autosave or named snapshot files. It
114
- preserves the root, Git/worktrees, trust decision, and all unrelated Registry
72
+ `Clear layout and open` preserves the root, Git/worktrees, trust decision, and all unrelated Registry
115
73
  graphs while changing the Project identity. A rejected commit retains the
116
- exact old Registry preimage. Repeating `Recreate Project` replaces identity again;
74
+ exact old Registry preimage. Repeating `Clear layout and open` replaces identity again;
117
75
  each successful result has exactly one Project claiming the root.
118
76
 
119
- ## Snapshot contents and diagnostics
120
-
121
- Snapshots keep window names, pane cwd/label/title, shell/startup/agent recipes,
122
- AI topic ownership, and provider resume metadata when available. Resume health
123
- in preview is `available`, `stale`, or `unavailable`; confidence derives from
124
- the stored source. Snapshot inspection never reads provider transcript or
125
- conversation database content.
126
-
127
- An approved projection restore records one safe Session State outcome with
128
- aggregate Window/Pane/recipe counts and source `manual`. Paths, commands,
129
- snapshot content, and provider conversation identifiers are never included.
130
- Dry-run remains read-only and records no mutation outcome.
77
+ The saved launch default is used only when the open carries the exact client
78
+ that pressed the row. The order is:
79
+
80
+ 1. Ask. A picker mode (`selective`, the unset default, or `resume`) opens its
81
+ picker on the Pane the row was pressed in, before the old layout is cleared
82
+ or the new Session exists; a provider mode and `shell` are already the
83
+ answer and open nothing.
84
+ 2. Clear the layout and create the new Session with its one shell Pane.
85
+ 3. Fill it: an Agent answer is created in that Window first, and the shell is
86
+ then removed through the canonical Pane delete.
87
+ 4. Move the pressing client onto the finished Session.
88
+
89
+ The client therefore never sees a shell Pane that is about to be replaced.
90
+ Closing the picker without a choice does not stop the open: the Session opens
91
+ with its shell Pane and nothing is said. An open without the exact client -- a
92
+ detached `start project`, a scripted open -- asks nothing, in every mode, and
93
+ keeps the plain shell Pane. A question that cannot be asked, or an answer that
94
+ cannot be filled in, costs one line on that client after the move and keeps the
95
+ shell Pane; the Project stays open either way. `Continue project` and the
96
+ `start project`/`open project` verbs never use the saved launch default.
@@ -58,7 +58,7 @@ step, never a silent no-op.
58
58
  Project`, which forwards to the canonical `create project --root` route for
59
59
  that one exact path, and `Unpin candidate`, which removes the preference and
60
60
  leaves the directory alone. Nothing here adopts a path automatically.
61
- - `Project Sidebar [View]` — holds the two Projects sidebar policies:
61
+ - `Project Sidebar [View]` — holds the Projects sidebar policy:
62
62
  `Runtime diagnostics [Choice]` chooses `When needed` (the read-time default
63
63
  with nothing saved) or `Always` for the sidebar's Runtime row. `When needed`
64
64
  keeps the row for a refused runtime class or for an observation that could
@@ -68,22 +68,17 @@ step, never a silent no-op.
68
68
  Recent Windows links are unchanged, and `projmux runtime diagnostics` and
69
69
  `get runtime` never read it. An unrecognized saved value applies the default
70
70
  without writing and shows an invalid source.
71
- `Closed Project startup` shows exactly two actions when no preference file
72
- exists and reports `Continue project / Recreate Project - default` (`이어서 열기 /
73
- Project 다시 만들기 - 기본값` in ko-KR). Saved `on` keeps those choices and reports
74
- `Continue project / Recreate Project - on - saved`. Saved `off` reports
75
- `Continue project - off - saved`, skips the picker, and retains the
76
- registered-Continue/unregistered-Fresh automatic
77
- adjudication. Reading the row or opening/cancelling the picker never writes
78
- the default or changes saved preference bytes/mtime.
71
+ The closed-Project startup screen has no setting: a registered closed
72
+ Project always gets it, and a root that is not a registered Project never
73
+ does. Its two rows are `Continue project` and `Clear layout and open`
74
+ (`이어서 열기` / `구성 비우고 새로 열기` in ko-KR).
79
75
  `Continue project` materializes the Project's current Registry desired state
80
- and then moves the client. `Recreate Project` confirms the exact old Project
76
+ and then moves the client. `Clear layout and open` confirms the exact old Project
81
77
  UID and per-kind counts before writing anything,
82
78
  atomically replaces the old Project graph with a new Project UID and a new
83
79
  canonical Window/shell UID pair, and leaves exactly one same-root claimant
84
80
  before ordinary materialization. Esc returns to Projects. Neither action
85
- deletes or rewrites snapshots, root, Git, or worktree data. The saved file
86
- keeps its `sidebar-startup-picker` spelling.
81
+ deletes or rewrites the folder, its files, Git, worktree data, or trust.
87
82
  - **AI** — `AI` is a product category, never an addressable resource.
88
83
  - `Default launch target [Choice]` — an Agent Provider, a Shell Pane, or
89
84
  choose-at-launch. It is a keybinding/picker preference and does not weaken
@@ -147,10 +142,10 @@ step, never a silent no-op.
147
142
  HUD`, `Working directory` and `Git` are component Views because each owns a
148
143
  `Visible` Toggle plus an icon `Choice`; cwd/Git icon `off` removes only the
149
144
  icon and leaves the text segment visible. `Agent Usage HUD` is a component
150
- View with `Visible`, then Claude/Codex/Antigravity provider Views in the
145
+ View with `Visible`, then Claude/Codex provider Views in the
151
146
  usage-supported catalog order. Each provider owns `Visible` plus only its
152
- explicit HUD windows: Claude/Codex own `5h` and `Weekly`; Antigravity owns
153
- `Weekly` only. Parent off states gate effective visibility without rewriting
147
+ explicit HUD windows, `5h` and `Weekly`; Antigravity has no usage source and
148
+ no provider View. Parent off states gate effective visibility without rewriting
154
149
  saved child values. Provider/window rows show saved, effective, and source.
155
150
  `Project`, `Clock` and `Settings launcher` are direct visibility Toggles. These global
156
151
  presentation values default on except Codex `5h`, which defaults off to
@@ -163,8 +158,6 @@ step, never a silent no-op.
163
158
  presentation-only and do not disable their underlying producers, cache,
164
159
  backoff, explicit table/JSON command, or cached popup.
165
160
  - `Language / Locale [Choice]` and `Agent attention badge style [Choice]`.
166
- - **Snapshots** — the visible noun is the Snapshot resource. `session-state`
167
- remains the config/route spelling and appears only as source detail.
168
161
  - **Keybindings** — `Launch & popups`, `Agent & Pane launch`,
169
162
  `Pane & Window navigation`, `Sidebar & picker actions` (nested by surface:
170
163
  Project Sidebar, Session Picker, Notification Sidebar, Settings), and
@@ -221,7 +214,6 @@ step, never a silent no-op.
221
214
  the approve/revoke actions) and `Project hooks [View]` (`Session lifecycle`
222
215
  plus `After notification queued`, with the same per-event Views the global
223
216
  scope uses, extended by a trust state row).
224
- - **Snapshots** — the auto-save override and the saved snapshots.
225
217
 
226
218
  Without an actionable project context the Project surface renders a single
227
219
  passive guidance row rather than repeating a disabled reason per row.
@@ -236,14 +228,14 @@ Confirm and Action rows included, so `Quit Projmux` and `Reset theme` are
236
228
  focused and never fired. The list is scope-pure: a Global query never returns
237
229
  Project nodes and a Project query never returns Global ones, and with no project
238
230
  context the Project tab returns nothing. User-data collection items -- an
239
- individual discovery root, pinned Project, candidate or snapshot -- are data
231
+ individual discovery root, pinned Project or candidate -- are data
240
232
  rather than settings, so results stop at their parent View and at the
241
233
  collection-level controls. Inside a View the query still filters that View's
242
234
  rows, and category rows carry their members' search text so search crosses
243
235
  categories.
244
236
 
245
237
  The result rows are built from the catalog alone: no Registry, tmux,
246
- filesystem or snapshot read participates, because a result is a destination
238
+ or filesystem read participates, because a result is a destination
247
239
  rather than a rendered value. Where that leaves a row unnameable -- a saved
248
240
  user path, a command whose row spelling depends on whether a command is
249
241
  already stored, a read-only state row that stands for several rendered lines --
@@ -254,7 +246,7 @@ more: the View opens, unfocused, and nothing errors.
254
246
  ## Vocabulary and compatibility
255
247
 
256
248
  Visible nouns follow the shared resource vocabulary: `Project`, `Window`,
257
- `Pane`, `Agent`, `Provider`, `Notification`, `Snapshot`, with `AI` as a category
249
+ `Pane`, `Agent`, `Provider`, `Notification`, with `AI` as a category
258
250
  and `Session` as the runtime projection. The Agent Usage HUD is a presentation
259
251
  of what the canonical `agent usage` command provides; there is no addressable
260
252
  `Usage` resource, and Settings never spells usage as a readable resource kind.
@@ -302,7 +294,7 @@ result replaces it. Typed validation and staged apply failures stay in the popup
302
294
  instead of being visible only on stdout/stderr.
303
295
 
304
296
  The generic feedback inventory deliberately excludes Welcome, Quit, read-only
305
- hook/effective/notification diagnostics, Snapshot preview, and key
297
+ hook/effective/notification diagnostics, and key
306
298
  capture/probe/diagnostic bodies. Those flows own a viewer, confirmation, or
307
299
  multi-step output surface; only an actual Settings write at their boundary is
308
300
  eligible for transient mutation feedback.