detached 80cdd12

docs: remove client contract plans

commit
80cdd12f76fdcfa29a7536fc5a70f56e61425c8f
author
brice · · 2026-09-23 18:57 UTC
parent
eabbcfe
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
2 files · +0 -221
D docs/plans/client-preferences-contract.md +0 -83
@@ -1,83 +0,0 @@
1-# Client preferences contract (P4.1, design-only)
2-
3-Status: proposed. No production edits accompany this document. Any scope
4-change below requires explicit approval before implementation.
5-
6-## Ground rules
7-
8-- Shared pane VT defaults stay daemon-arbitrated. `applyHostThemeLocked`
9- (`internal/usecase/daemon/theme_resolve.go`) fans the resolved theme out to
10- every pane VT in every tab of the session; per-attachment values never reach
11- pane screens.
12-- Picker sort scope is unchanged: `togglePickerSort` flips the daemon-wide
13- `pickerSort` atomic (`internal/usecase/daemon/picker.go:40`); it is session
14- chrome, not an attachment preference.
15-- Bindings stay daemon-admitted. `keys.BuildBindingEntries` +
16- `keys.Router.RouteWithHandler` (`internal/usecase/keys`) remain the single
17- admission path; no duplicate client-side key router is proposed.
18-- Config source of truth stays the daemon-loaded file parsed by
19- `internal/adapters/config` into `domain.Config`, published atomically via
20- `Daemon.ApplyConfig` snapshots (`internal/usecase/daemon/config.go`).
21-
22-## Classes
23-
24-- **Client-local interaction**: affects only the local terminal device, never
25- pane VT state, session state, or another attachment. The only class eligible
26- for client-side ownership without a P5 lease.
27-- **Attachment-rendering**: affects presentation of one attachment, executed by
28- the serving daemon from per-attachment state. Shared session/pane state
29- untouched.
30-- **Shared application-visible**: affects panes, sessions, or all attachments.
31- Daemon-owned; not candidates for client ownership in this plan.
32-
33-## Field classification
34-
35-All keys are the existing `internal/adapters/config` keys. "Two-attachment
36-result" describes two simultaneous attachments A and B on the same session
37-with different local wishes.
38-
39-| Key | Class | Source / default / reload / validation | Two-attachment result |
40-|---|---|---|---|
41-| `theme`, `theme.palette`, `theme.accent` | Shared | Daemon config file; defaults `auto`/`on`/`auto` (`domain.Defaults`); atomic reload via `storeThemeConfig`, reapplied with `reapplyThemeAllSessions`; invalid values warn and keep prior (`config_test.go`) | Single effective theme per session (`effectiveThemeForConfig`, `resolveAppliedTheme`); per-attachment client theme is only an input to arbitration, never applied directly to panes. **Preserved as-is.** |
42-| `scrollback.*` | Shared | Daemon config; defaults 50 MiB / 10k lines; `Scrollback.Valid()` falls back to defaults with a warning | Per-pane retention policy; identical for every viewer. **Preserved as-is.** |
43-| `snapshot.*` | Shared | Daemon config; allowlist snapshot via `restoreProcessAllowlistFromConfig` | Session restore; identical for every viewer. **Preserved as-is.** |
44-| `floating.command`, `floating.width`, `floating.height` | Shared | Daemon config; defaults 80x80; stored via `floatingConfig` | Geometry arbitration is daemon-owned (`calculateContentFloatingGeometry`). **Preserved as-is.** |
45-| `bar.top-right`, `bar.bottom-right`, `bar.interval` | Shared | Daemon config; defaults empty/empty/5s, clamped to `MinBarInterval` (1s) | Daemon-host execution, per-session outputs. Governed by the status contract; **no client execution proposed here.** |
46-| `code.*`, `binding.*` | Shared | Daemon config; `buildCodeOverrides` + `BuildBindingEntries`, warn-and-keep on invalid | Daemon-admitted palette commands and key bindings. **Single router preserved.** |
47-| `copy.word-separators`, `copy.reduce-motion` | Shared (daemon-executed attached interaction) | Daemon config; defaults ` -_@` / false; `currentCopyConfig` snapshot | Copy mode stays daemon-owned per plan non-goals. Selection documents (`scopy.NewDocument`) and scroll animation execute in the daemon. **Not candidates; moving them requires the P5 lease.** |
48-| `nav.overflow-tabs`, `nav.overflow-sessions` | Shared (attached-action behavior) | Daemon config; defaults false; `currentNavConfig` | Directional navigation is daemon-admitted (`pane_actions.go:465`). **Not candidates.** |
49-| `tabs.terminal-title` | Attachment-rendering | Daemon config; default true; `currentTabsConfig` snapshot read at status composition (`status.go:303,334`, `picker.go:96`) | Label-only; per-attachment divergence is safe in principle, but no per-attachment source exists today. **Proposed future field (example): read from attachment claim, defaulting to daemon config. Requires approval; not implemented here.** |
50-| `palette.anchor` | Attachment-rendering | Daemon config; default center; `currentPaletteConfig` read at render (`render.go:443`) | Placement-only; same future-field shape as above. **Requires approval; not implemented here.** |
51-
52-## Proposed fields and examples
53-
54-No new configuration keys are introduced by this contract. The only proposed
55-shape, pending approval, is: an attachment-rendering value resolves as
56-*attachment-claimed value if present, else daemon config*, read at composition
57-time from existing snapshots, never written to `domain.Config` or persisted.
58-`tabs.terminal-title` and `palette.anchor` are the worked examples above.
59-
60-Explicitly out of scope: client-local clipboard policy, client-side theme
61-editing, per-attachment scrollback, per-attachment bindings, and any new
62-`domain.Config` field. Each would need its own approved contract.
63-
64-## Tests mapped (to be added only after approval)
65-
66-- `TestAttachmentRenderingPreferenceFallsBackToDaemonConfig` (daemon):
67- attachment without claim renders with daemon `palette.anchor` /
68- `tabs.terminal-title`.
69-- `TestAttachmentRenderingPreferenceClaimWins` (daemon): claimed value used,
70- shared pane VT colors byte-identical to unclaimed control.
71-- `TestTwoAttachmentsIndependentRenderingPrefs` (daemon): A and B on one
72- session render different anchors/titles; pane screens identical.
73-- `TestInvalidRenderingPreferenceWarnsAndKeepsPrior` (config + daemon):
74- warn-and-continue path mirrors existing theme/scrollback tests.
75-- Existing preserved-behavior pins: `TestDefaults*`, `TestParse*` in
76- `internal/adapters/config`, `config_test.go` snapshot tests,
77- `picker_test.go` sort tests, `theme_integration_test.go`.
78-
79-## Stop condition
80-
81-P4.1 ends at this document. No preference scope change, no `domain.Config`
82-field, no wire field, and no client ownership move is authorized by this
83-plan alone.
@@ -1,83 +0,0 @@
1-# Client preferences contract (P4.1, design-only)
2-
3-Status: proposed. No production edits accompany this document. Any scope
4-change below requires explicit approval before implementation.
5-
6-## Ground rules
7-
8-- Shared pane VT defaults stay daemon-arbitrated. `applyHostThemeLocked`
9- (`internal/usecase/daemon/theme_resolve.go`) fans the resolved theme out to
10- every pane VT in every tab of the session; per-attachment values never reach
11- pane screens.
12-- Picker sort scope is unchanged: `togglePickerSort` flips the daemon-wide
13- `pickerSort` atomic (`internal/usecase/daemon/picker.go:40`); it is session
14- chrome, not an attachment preference.
15-- Bindings stay daemon-admitted. `keys.BuildBindingEntries` +
16- `keys.Router.RouteWithHandler` (`internal/usecase/keys`) remain the single
17- admission path; no duplicate client-side key router is proposed.
18-- Config source of truth stays the daemon-loaded file parsed by
19- `internal/adapters/config` into `domain.Config`, published atomically via
20- `Daemon.ApplyConfig` snapshots (`internal/usecase/daemon/config.go`).
21-
22-## Classes
23-
24-- **Client-local interaction**: affects only the local terminal device, never
25- pane VT state, session state, or another attachment. The only class eligible
26- for client-side ownership without a P5 lease.
27-- **Attachment-rendering**: affects presentation of one attachment, executed by
28- the serving daemon from per-attachment state. Shared session/pane state
29- untouched.
30-- **Shared application-visible**: affects panes, sessions, or all attachments.
31- Daemon-owned; not candidates for client ownership in this plan.
32-
33-## Field classification
34-
35-All keys are the existing `internal/adapters/config` keys. "Two-attachment
36-result" describes two simultaneous attachments A and B on the same session
37-with different local wishes.
38-
39-| Key | Class | Source / default / reload / validation | Two-attachment result |
40-|---|---|---|---|
41-| `theme`, `theme.palette`, `theme.accent` | Shared | Daemon config file; defaults `auto`/`on`/`auto` (`domain.Defaults`); atomic reload via `storeThemeConfig`, reapplied with `reapplyThemeAllSessions`; invalid values warn and keep prior (`config_test.go`) | Single effective theme per session (`effectiveThemeForConfig`, `resolveAppliedTheme`); per-attachment client theme is only an input to arbitration, never applied directly to panes. **Preserved as-is.** |
42-| `scrollback.*` | Shared | Daemon config; defaults 50 MiB / 10k lines; `Scrollback.Valid()` falls back to defaults with a warning | Per-pane retention policy; identical for every viewer. **Preserved as-is.** |
43-| `snapshot.*` | Shared | Daemon config; allowlist snapshot via `restoreProcessAllowlistFromConfig` | Session restore; identical for every viewer. **Preserved as-is.** |
44-| `floating.command`, `floating.width`, `floating.height` | Shared | Daemon config; defaults 80x80; stored via `floatingConfig` | Geometry arbitration is daemon-owned (`calculateContentFloatingGeometry`). **Preserved as-is.** |
45-| `bar.top-right`, `bar.bottom-right`, `bar.interval` | Shared | Daemon config; defaults empty/empty/5s, clamped to `MinBarInterval` (1s) | Daemon-host execution, per-session outputs. Governed by the status contract; **no client execution proposed here.** |
46-| `code.*`, `binding.*` | Shared | Daemon config; `buildCodeOverrides` + `BuildBindingEntries`, warn-and-keep on invalid | Daemon-admitted palette commands and key bindings. **Single router preserved.** |
47-| `copy.word-separators`, `copy.reduce-motion` | Shared (daemon-executed attached interaction) | Daemon config; defaults ` -_@` / false; `currentCopyConfig` snapshot | Copy mode stays daemon-owned per plan non-goals. Selection documents (`scopy.NewDocument`) and scroll animation execute in the daemon. **Not candidates; moving them requires the P5 lease.** |
48-| `nav.overflow-tabs`, `nav.overflow-sessions` | Shared (attached-action behavior) | Daemon config; defaults false; `currentNavConfig` | Directional navigation is daemon-admitted (`pane_actions.go:465`). **Not candidates.** |
49-| `tabs.terminal-title` | Attachment-rendering | Daemon config; default true; `currentTabsConfig` snapshot read at status composition (`status.go:303,334`, `picker.go:96`) | Label-only; per-attachment divergence is safe in principle, but no per-attachment source exists today. **Proposed future field (example): read from attachment claim, defaulting to daemon config. Requires approval; not implemented here.** |
50-| `palette.anchor` | Attachment-rendering | Daemon config; default center; `currentPaletteConfig` read at render (`render.go:443`) | Placement-only; same future-field shape as above. **Requires approval; not implemented here.** |
51-
52-## Proposed fields and examples
53-
54-No new configuration keys are introduced by this contract. The only proposed
55-shape, pending approval, is: an attachment-rendering value resolves as
56-*attachment-claimed value if present, else daemon config*, read at composition
57-time from existing snapshots, never written to `domain.Config` or persisted.
58-`tabs.terminal-title` and `palette.anchor` are the worked examples above.
59-
60-Explicitly out of scope: client-local clipboard policy, client-side theme
61-editing, per-attachment scrollback, per-attachment bindings, and any new
62-`domain.Config` field. Each would need its own approved contract.
63-
64-## Tests mapped (to be added only after approval)
65-
66-- `TestAttachmentRenderingPreferenceFallsBackToDaemonConfig` (daemon):
67- attachment without claim renders with daemon `palette.anchor` /
68- `tabs.terminal-title`.
69-- `TestAttachmentRenderingPreferenceClaimWins` (daemon): claimed value used,
70- shared pane VT colors byte-identical to unclaimed control.
71-- `TestTwoAttachmentsIndependentRenderingPrefs` (daemon): A and B on one
72- session render different anchors/titles; pane screens identical.
73-- `TestInvalidRenderingPreferenceWarnsAndKeepsPrior` (config + daemon):
74- warn-and-continue path mirrors existing theme/scrollback tests.
75-- Existing preserved-behavior pins: `TestDefaults*`, `TestParse*` in
76- `internal/adapters/config`, `config_test.go` snapshot tests,
77- `picker_test.go` sort tests, `theme_integration_test.go`.
78-
79-## Stop condition
80-
81-P4.1 ends at this document. No preference scope change, no `domain.Config`
82-field, no wire field, and no client ownership move is authorized by this
83-plan alone.
D docs/plans/client-status-contract.md +0 -138
@@ -1,138 +0,0 @@
1-# Client status contract (P4.2, design-only)
2-
3-Status: proposed. No production edits accompany this document. No provider
4-implementation is authorized by this plan alone; a runnable task breakdown
5-follows only after contract approval.
6-
7-## Current daemon-host behavior (preserved)
8-
9-- Commands come only from the daemon-loaded config: `bar.top-right`,
10- `bar.bottom-right`, `bar.interval` (defaults empty/empty/5s,
11- `internal/adapters/config`, `domain.Defaults`).
12-- Execution is daemon-host via `barScriptRunner.run`
13- (`internal/usecase/daemon/bar_script.go`) with `ports.ShellCommandRunner`:
14- timeout 1s (`barScriptTimeout`), stdout cap 1024, stderr cap 512, display
15- cap 256, first-line + `stripANSIAndControls` sanitization, warn-once
16- failure dedup (`lastFailure`).
17-- Scheduling is per-session in `bar_refresh.go`: poller on the daemon clock
18- with `MinBarInterval` (1s) floor, `running`/`pending` coalescing, context
19- change (`lastContext`) forces refresh, `reload` channel wakes the poller on
20- config change, `refreshBarScriptsAllSessions` applies new commands
21- immediately.
22-- Scripts observe daemon-side context only: `VEV_ANCHOR`, `VEV_SESSION`,
23- `VEV_TAB`, `VEV_PANE`, `VEV_PANE_CWD`, `VEV_COLS`.
24-- Composition reads snapshots at paint time: `barScriptSnapshot(sess)` feeds
25- `barState.topRight`/`bottomRight` alongside `status`, `statusFeedback`,
26- `mru`/`rankedRecent`, `attentionFrame` (`status.go:161,308,334`).
27-- The client has no status-provider code today: no reference to
28- `topRight`/`bottomRight` exists under `internal/usecase/client`. The client
29- renders daemon-published frames verbatim. This contract does not change
30- that until its implementation is approved.
31-
32-## Opt-in client-local provider contract
33-
34-### Configuration authority
35-
36-- The command string for a client-local provider comes exclusively from the
37- **local** configuration authority (the config file loaded by the local
38- process composition). A remote daemon MUST NOT supply a shell command over
39- the wire; the wire carries only rendered text values (see fields below).
40-- Existing `bar.top-right` / `bar.bottom-right` scripts remain daemon-host
41- execution with unchanged semantics. Client-local providers target the same
42- two slots but never replace daemon execution implicitly: both run, and
43- precedence (below) decides what paints.
44-- Opt-in per slot: unset means absent. No implicit fallback to a remote path,
45- and remote pane CWD is never interpreted as a local path. A provider that
46- needs a working directory uses only locally configured values.
47-
48-### Generations
49-
50-- **Provider generation**: a monotonically increasing epoch owned by the
51- executing side, bumped on every configuration reload affecting the slot.
52- Stale values (older provider generation than the latest admitted for that
53- slot and attachment) are discarded, never painted.
54-- **Attachment generation**: the existing attachment identity/generation used
55- in output arbitration binds a value to the attachment it was produced for.
56- A value admitted for attachment A is never painted on attachment B; on
57- navigation/switch, pending provider values for the retired attachment are
58- dropped with the foreground owner.
59-
60-### Bounds, queues, cancellation
61-
62-- Keep the existing daemon bounds unless separately approved: 1s timeout,
63- 1024-byte stdout cap, 512-byte stderr cap, 256-rune display cap.
64- Client-local execution adopts the same caps.
65-- Bounded work queue per attachment (proposed depth 1 with coalescing,
66- mirroring `running`/`pending`): a slow provider run never stacks; a newer
67- tick marks pending and supersedes.
68-- Cancellation: provider runs cancel on attachment retirement, slot
69- reconfiguration, and client shutdown via the existing foreground-lifetime
70- mechanism. A late completion after cancel is discarded.
71-
72-### Sanitization and error presentation
73-
74-- All provider output passes daemon-side-equivalent sanitization on the
75- executing side before admission: first line, valid UTF-8, ANSI/control
76- stripping (`sanitizeBarScriptOutput` semantics), display-cap truncation.
77-- Stale/error presentation is deterministic: on failure the slot keeps the
78- last good value; a slot with no good value renders empty. Failures warn
79- once per (provider, slot, signature) until the signature changes. No error
80- text from stderr is painted (stderr is diagnostics only, capped).
81-
82-### Precedence (same slot, daemon + client values)
83-
84-1. A fresh admitted daemon value and a fresh admitted client value for the
85- same slot: the **client-local value wins only when the slot is explicitly
86- configured for client-local authority**; otherwise the daemon value wins.
87- Authority is per slot, from local configuration, never negotiated.
88-2. A stale value (older generation) never overrides a fresh one regardless
89- of side.
90-3. Absent/unset on the winning side falls through to the other side's latest
91- admitted value, then to empty.
92-
93-## Proposed fields (exact, pending approval)
94-
95-Configuration (local file, new keys under the existing parser):
96-
97-- `status.top-right.provider = local | daemon` (default `daemon`)
98-- `status.top-right.command = "<shell>"` (default empty = absent)
99-- `status.bottom-right.provider`, `status.bottom-right.command`: same shape.
100-- `status.interval`: duration, default 5s, floor 1s (`MinBarInterval`);
101- invalid values warn and keep prior, mirroring `bar.interval`.
102-
103-Wire/schema (rendered text only, never commands):
104-
105-- `StatusSlotValue{Slot: top-right | bottom-right, Text: string (already
106- sanitized, ≤256 runes), ProviderGeneration: uint64,
107- AttachmentID: <existing attachment identity type>}` carried inside the
108- existing publication path for the owning attachment only.
109-- No new carriage primitive: values travel with the attachment's ordered
110- publication; no persistence-format change.
111-
112-Validation: unknown `provider` value warns and keeps `daemon`; empty command
113-disables the slot; interval below minimum clamps and warns.
114-
115-## Tests mapped (to be added only after approval)
116-
117-- `TestClientProviderCommandNeverComesFromWire` (sessionwire/client):
118- crafted inbound value with command content is rejected; only text fields
119- admitted.
120-- `TestStaleProviderGenerationDiscarded` (client/composition): older
121- provider generation never paints over a newer admitted value.
122-- `TestProviderValueBoundToAttachment` (client): value admitted for A never
123- paints on B; retirement drops pending values.
124-- `TestProviderPrecedenceDaemonWinsByDefault` / `TestProviderPrecedenceLocalWinsWhenConfigured`
125- (composition): per-slot authority matrix.
126-- `TestProviderFailureKeepsLastGood` / `TestProviderEmptyWhenNoGood`
127- (provider harness): deterministic stale/error presentation; warn-once.
128-- `TestProviderQueueCoalesces` (provider harness, fake clock): slow run +
129- newer tick yields at most one pending execution; cancel on retirement.
130-- `TestRemoteCwdNeverInterpretedLocally` (provider harness): provider env
131- contains no remote-derived path.
132-- Existing pins stay green: `bar_refresh_test.go`, `bar_script` tests,
133- `status` composition tests, config parse tests.
134-
135-## Stop condition
136-
137-P4.2 ends at this document. No provider implementation, no new config key,
138-no wire field, and no new dependency is authorized by this plan alone.
@@ -1,138 +0,0 @@
1-# Client status contract (P4.2, design-only)
2-
3-Status: proposed. No production edits accompany this document. No provider
4-implementation is authorized by this plan alone; a runnable task breakdown
5-follows only after contract approval.
6-
7-## Current daemon-host behavior (preserved)
8-
9-- Commands come only from the daemon-loaded config: `bar.top-right`,
10- `bar.bottom-right`, `bar.interval` (defaults empty/empty/5s,
11- `internal/adapters/config`, `domain.Defaults`).
12-- Execution is daemon-host via `barScriptRunner.run`
13- (`internal/usecase/daemon/bar_script.go`) with `ports.ShellCommandRunner`:
14- timeout 1s (`barScriptTimeout`), stdout cap 1024, stderr cap 512, display
15- cap 256, first-line + `stripANSIAndControls` sanitization, warn-once
16- failure dedup (`lastFailure`).
17-- Scheduling is per-session in `bar_refresh.go`: poller on the daemon clock
18- with `MinBarInterval` (1s) floor, `running`/`pending` coalescing, context
19- change (`lastContext`) forces refresh, `reload` channel wakes the poller on
20- config change, `refreshBarScriptsAllSessions` applies new commands
21- immediately.
22-- Scripts observe daemon-side context only: `VEV_ANCHOR`, `VEV_SESSION`,
23- `VEV_TAB`, `VEV_PANE`, `VEV_PANE_CWD`, `VEV_COLS`.
24-- Composition reads snapshots at paint time: `barScriptSnapshot(sess)` feeds
25- `barState.topRight`/`bottomRight` alongside `status`, `statusFeedback`,
26- `mru`/`rankedRecent`, `attentionFrame` (`status.go:161,308,334`).
27-- The client has no status-provider code today: no reference to
28- `topRight`/`bottomRight` exists under `internal/usecase/client`. The client
29- renders daemon-published frames verbatim. This contract does not change
30- that until its implementation is approved.
31-
32-## Opt-in client-local provider contract
33-
34-### Configuration authority
35-
36-- The command string for a client-local provider comes exclusively from the
37- **local** configuration authority (the config file loaded by the local
38- process composition). A remote daemon MUST NOT supply a shell command over
39- the wire; the wire carries only rendered text values (see fields below).
40-- Existing `bar.top-right` / `bar.bottom-right` scripts remain daemon-host
41- execution with unchanged semantics. Client-local providers target the same
42- two slots but never replace daemon execution implicitly: both run, and
43- precedence (below) decides what paints.
44-- Opt-in per slot: unset means absent. No implicit fallback to a remote path,
45- and remote pane CWD is never interpreted as a local path. A provider that
46- needs a working directory uses only locally configured values.
47-
48-### Generations
49-
50-- **Provider generation**: a monotonically increasing epoch owned by the
51- executing side, bumped on every configuration reload affecting the slot.
52- Stale values (older provider generation than the latest admitted for that
53- slot and attachment) are discarded, never painted.
54-- **Attachment generation**: the existing attachment identity/generation used
55- in output arbitration binds a value to the attachment it was produced for.
56- A value admitted for attachment A is never painted on attachment B; on
57- navigation/switch, pending provider values for the retired attachment are
58- dropped with the foreground owner.
59-
60-### Bounds, queues, cancellation
61-
62-- Keep the existing daemon bounds unless separately approved: 1s timeout,
63- 1024-byte stdout cap, 512-byte stderr cap, 256-rune display cap.
64- Client-local execution adopts the same caps.
65-- Bounded work queue per attachment (proposed depth 1 with coalescing,
66- mirroring `running`/`pending`): a slow provider run never stacks; a newer
67- tick marks pending and supersedes.
68-- Cancellation: provider runs cancel on attachment retirement, slot
69- reconfiguration, and client shutdown via the existing foreground-lifetime
70- mechanism. A late completion after cancel is discarded.
71-
72-### Sanitization and error presentation
73-
74-- All provider output passes daemon-side-equivalent sanitization on the
75- executing side before admission: first line, valid UTF-8, ANSI/control
76- stripping (`sanitizeBarScriptOutput` semantics), display-cap truncation.
77-- Stale/error presentation is deterministic: on failure the slot keeps the
78- last good value; a slot with no good value renders empty. Failures warn
79- once per (provider, slot, signature) until the signature changes. No error
80- text from stderr is painted (stderr is diagnostics only, capped).
81-
82-### Precedence (same slot, daemon + client values)
83-
84-1. A fresh admitted daemon value and a fresh admitted client value for the
85- same slot: the **client-local value wins only when the slot is explicitly
86- configured for client-local authority**; otherwise the daemon value wins.
87- Authority is per slot, from local configuration, never negotiated.
88-2. A stale value (older generation) never overrides a fresh one regardless
89- of side.
90-3. Absent/unset on the winning side falls through to the other side's latest
91- admitted value, then to empty.
92-
93-## Proposed fields (exact, pending approval)
94-
95-Configuration (local file, new keys under the existing parser):
96-
97-- `status.top-right.provider = local | daemon` (default `daemon`)
98-- `status.top-right.command = "<shell>"` (default empty = absent)
99-- `status.bottom-right.provider`, `status.bottom-right.command`: same shape.
100-- `status.interval`: duration, default 5s, floor 1s (`MinBarInterval`);
101- invalid values warn and keep prior, mirroring `bar.interval`.
102-
103-Wire/schema (rendered text only, never commands):
104-
105-- `StatusSlotValue{Slot: top-right | bottom-right, Text: string (already
106- sanitized, ≤256 runes), ProviderGeneration: uint64,
107- AttachmentID: <existing attachment identity type>}` carried inside the
108- existing publication path for the owning attachment only.
109-- No new carriage primitive: values travel with the attachment's ordered
110- publication; no persistence-format change.
111-
112-Validation: unknown `provider` value warns and keeps `daemon`; empty command
113-disables the slot; interval below minimum clamps and warns.
114-
115-## Tests mapped (to be added only after approval)
116-
117-- `TestClientProviderCommandNeverComesFromWire` (sessionwire/client):
118- crafted inbound value with command content is rejected; only text fields
119- admitted.
120-- `TestStaleProviderGenerationDiscarded` (client/composition): older
121- provider generation never paints over a newer admitted value.
122-- `TestProviderValueBoundToAttachment` (client): value admitted for A never
123- paints on B; retirement drops pending values.
124-- `TestProviderPrecedenceDaemonWinsByDefault` / `TestProviderPrecedenceLocalWinsWhenConfigured`
125- (composition): per-slot authority matrix.
126-- `TestProviderFailureKeepsLastGood` / `TestProviderEmptyWhenNoGood`
127- (provider harness): deterministic stale/error presentation; warn-once.
128-- `TestProviderQueueCoalesces` (provider harness, fake clock): slow run +
129- newer tick yields at most one pending execution; cancel on retirement.
130-- `TestRemoteCwdNeverInterpretedLocally` (provider harness): provider env
131- contains no remote-derived path.
132-- Existing pins stay green: `bar_refresh_test.go`, `bar_script` tests,
133- `status` composition tests, config parse tests.
134-
135-## Stop condition
136-
137-P4.2 ends at this document. No provider implementation, no new config key,
138-no wire field, and no new dependency is authorized by this plan alone.

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 12

Tags 11

↑↓ moveenter openesc close