Browse Source
Index entries in GOALS.md plus detail files, all proposed: - G-007 location-transparent punk::console across repl and code interps: punk-side tsv-backed ownership registry, fact/cache visibility, choke-point brokering generalising the vt52 alias; detail records rejected alternatives (audience namespace split, owner field on ::opunk::Console, opunk-package-namespace registry) - G-008 scoped console state for same-console subshells: activatable per-subshell state sets (journal-on-write, re-emit on activation) rather than a LIFO undo stack, so later tree switching works; restore of tabstops/modes/palette on quit; opt-in, shared behaviour stays default - G-009 themed subshell profiles: poshinfo schemes bound to restriction aspects (G-003 limits/hidden commands, punk::island) so restricted subshells are visually distinct; terminal effects ride the G-008 state set - G-010 subshell suspend/resume and tree navigation: switch to any live subshell (grandparent, cross-branch) via attachment handoff + G-008 state activation; navigation commands governed by G-003 hide/expose; depends on G-002's non-nested model Sequencing: G-007 -> G-008 -> G-009/G-010, with G-002 prerequisite for G-010. Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.commaster
5 changed files with 312 additions and 0 deletions
@ -0,0 +1,106 @@
|
||||
# G-007 Location-transparent punk::console across repl and code interps |
||||
|
||||
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 |
||||
Acceptance: from a running punk session's code interp, without `repl eval`: `console_fact_get` returns the same values the parent sees and a fact set in the parent is immediately visible; a terminal query (e.g. `get_cursor_pos` or `dec_get_mode`) against the default console succeeds and cooperates with the repl reader (no lost or garbled input); a console constructed and owned by code-interp code is operated on locally (no round-trip to the parent); the existing console test suites pass and single-interp (non-repl) usage is unchanged. |
||||
|
||||
## Context |
||||
|
||||
A punk session splits work between the parent (repl) thread/interp and a code interp running in a |
||||
codethread. `punk::console` was written for application code, but it also carries the framework's |
||||
console infrastructure - and all of its interesting state lives in the parent: the per-console |
||||
facts store (`console_facts` + legacy variables), the `dec_has_mode`/`ansi_has_mode` caches, the |
||||
anchored `default` instance with its settled `can_respond`, the cooperative |
||||
`input_chunks_waiting` store, and raw-mode state. A code interp that does |
||||
`package require punk::console` gets a structurally identical but hollow twin: empty facts, no |
||||
anchored instances - and, worse than invisibility, a second would-be stdin reader that is not |
||||
part of the parent's cooperative reader protocol. Exploring or building against punk::console |
||||
from inside a session therefore requires `repl eval`, which exists as a development hack. |
||||
|
||||
The distinction that matters is not application-vs-infrastructure audience but **ownership and |
||||
location of state**: exactly one context can own an input channel's reader and the process |
||||
console's raw mode. Any operation on a console you do not own must be brokered to the owner. |
||||
The brokering already exists in ad-hoc form: the repl installs a `vt52` alias in the code interp |
||||
that forwards via `thread::send` to the repl thread (repl-999999.0a1.0.tm ~:3435), proving the |
||||
transport works while the codethread runs (the repl thread services events). |
||||
|
||||
The punk::console 0.2.0-0.5.0 migration created the small set of choke points that make a |
||||
systematic bridge tractable: all queries flow through `internal::get_ansi_response_payload`, |
||||
all spec handling through `console_spec_resolve` / `internal::opt_console_*` / |
||||
`internal::hybrid_console_spec`, all terminal-property facts through |
||||
`console_fact_get`/`console_fact_set`, and raw mode through `enableRaw`/`disableRaw`. Bridging |
||||
those bridges everything above them. There is also precedent for cross-thread state: `is_raw` |
||||
already lives in tsv because it must be visible to all threads. |
||||
|
||||
## Approach |
||||
|
||||
1. **Ownership registry (punk-side, tsv-backed).** punk::console records an owner context per |
||||
registered/anchored console, captured at registration time (the context performing |
||||
`opunk::console::create`/anchoring is the owner - this covers G-001-style backends such as a |
||||
tk-widget console owned by the thread running the Tk mainloop, with no change to the opunk |
||||
class). The default console's owner is the repl thread. Consulted at the routing choke |
||||
points at use time, so it is always current. Stale-entry handling: hook the opunk lifecycle |
||||
(ensure_object_integration precedent) so `forget` clears the entry, and/or validate owner |
||||
liveness (`thread::exists`) at consult time - this recovers the lifecycle cohesion that an |
||||
opunk-side registry would have had natively. |
||||
2. **Fact and cache visibility via tsv.** `console_fact_get`/`console_fact_set` (and the mode |
||||
caches) move to - or are mirrored in - tsv so any thread reads the same values with no RPC. |
||||
Nuance: non-default channel *names* are thread-local, so tsv keys need an owner qualifier |
||||
(or sharing may initially be limited to the default console) to avoid cross-thread aliasing. |
||||
3. **Choke-point brokering.** Codethread setup installs shims generalising the existing `vt52` |
||||
alias: `thread::send` across the thread boundary, `interp alias` for nested interps within |
||||
the codethread. Local fast path when the calling context is the owner; bridge otherwise. |
||||
Channel values cannot cross threads - the anchored *instance name* spec form is the handle |
||||
that travels (resolved in the owning context), which the existing spec grammar already |
||||
provides. |
||||
4. **Per-console routing, not per-context.** A console constructed and owned by code-interp |
||||
code (e.g. an ssh-channel console opened by user code) operates locally; only operations on |
||||
consoles owned elsewhere route. The brokering must compose with the settled-capability |
||||
gating that already refuses queries on consoles settled as unable to respond. |
||||
|
||||
## Alternatives considered |
||||
|
||||
- **Split punk::console into an application namespace and an infrastructure namespace** - |
||||
rejected: both audiences need the same operations; an audience split duplicates the API |
||||
surface while fixing neither state visibility nor reader contention. The split that matters |
||||
is client-vs-owner transport, behind one API shape. |
||||
- **Owner field on `::opunk::Console`** - rejected: voo object values are immutable snapshots, |
||||
so an embedded owner is a cached claim that goes stale exactly when it matters (console |
||||
handoff between subshells is G-002's purpose; owning threads can exit). Ownership is process |
||||
topology, not a terminal fact, and the class's design deliberately avoids depending on the |
||||
integrating layer; G-001's acceptance also requires the base class unchanged. The |
||||
construction-time knowledge an owner field would capture is instead recorded at registration |
||||
time into the punk-side registry. If a subclass ever needs to *hint* ownership, that is a |
||||
small additive follow-up. |
||||
- **Ownership registry in the opunk::console package namespace (beside `instances::*`)** - |
||||
rejected: the instance anchors are per-interp/per-thread namespace variables, but the entire |
||||
point of the ownership registry is that *other* threads consult it - it must live in shared |
||||
storage (tsv), and opunk::console deliberately carries no Thread dependency, whereas |
||||
punk::console already requires Thread and has the tsv precedent (`is_raw`). punk::console's |
||||
choke points also need ownership answers for plain channel-pair consoles in sessions where |
||||
opunk::console is never loaded, so a punk-side path would exist regardless - an opunk-side |
||||
registry would duplicate it. Its one real virtue, lifecycle cohesion with `create`/`forget`, |
||||
is recovered punk-side via the integration-hook approach (see Approach) rather than by |
||||
relocating the registry. |
||||
- **Status quo (independent module load per interp + `repl eval`)** - rejected: hollow twin |
||||
state, competing stdin readers, and `repl eval` as a permanent requirement for basic |
||||
interactive exploration. |
||||
- **tsv-share everything, no brokering** - insufficient alone: facts and caches can be shared, |
||||
but channels cannot; queries, emission and raw-mode cycling still need a single owner. tsv |
||||
handles the visibility half; brokering handles the operation half. |
||||
|
||||
## Notes |
||||
|
||||
- **Not blocked by G-002** - deliberately sequenced before it. A v1 with a single fixed owner |
||||
(the repl thread) is definable now and is the mechanism G-002's "target a named console" |
||||
criterion generalises to multiple owners. Deferred to G-002: multi-owner topology, |
||||
codethread-owned consoles as first-class subshell targets, and full retirement of |
||||
`repl eval`. |
||||
- Raw-mode arbitration brokering and retiring the hand-written `vt52` alias in favour of the |
||||
general mechanism are natural extensions but are intentionally not in this goal's acceptance |
||||
(a "broker complete" variant was considered and deferred). |
||||
- Deadlock consideration for `thread::send` sync shims: proven safe in the existing `vt52` |
||||
alias pattern because the repl thread services events while the codethread executes; |
||||
preserve that property (or use async+vwait) when generalising. |
||||
- The choke points listed in Context are the intended shim set; anything routed above them |
||||
(the ~60 public procs) inherits brokering for free. |
||||
@ -0,0 +1,80 @@
|
||||
# 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 |
||||
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: 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. |
||||
@ -0,0 +1,52 @@
|
||||
# G-009 Themed subshell profiles binding poshinfo schemes to behavioural configuration |
||||
|
||||
Status: proposed |
||||
Scope: src/modules/punk/repl-999999.0a1.0.tm, src/modules/poshinfo-999999.0a1.0.tm, src/modules/punk/console-999999.0a1.0.tm, src/modules/punk/island-999999.0a1.0.tm |
||||
Acceptance: a named profile associating a poshinfo scheme with at least one restriction aspect (an interp limit, a hidden command, or island-restricted filesystem access) launches by name; the scheme's visual state applies on entry (at minimum prompt styling plus one underlying terminal aspect such as a palette change) and is removed/restored on quit via the G-008 mechanism; profile-associated non-terminal data is scoped to the subshell's lifetime; an unthemed launch is unchanged. |
||||
|
||||
## Context |
||||
|
||||
Subshells with restricted capabilities (G-003 interp limits and hidden commands, punk::island |
||||
filesystem restrictions) should be *visually distinct* so the user always knows which trust |
||||
context they are typing into - a security-usability property, not decoration. The poshinfo |
||||
module already enumerates schemes suitable as the visual vocabulary. Applying a scheme is not |
||||
just prompt styling: schemes may change underlying terminal state (colour palette, default |
||||
fg/bg), which must be cleanly removed when leaving the subshell - and a profile may carry |
||||
associated non-terminal data (prompt configuration, scheme identity, aspect settings) that |
||||
needs the same push/pop lifetime. |
||||
|
||||
## Approach |
||||
|
||||
- **Named profiles** bind: a poshinfo scheme + behavioural aspects (any of: G-003 interp |
||||
limits, hidden/exposed command sets, punk::island filesystem access) + associated data. |
||||
Launch by profile name (e.g. `subshell <profile>` or a `-profile` flag). |
||||
- **Terminal effects ride G-008**: the scheme's terminal-state changes (palette etc.) are |
||||
*declared* into the subshell's console-state set at launch, so entry applies them and |
||||
quit/switch-away restores the prior state through the same mechanism as journaled ad-hoc |
||||
mutations - no second restore path. |
||||
- **Non-terminal profile data** (scheme identity, prompt config, aspect parameters) is scoped |
||||
to the subshell's lifetime alongside the state set. |
||||
- **Fail-safe distinctness**: if the terminal cannot honour the scheme's deeper effects (no |
||||
palette support), the profile must still yield a visible distinction (prompt-level styling at |
||||
minimum) - a restricted subshell must never silently look like an unrestricted one. |
||||
|
||||
## Alternatives considered |
||||
|
||||
- **Prompt-string theming only** (no terminal-state effects) - insufficient as the design |
||||
ceiling: easily missed, and scrollback regions written by the subshell carry no distinction; |
||||
retained as the fail-safe minimum tier. |
||||
- **Folding theming into G-003** - rejected: restriction mechanics and visual identity are |
||||
orthogonal; G-003's acceptance is already substantial, and themes apply equally to |
||||
unrestricted profiles. |
||||
- **Ad-hoc theme application on entry with hand-written cleanup on exit** - rejected: duplicates |
||||
G-008's restore machinery and diverges from it exactly when switching (G-010) arrives. |
||||
|
||||
## Notes |
||||
|
||||
- Depends on G-008 (state-set declaration/restore); applies aspects whose mechanisms are |
||||
G-003's deliverables (limits, hide/expose) and punk::island's - this goal binds them into |
||||
profiles, it does not re-implement them. |
||||
- Mapping a poshinfo scheme to concrete terminal state (which palette slots, fg/bg, prompt |
||||
segments) is an implementation-time design task within poshinfo/punk::repl; the goal only |
||||
requires that at least one underlying terminal aspect is driven by the scheme and correctly |
||||
restored. |
||||
@ -0,0 +1,50 @@
|
||||
# G-010 Subshell suspend/resume and tree navigation |
||||
|
||||
Status: proposed |
||||
Scope: src/modules/punk/repl-999999.0a1.0.tm, src/modules/punk/repl/codethread-999999.0a1.0.tm |
||||
Acceptance: from a grandchild subshell a single switch command reaches the grandparent without unwinding through the intermediate parent; switching between subshells on different branches preserves each subshell's session state and re-applies its console state set on activation; suspended subshells can be listed and resumed; `quit` still unwinds to the launching parent as today; a subshell whose switch commands are hidden/restricted cannot initiate switches. |
||||
|
||||
## Context |
||||
|
||||
The current subshell model supports only launch and quit, in strict LIFO order, implemented as |
||||
synchronous nested interp evals - a grandchild cannot reach its grandparent without unwinding |
||||
through the parent, and nothing can be suspended. The desired model: subshells form a tree |
||||
(launch structure), any live subshell can be suspended rather than quit, and the console can be |
||||
switched to any live subshell - grandchild to grandparent, or across branches - not only along |
||||
parent/child edges. Which switches are *available* is governed by the commands exposed in the |
||||
current subshell, so restricted subshells (G-003/G-009 profiles) cannot use switching as an |
||||
escape hatch. |
||||
|
||||
## Approach |
||||
|
||||
- **Subshell registry as a tree**: launch records parentage; suspend detaches a subshell from |
||||
the console without tearing down its interp/session state; resume/switch re-attaches. |
||||
- **Switching = console attachment handoff + state activation**: transferring which subshell |
||||
drives the console is an ownership/attachment change (the G-007 registry is the natural |
||||
record), followed by activating the target's G-008 console-state set. Session state |
||||
(variables, history, running state) stays with each subshell's interp/thread. |
||||
- **Navigation as commands**: list/switch/suspend/resume are ordinary exposed commands, so |
||||
G-003's hide/expose mechanism governs who may navigate; `quit` retains its current meaning |
||||
(terminate and return to the launching parent). |
||||
- Requires G-002's non-nested execution model: with synchronous nested evals, every ancestor is |
||||
blocked inside the descendant's eval and cross-branch attachment is impossible. |
||||
|
||||
## Alternatives considered |
||||
|
||||
- **Flat MRU list instead of a tree** - rejected as the model (parentage determines `quit` |
||||
semantics and inheritance of profiles/state baselines), though flat listing is fine as a |
||||
*view* for switching. |
||||
- **Delegating switching to a terminal multiplexer** (tmux-style panes/windows per subshell) - |
||||
rejected as the mechanism: punk sessions must work without a multiplexer and across |
||||
platforms; nothing prevents complementary use of one. |
||||
- **Allowing switch from any subshell unconditionally** - rejected: switching is a capability; |
||||
restricted subshells must not reach less-restricted contexts (alignment with G-003/G-009). |
||||
|
||||
## Notes |
||||
|
||||
- Dependency chain: G-002 (non-nested subshells, first-subshell asymmetry) is prerequisite; |
||||
G-008 supplies the console-state swap on activation; G-007's ownership registry is where |
||||
attachment handoff is recorded. G-009 profiles make switch targets visually identifiable. |
||||
- Suspend/resume semantics for in-flight commands in a suspended subshell (block? continue |
||||
headless with output buffered?) is an implementation-time decision to be recorded here when |
||||
made; the acceptance only requires suspended subshells to be listable and resumable. |
||||
Loading…
Reference in new issue