# G-008 Scoped console state for same-console subshells (activatable state sets) Status: proposed Scope: src/modules/punk/console-999999.0a1.0.tm, src/modules/punk/repl-999999.0a1.0.tm, src/modules/punk/repl/codethread-999999.0a1.0.tm Goal: a subshell on the shared console can opt into scoped console state - its terminal mutations (tabstops, modes, cursor style, palette, title) are captured as an activatable per-subshell state set and the prior state is re-established on quit or switch-away, with today's shared/persistent behaviour remaining the default. Acceptance: with a scoped subshell: tabstops, at least one DEC/ANSI mode and at least one palette entry changed inside the subshell are restored for the parent on quit (verified by terminal query where queryable) and the facts store matches the terminal; the default (unscoped) launch behaviour is unchanged; activation is set-based, proven by applying state set A, activating set B, then re-activating A and observing A's state; irreversible outputs (cleared screen/scrollback, emitted text) are documented as out of scope. ## Context Motivating observation: in a punk repl, `subshell punk` then `punk::console::set_tabstop_width 20` then `quit` leaves the parent repl with 20-wide tabstops. This is only partly a data problem - `get_tabstops` queries the terminal (DECTABSR), and the terminal genuinely was reprogrammed by the subshell's emission. No amount of per-interp data scoping restores the parent's tabstops; restore is inescapably an *active re-emission* problem: something must know the prior state and write sequences to reinstate it. The per-console facts store (punk::console 0.3.0) is deliberately keyed per console, not per scope, because facts mirror terminal reality - rolling back a fact without re-emitting would make the store lie. Two future requirements shape the design (see G-009, G-010): - Themed subshells will declare terminal effects (e.g. palette changes from poshinfo schemes) that must be applied on entry and removed on exit alongside journaled ad-hoc mutations. - Subshell switching will be tree-shaped and non-LIFO (grandchild to grandparent, cross-branch), so a pure undo-stack model dies at the first cross-branch jump. State must be organised as *activatable per-subshell state sets*: activating a subshell (re-)applies its set; quitting activates the parent's set. LIFO restore-on-quit is then just the degenerate case. The punk::console 0.2.0-0.5.0 migration and the G-007 broker provide the hook sites: every state-mutating operation funnels through a small set of choke points (the emit helpers, the mode setters, `console_fact_set`), including mutations originating in code interps once G-007 routes them through the owning context. ## Approach 1. **Per-subshell console-state set** = inherited baseline + declared entries (themes, G-009) + journaled runtime mutations. Journal-on-write at the choke points: at a subshell's first mutation of a given aspect, record the prior value (from the facts store, or one terminal query where needed), then record the new value in the subshell's set. 2. **Activation, not undo.** Activating a state set (re-)emits its entries (idempotent, or as a diff against the currently-applied set). Deactivation is implicit in activating another set. `quit` = activate the parent's set. This is what makes G-010's arbitrary switching possible. 3. **Opt-in per launch** (e.g. `subshell punk -consolestate scoped|shared`), defaulting to today's shared/persistent behaviour - persisting is sometimes exactly what the user wants when configuring their terminal from a subshell. 4. **Restorable classification** (each with its capture/re-emission mechanism): - tabstops - DECTABSR query / clear-all + HTS per recorded column - DEC and ANSI modes - DECRQM query / DECSET-DECRST, SM-RM - cursor style - DECRQSS(DECSCUSR) / DECSCUSR - colour palette and default fg/bg - OSC 4/10/11 (query support varies; journal what punk set; OSC 104/110/111 reset only reaches terminal defaults, which may differ from the parent's own customised baseline - prefer journaled priors) - title - journal only (queryability unreliable); mouse/paste reporting, bracketed paste, vt52 mode, raw mode - journal punk-side state Irreversible and documented out of scope: screen contents, scrollback, emitted output. 5. **Facts consistency**: state-set activation updates the per-console facts store in the same step as re-emission, so `console_fact_get` keeps matching terminal reality. ## Alternatives considered - **LIFO frame/undo stack** - rejected as the core model: G-010's cross-branch switching cannot be expressed as pops. Retained conceptually as the behaviour observed in the simple launch/quit case. - **Snapshot-everything on subshell entry** - rejected as primary mechanism: depends on the terminal answering every query (DECTABSR/DECRQSS/OSC-with-? support varies widely); journal-on-write is exact, cheap, and only touches aspects actually mutated. Entry snapshot remains useful for the root baseline where queryable. - **Scope the facts store per subshell without re-emission** - rejected: the terminal is the ground truth; data-only scoping makes the facts store lie (the motivating `get_tabstops` observation would be unchanged). - **Alt-screen as the scoping mechanism** - insufficient: alt screen scopes display content but not tabstops, palette, modes or cursor style. ## Notes - Sequence after G-007 (achieved 2026-07-05 - prerequisite in place): the journal hooks belong at the same choke points the broker instruments, and journaling must catch mutations originating in code interps - exactly what the broker funnels. Composes with G-002: a subshell targeting a *different* console needs no scoping (its terminal is its own); this goal covers the common shared-console case. - G-009 consumes this by declaring theme effects into the launch-time state set; G-010 consumes the activation primitive for switching. Neither is required for this goal's acceptance. - The A/B/A acceptance clause exists precisely to force the set-based design - a pure stack implementation passes restore-on-quit but fails A/B/A.