@promptctl/cc-candybar 1.42.1 → 1.43.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.
Files changed (126) hide show
  1. package/dist/index.mjs +72 -71
  2. package/package.json +5 -6
  3. package/src/check.ts +0 -478
  4. package/src/cli-flags.ts +0 -8
  5. package/src/click/wire.ts +0 -158
  6. package/src/config/action.ts +0 -329
  7. package/src/config/cli.ts +0 -71
  8. package/src/config/default-dsl-config.ts +0 -1645
  9. package/src/config/disclosure.ts +0 -170
  10. package/src/config/dsl-loader.ts +0 -339
  11. package/src/config/dsl-types.ts +0 -581
  12. package/src/config/edit-chrome.ts +0 -559
  13. package/src/config/help.ts +0 -151
  14. package/src/config/ident.ts +0 -22
  15. package/src/config/layout-ops.ts +0 -177
  16. package/src/config/loader/actions.ts +0 -972
  17. package/src/config/loader/cache.ts +0 -206
  18. package/src/config/loader/cross-ref.ts +0 -714
  19. package/src/config/loader/cycles.ts +0 -148
  20. package/src/config/loader/diagnostics.ts +0 -99
  21. package/src/config/loader/discovery.ts +0 -182
  22. package/src/config/loader/edit-mode.ts +0 -137
  23. package/src/config/loader/emit-schema.ts +0 -68
  24. package/src/config/loader/globals.ts +0 -269
  25. package/src/config/loader/helpers.ts +0 -48
  26. package/src/config/loader/layout.ts +0 -693
  27. package/src/config/loader/looks.ts +0 -96
  28. package/src/config/loader/menu-synth.ts +0 -435
  29. package/src/config/loader/merge.ts +0 -115
  30. package/src/config/loader/persist-target.ts +0 -67
  31. package/src/config/loader/presets.ts +0 -119
  32. package/src/config/loader/refs.ts +0 -100
  33. package/src/config/loader/reserved-namespace.ts +0 -38
  34. package/src/config/loader/segments.ts +0 -120
  35. package/src/config/loader/validate-core.ts +0 -737
  36. package/src/config/loader/variables.ts +0 -260
  37. package/src/config/menu-keys.ts +0 -139
  38. package/src/config/option-domain.ts +0 -164
  39. package/src/config/presets.ts +0 -326
  40. package/src/config/settings-menu.ts +0 -775
  41. package/src/daemon/acquire.ts +0 -684
  42. package/src/daemon/cache/git.ts +0 -649
  43. package/src/daemon/cache/render.ts +0 -623
  44. package/src/daemon/cache/session-usage-store.ts +0 -720
  45. package/src/daemon/cache/watchers.ts +0 -249
  46. package/src/daemon/client-debug.ts +0 -120
  47. package/src/daemon/client-stats.ts +0 -130
  48. package/src/daemon/client-transport.ts +0 -273
  49. package/src/daemon/client.ts +0 -78
  50. package/src/daemon/config-overrides-store.ts +0 -663
  51. package/src/daemon/debug-types.ts +0 -91
  52. package/src/daemon/debug.ts +0 -264
  53. package/src/daemon/fork-bomb-breaker.ts +0 -351
  54. package/src/daemon/limits.ts +0 -211
  55. package/src/daemon/log.ts +0 -81
  56. package/src/daemon/parent-watchdog.ts +0 -87
  57. package/src/daemon/paths.ts +0 -211
  58. package/src/daemon/process-fingerprint.ts +0 -146
  59. package/src/daemon/protocol.ts +0 -292
  60. package/src/daemon/render-payload.ts +0 -1256
  61. package/src/daemon/server.ts +0 -1330
  62. package/src/daemon/session-state-file.ts +0 -108
  63. package/src/daemon/session-state.ts +0 -237
  64. package/src/daemon/socket-lease.ts +0 -209
  65. package/src/daemon/socket-ownership.ts +0 -209
  66. package/src/daemon/stats.ts +0 -235
  67. package/src/daemon/verbs/config-validators.ts +0 -250
  68. package/src/daemon/verbs/index.ts +0 -706
  69. package/src/daemon/verbs/state-validators.ts +0 -249
  70. package/src/daemon/verbs/validator-registry.ts +0 -457
  71. package/src/demo/dsl.ts +0 -143
  72. package/src/demo/mock-data.ts +0 -67
  73. package/src/demo/statusline.json5 +0 -94
  74. package/src/dsl/node-registry.ts +0 -374
  75. package/src/dsl/render.ts +0 -803
  76. package/src/help-text.ts +0 -90
  77. package/src/index.ts +0 -210
  78. package/src/install/currency.ts +0 -197
  79. package/src/install/index.ts +0 -557
  80. package/src/proc/launch.ts +0 -459
  81. package/src/proc/stats-handle.ts +0 -13
  82. package/src/render/action.ts +0 -883
  83. package/src/render/active-segment.ts +0 -78
  84. package/src/render/diagnostic-style.ts +0 -23
  85. package/src/render/diagnostic-text.ts +0 -77
  86. package/src/render/error-glyph.ts +0 -53
  87. package/src/render/menu.ts +0 -257
  88. package/src/render/outcome-plan.ts +0 -45
  89. package/src/render/picker.ts +0 -372
  90. package/src/render/segment-color.ts +0 -74
  91. package/src/render/split-lines.ts +0 -51
  92. package/src/render/strip.ts +0 -228
  93. package/src/segments/cache.ts +0 -131
  94. package/src/segments/context.ts +0 -190
  95. package/src/segments/git.ts +0 -1084
  96. package/src/segments/metrics.ts +0 -187
  97. package/src/segments/pricing.ts +0 -452
  98. package/src/segments/session.ts +0 -23
  99. package/src/segments/tmux.ts +0 -74
  100. package/src/template-engine/cells.ts +0 -90
  101. package/src/template-engine/colors.ts +0 -124
  102. package/src/template-engine/engine.ts +0 -108
  103. package/src/template-engine/funcs.ts +0 -232
  104. package/src/template-engine/index.ts +0 -11
  105. package/src/template-engine/layout.ts +0 -133
  106. package/src/template-engine/scope.ts +0 -62
  107. package/src/template-engine/sparkline.ts +0 -79
  108. package/src/themes/index.ts +0 -20
  109. package/src/themes/palette-resolvers.ts +0 -84
  110. package/src/themes/policy.ts +0 -393
  111. package/src/utils/cache.ts +0 -206
  112. package/src/utils/claude.ts +0 -683
  113. package/src/utils/color-support.ts +0 -118
  114. package/src/utils/formatters.ts +0 -99
  115. package/src/utils/logger.ts +0 -5
  116. package/src/utils/outcome.ts +0 -33
  117. package/src/utils/schema-validator.ts +0 -126
  118. package/src/utils/single-flight.ts +0 -57
  119. package/src/utils/terminal-width.ts +0 -51
  120. package/src/utils/terminal.ts +0 -11
  121. package/src/utils/transcript-fs.ts +0 -279
  122. package/src/var-system/index.ts +0 -24
  123. package/src/var-system/sources.ts +0 -1047
  124. package/src/var-system/store.ts +0 -223
  125. package/src/var-system/types.ts +0 -57
  126. package/src/version.ts +0 -17
@@ -1,581 +0,0 @@
1
- // [LAW:types-are-the-program] DslConfig is the strongest theorem we can write
2
- // about a validated config: every legal config is representable, every illegal
3
- // one is not. The loader is the proof — its body either narrows `unknown` to
4
- // `DslConfig` or throws ConfigError. Downstream consumers receive a DslConfig
5
- // and are free to assume invariants (closed source-kind set, exactly-one cache
6
- // key, no dangling cross-refs, no template cycles) without re-checking.
7
- //
8
- // [LAW:one-source-of-truth] These shapes are the JSON-shape mirror of the
9
- // var-system's runtime types (`CachePolicy`, `ShellOptions`, etc. in
10
- // src/var-system/sources.ts). The loader is the single point that translates
11
- // between the two; no other module should re-derive these shapes.
12
-
13
- // [LAW:one-way-deps] The action schema lives in its own leaf module; DslConfig
14
- // references it here. The dependency is one-way (this file → action.ts), never
15
- // the reverse, so that shape can be lifted out without a cycle.
16
- import type { ActionDecl } from "./action.js";
17
- // [LAW:one-source-of-truth] A look IS a rich-js ThemeKey (four numeric axes:
18
- // hueShift / chromaScale / lightnessScale / lightnessShift) — the config type
19
- // references the vocabulary owner's type verbatim, so a rich-js axis rename is
20
- // a compile error here, never silent drift. Type-only: no runtime rich-js
21
- // dependency enters the config layer.
22
- import type { ThemeKey } from "@promptctl/rich-js";
23
- import type {
24
- Charset,
25
- ColorCompatibility,
26
- StripStyle,
27
- } from "../themes/policy.js";
28
-
29
- // [LAW:types-are-the-program] Three stages, three names.
30
- //
31
- // RawDslConfig — the user-file shape. Every top-level key is optional
32
- // because "user didn't write this" is a representable,
33
- // distinct state from "user wrote an explicit empty."
34
- // Internal to the loader module; downstream consumers
35
- // never see it.
36
- //
37
- // DslConfig — the effective shape: the user's deltas merged on top
38
- // of DEFAULT_DSL_CONFIG. Every top-level key is required.
39
- // Output of `loadConfig`. Cross-refs and cycles have NOT
40
- // yet been checked at this stage.
41
- //
42
- // ValidatedConfig — DslConfig + a phantom brand proving validateConfig()
43
- // has run. The renderer accepts only this type, so the
44
- // compiler structurally enforces "no unvalidated config
45
- // can reach rendering." The brand is module-scoped via
46
- // `unique symbol`, so the only construction site is
47
- // `validateConfig` itself.
48
- // [LAW:types-are-the-program] The recursive layout substrate collapses to
49
- // exactly two kinds: a `segment` leaf (a ref into the named `segments` block —
50
- // THE unit of rendering, a single template that IS its content) or a
51
- // `container` whose `direction` is DATA that decides how its children map onto
52
- // the 2D plane. Both the bar and (a later child's) menu are projections of this
53
- // one tree — they differ only in `direction`, not in code path
54
- // [LAW:dataflow-not-control-flow].
55
- //
56
- // [LAW:types-are-the-program] `Direction` carries the projection a container
57
- // applies to its child blocks as DATA. `vertical` STACKS them (concat the
58
- // children's line-lists); `horizontal` ZIPS them (per row, the children's cells
59
- // concatenate into one strip, so the powerline joiner caps ACROSS the seam —
60
- // abut is never valid). `outline` (a later child's menu) is NOT in the union
61
- // yet — it joins as a new arm only when its renderer exists, so the union stays
62
- // the strongest theorem that is still TRUE, with no representable-but-
63
- // unrenderable direction.
64
- // [LAW:one-source-of-truth] The runtime list and the type derive from one
65
- // declaration; the loader validates a container's `direction` against this set,
66
- // and renderDsl's projection switch is exhaustive over it (adding an arm here
67
- // forces a matching render arm).
68
- export const DIRECTIONS = ["vertical", "horizontal"] as const;
69
- export type Direction = (typeof DIRECTIONS)[number];
70
-
71
- // [LAW:one-type-per-behavior] THE unit of rendering: a ref into the named
72
- // `segments` block. A segment IS a single template (text, state-driven display,
73
- // clickable regions — whatever the template produces); there is no `inline` /
74
- // `stepper` / `picker` node kind, because "make a node flexible enough for
75
- // whatever" = one template expresses anything. A segment renders to ONE strip
76
- // item; the powerline joiner joins items, never inside one. A horizontal run of
77
- // segments is spelled `{ h: ["seg1", "seg2"] }` in the A-grammar.
78
- export interface SegmentNode {
79
- readonly kind: "segment";
80
- // A name into the `segments` block. The segment's own template/palette/`when`
81
- // live on its SegmentDecl, not here; this node is purely the tree position.
82
- readonly name: string;
83
- // [LAW:dataflow-not-control-flow] Absent `when` ≡ always-rendered. A node-level
84
- // predicate, ANDed with the segment-decl's own `when` at render.
85
- readonly when?: string;
86
- }
87
-
88
- export interface ContainerNode {
89
- readonly kind: "container";
90
- readonly direction: Direction;
91
- readonly children: readonly LayoutNode[];
92
- // A container's `when` gates the whole subtree: a hidden container emits no
93
- // lines, but its descendants are still walked so per-segment hue indices stay
94
- // positionally stable.
95
- readonly when?: string;
96
- }
97
-
98
- export type LayoutNode = ContainerNode | SegmentNode;
99
-
100
- // [LAW:types-are-the-program] The `group` SUGAR as collected at parse — an
101
- // INPUT-only shape, never a canonical LayoutNode kind: arranging + gating are
102
- // behaviors `container` already has, so "group" may only be a spelling. The
103
- // loader lowers each group to container/segment nodes and SYNTHESIZES its state
104
- // var + cycle action + toggle segment under the reserved `groups.` namespace
105
- // (one declaration; every derived artifact single-sourced from it
106
- // [LAW:one-source-of-truth]). `path` records the node's tree position so the
107
- // nesting invariant (an ancestor and a descendant must not share a state key)
108
- // is checkable after the walk.
109
- export interface GroupSugarDecl {
110
- readonly name: string;
111
- readonly label: string;
112
- readonly open?: boolean;
113
- readonly direction?: Direction;
114
- readonly key?: string;
115
- readonly bg?: string;
116
- readonly fg?: string;
117
- readonly when?: string;
118
- readonly path: string;
119
- }
120
-
121
- // [LAW:single-enforcer] THE one pre-order walk over a node tree. Every consumer
122
- // that needs "which segments / which `when` predicates does this layout name"
123
- // (the reachability closure, the debug dump, the cross-ref validator) iterates
124
- // this — none re-recurses the tree itself.
125
- export function* walkNodes(node: LayoutNode): IterableIterator<LayoutNode> {
126
- yield node;
127
- if (node.kind === "container") {
128
- for (const child of node.children) yield* walkNodes(child);
129
- }
130
- }
131
-
132
- // [LAW:types-are-the-program] A PRESET is a named config FRAGMENT — one
133
- // alternative arrangement of a bar the user can switch to — and its field set
134
- // is capped at exactly `root` + `globals`. That cap is not taste; it is the
135
- // daemon's own lifetime boundary made into a type.
136
- //
137
- // One RenderCache entry is keyed by (projectDir, cwd) and serves MANY sessions.
138
- // Its SourceRegistry (timers, fs watchers, git subscriptions) and its derived
139
- // click gate (registerStateValidator over deriveActionValidators) are built
140
- // ONCE, in buildState. A preset, by contrast, is a per-SESSION pick. So a
141
- // preset carrying `variables` would need a per-session registry, and one
142
- // carrying `actions` would need a per-session wire gate — or a gate that is the
143
- // union of every preset's actions anyway, at which point the preset scoped
144
- // nothing and only the merge got harder [LAW:no-ambient-temporal-coupling].
145
- // `root` and `globals` have no such problem: the root is WALKED per render (so
146
- // every preset's tree is compiled up front and one is selected by name, exactly
147
- // how every look's ThemeKey is resolved up front and one is selected by name),
148
- // and globals already resolve per render into EffectiveGlobals.
149
- //
150
- // Read as a rule an author can hold: a preset may carry what the bar RESOLVES
151
- // each render, never what the daemon REGISTERS once per process. An unbounded
152
- // preset would just be a second config file with extra steps.
153
- export interface PresetDecl {
154
- // Absent ⇒ this preset does not restage the layout; the config's own `root`
155
- // renders. A preset declares only its delta [LAW:carrying-cost] — a preset
156
- // that had to restate every row to change one would be a copy, and copies go
157
- // stale silently while continuing to look intentional.
158
- readonly root?: LayoutNode;
159
- // Absent ⇒ no display-default changes. Shallow-merged OVER the config's own
160
- // globals when this preset is active, so a preset naming `padding` says
161
- // nothing about `charset`.
162
- readonly globals?: Globals;
163
- }
164
-
165
- export interface RawDslConfig {
166
- readonly globals?: Partial<Globals>;
167
- readonly variables?: Readonly<Record<string, VariableDecl>>;
168
- readonly segments?: Readonly<Record<string, SegmentDecl>>;
169
- readonly root?: LayoutNode;
170
- readonly actions?: Readonly<Record<string, ActionDecl>>;
171
- // Named config fragments ("presets"): each an alternative `root`/`globals`
172
- // arrangement selected per session, the exact twin of `looks` one level up
173
- // (a look adapts the THEME; a preset adapts the LAYOUT + display globals).
174
- readonly presets?: Readonly<Record<string, PresetDecl>>;
175
- // The display globals edit mode stages while it is on — see DslConfig's own
176
- // `editGlobals` for the shape, the merge, and where it sits in the chain.
177
- readonly editGlobals?: Partial<Globals>;
178
- // Named theme-adaptation bundles ("looks"): each is a full ThemeKey (the
179
- // loader normalizes absent axes to identity at parse). Applied ON TOP of the
180
- // active theme at render — a transform composing with every theme, selected
181
- // per session exactly like theme/style (session key `look`).
182
- readonly looks?: Readonly<Record<string, ThemeKey>>;
183
- // [LAW:single-enforcer] Config-level shared helper templates: name → Go-template
184
- // body. Each compiles to one `{{ define "name" }}body{{ end }}` unit, and the
185
- // whole set into one shared define set every template this config parses
186
- // inherits — so a formatter (`{{ template "formatCost" .x }}`) is
187
- // defined ONCE and callable from any segment/predicate, never re-inlined per
188
- // segment. Absent ≡ no helpers; merges by-name (user overrides a helper).
189
- readonly helpers?: Readonly<Record<string, string>>;
190
- }
191
-
192
- export interface DslConfig {
193
- readonly globals: Globals;
194
- readonly variables: Readonly<Record<string, VariableDecl>>;
195
- readonly segments: Readonly<Record<string, SegmentDecl>>;
196
- // [LAW:one-source-of-truth] The SINGLE canonical layout representation authored
197
- // via the A-grammar (seg/h/v node arms, group sugar). No legacy sugar reaches
198
- // this field; the loader rejects `layout:` and `kind:"cells"` with migration errors.
199
- readonly root: LayoutNode;
200
- // [LAW:locality-or-seam] The named seam between click BEHAVIOR and the
201
- // clickable REPRESENTATION. Each entry is a statically-declared effect a
202
- // segment template binds a region to via `{{ action "name" … }}`. The
203
- // writable-key gate derives from this table (deriveActionValidators), so a
204
- // template cannot smuggle an un-gated write. Empty when no config declares
205
- // actions — an absent `actions` key merges to `{}`.
206
- readonly actions: Readonly<Record<string, ActionDecl>>;
207
- // [LAW:one-source-of-truth] The effective look set: name → full ThemeKey.
208
- // Merges by name with the bundled default (user wins per name), like
209
- // segments/actions/variables — so the default's `none` (the identity look and
210
- // the resolution floor of effectiveLookName) is present in EVERY merged
211
- // config by construction. An action `{ set: …, from: "looks" }` ranges these
212
- // names; the derived click gate and the rendered options read this one map.
213
- readonly looks: Readonly<Record<string, ThemeKey>>;
214
- // [LAW:one-source-of-truth] The effective preset set: name → config fragment.
215
- // Merges by name with the bundled default (user wins per name) like every
216
- // other section — so the default's `default` preset (the empty fragment, and
217
- // the resolution floor of effectivePresetName) is present in EVERY merged
218
- // config by construction, exactly as `looks` guarantees `none`. An action
219
- // `{ set: …, from: "presets" }` ranges these names; the derived click gate and
220
- // the rendered options read this one map.
221
- readonly presets: Readonly<Record<string, PresetDecl>>;
222
- // [LAW:one-source-of-truth] The display globals edit mode stages while it is
223
- // on — the `globals` half of the fragment whose `root` half edit chrome
224
- // already stages (src/config/edit-chrome.ts). Merges FIELD BY FIELD with the
225
- // bundled default's (like `globals` itself, not wholesale like `root`), so a
226
- // user retuning the separator keeps the bundled `style: "plain"`.
227
- //
228
- // [LAW:types-are-the-program] `Partial<Globals>`, deliberately NOT
229
- // `PresetDecl`: a preset is root + globals, and edit mode needs only the
230
- // globals half. Taking the wider type to use half of it would make "an edit
231
- // fragment that restages the layout" representable — a second authority over
232
- // a tree edit-chrome already owns. The loader additionally rejects `preset`
233
- // inside it, for the same reason a preset may not select a preset.
234
- //
235
- // Its rung in the precedence chain is the RIGHTMOST one (see
236
- // src/config/presets.ts): it outranks even a session pick, because entering
237
- // edit mode is decided later than picking a style. Nothing writes it back to
238
- // SessionState or the overrides layer, which is why leaving edit mode
239
- // restores the previous look with no save/restore path
240
- // [LAW:dataflow-not-control-flow].
241
- readonly editGlobals: Partial<Globals>;
242
- // [LAW:single-enforcer] The effective helper set: a name → template-body map
243
- // compiled to one shared define set at registerDslConfig. Empty when no config
244
- // declares helpers — an absent `helpers` key merges to `{}` (same cascade as
245
- // actions). The single definition site for each formatter/transform a template
246
- // calls via `{{ template "name" .arg }}`.
247
- readonly helpers: Readonly<Record<string, string>>;
248
- }
249
-
250
- // [LAW:single-enforcer] The brand symbol is `unique` and module-private —
251
- // nothing outside this file can construct a value carrying it. The only
252
- // production-path producer is validateConfig() in dsl-loader.ts (one
253
- // callsite of `config as ValidatedConfig`). Renderer signatures require
254
- // ValidatedConfig; the type system therefore proves the validation step
255
- // ran before any render path consumed the config.
256
- declare const __validated: unique symbol;
257
- export type ValidatedConfig = DslConfig & {
258
- readonly [__validated]: true;
259
- };
260
-
261
- export interface Globals {
262
- readonly default_bg?: string;
263
- readonly default_fg?: string;
264
- readonly default_empty_value?: string;
265
- readonly default_separator?: string;
266
- readonly default_truncate_marker?: string;
267
- // [LAW:one-source-of-truth] A palette NAME, not a resolved Palette: DslConfig
268
- // is the JSON-shape mirror, so the name is the authoritative datum and the
269
- // renderer owns name→Palette resolution. The config default for the base
270
- // theme; the daemon resolves the live base per render as
271
- // `sessionState.theme ?? globals.palette ?? default`, and a per-segment
272
- // `palette` is an explicit override that ignores the session theme.
273
- readonly palette?: string;
274
-
275
- // [LAW:one-type-per-behavior] The config default for the LOOK (a named
276
- // theme-adaptation from the `looks` block) — the exact twin of `palette` one
277
- // dimension over: the daemon resolves the live look per render as
278
- // `sessionState.look ?? globals.look ?? "none"` (effectiveLookName), so a
279
- // look click recolors the bar live and a config can set a default adaptation
280
- // without an edit-per-session. Membership in the merged `looks` map is
281
- // validated post-merge (cross-ref) — a user's globals.look may name a
282
- // default-provided look.
283
- readonly look?: string;
284
-
285
- // [LAW:one-type-per-behavior] The config default for the PRESET (a named
286
- // config fragment from the `presets` block) — the same twin-of-`palette`
287
- // shape as `look` one dimension over: the daemon resolves the live preset per
288
- // render as `sessionState.preset ?? globals.preset ?? "default"`
289
- // (effectivePresetName), so a preset click restages the bar live and a config
290
- // can pick a default arrangement without an edit-per-session. Membership in
291
- // the merged `presets` map is validated post-merge (cross-ref) — a user's
292
- // globals.preset may name a default-provided preset.
293
- //
294
- // [LAW:one-source-of-truth] A preset's own `globals` may NOT carry this field
295
- // (the loader rejects it): a preset selecting a preset is a second authority
296
- // over which preset is active, and a cyclic one.
297
- readonly preset?: string;
298
-
299
- // [LAW:one-type-per-behavior] The config default for the powerline cap/
300
- // separator SHAPE — the exact twin of `palette` one dimension over: the
301
- // daemon resolves the live strip style per render as
302
- // `sessionState.style ?? globals.style ?? "powerline"` (effectiveStripStyle),
303
- // so a style click reshapes the bar live and a config can set the default
304
- // shape without an edit-per-session.
305
- readonly style?: StripStyle;
306
-
307
- // The legacy display.autoWrap knob: whether FlexStrip soft-wraps a root
308
- // row that exceeds the usable width. Default true (current behavior);
309
- // false renders each row as one unbounded line, overflow off-screen.
310
- // [config-only] Unlike palette/style there is no SessionState/click half —
311
- // the daemon resolves `globals.autoWrap ?? true` into renderOpts.wrap.
312
- readonly autoWrap?: boolean;
313
-
314
- // The legacy display.padding knob: spaces synthesized INSIDE each segment
315
- // cell per side (intra-cell, within the bg fill — not rich-js FlexStrip's
316
- // inter-item gap). Default 1 (current behavior). Templates author content;
317
- // this chrome is applied structurally at the cell-formation seam.
318
- // [config-only] The daemon resolves `globals.padding ?? 1` into
319
- // renderOpts.padding; no SessionState/click half.
320
- readonly padding?: number;
321
-
322
- // The legacy display.charset knob: which glyph vocabulary the strip joiners
323
- // render with. Default "unicode" (current behavior — rich-js's powerline
324
- // caps, U+E0Bx). "ascii" swaps the caps for single-column ASCII glyphs so
325
- // terminals/fonts without powerline glyphs render cleanly instead of tofu.
326
- // Orthogonal to `style`: style picks the joiner shape, charset the glyphs.
327
- // [config-only] The daemon resolves `globals.charset ?? "unicode"` into
328
- // renderOpts.charset; no SessionState/click half.
329
- readonly charset?: Charset;
330
-
331
- // The legacy display.colorCompatibility knob: the color depth rich-js
332
- // downsamples output to. Default "truecolor" (current behavior — NOT the
333
- // legacy "auto" default, which would change rendering for existing users).
334
- // The type excludes "auto" entirely: the daemon is detached, so env
335
- // detection would read the wrong terminal — see COLOR_COMPATIBILITIES.
336
- // [config-only] The daemon resolves `globals.colorCompatibility ??
337
- // "truecolor"` into renderOpts.colorCompatibility; no SessionState/click half.
338
- readonly colorCompatibility?: ColorCompatibility;
339
- }
340
-
341
- // [LAW:one-type-per-behavior] One discriminated union covers every source
342
- // kind. Adding a new kind = code change here + matching runtime support in
343
- // var-system. There is no "extension" path that bypasses this list.
344
- export type VariableDecl =
345
- | LiteralVarDecl
346
- | InputVarDecl
347
- | EnvVarDecl
348
- | FileVarDecl
349
- | ShellVarDecl
350
- | TemplateVarDecl
351
- | TimeVarDecl
352
- | GitVarDecl
353
- | StateVarDecl;
354
-
355
- export interface LiteralVarDecl {
356
- readonly kind: "literal";
357
- readonly value: string | number | boolean;
358
- readonly default?: string;
359
- }
360
-
361
- // [LAW:types-are-the-program] `type` carries the runtime kind of the value at
362
- // the resolved payload path. Number/bool are needed for the usage/cost/today
363
- // family — token counts, cost amounts, percentages — whose formatters
364
- // (`formatCost`, `formatTokens`, `round`, `budgetStatus`) take numeric inputs.
365
- // Absent `type` defaults to "string" at the loader, preserving the historical
366
- // behavior of every existing declaration. The default value's literal type
367
- // must match the declared type — a number default on a string-typed input
368
- // (or vice versa) is rejected at load time, not at first render.
369
- export interface InputVarDecl {
370
- readonly kind: "input";
371
- readonly path: string;
372
- readonly type?: "string" | "number" | "boolean";
373
- readonly default?: string | number | boolean;
374
- }
375
-
376
- export interface EnvVarDecl {
377
- readonly kind: "env";
378
- readonly name: string;
379
- readonly default?: string;
380
- }
381
-
382
- export interface FileVarDecl {
383
- readonly kind: "file";
384
- readonly path: string;
385
- readonly readMode?: "whole" | "first-line";
386
- readonly regex?: string;
387
- readonly cache: CacheDecl;
388
- readonly default?: string;
389
- }
390
-
391
- export interface ShellVarDecl {
392
- readonly kind: "shell";
393
- readonly command: string;
394
- readonly regex?: string;
395
- readonly cache: CacheDecl;
396
- readonly default?: string;
397
- }
398
-
399
- export interface TemplateVarDecl {
400
- readonly kind: "template";
401
- readonly template: string;
402
- readonly cache?: CacheDecl;
403
- readonly default?: string;
404
- }
405
-
406
- // [LAW:types-are-the-program] Time vars refresh on a clock — ttl is the only
407
- // cache form the runtime honors (declareTime always registers a TTL timer).
408
- // The loader rejects the other CacheDecl arms at load, so past that boundary
409
- // a non-ttl cache on a time var is unrepresentable, not silently coerced.
410
- export interface TimeVarDecl {
411
- readonly kind: "time";
412
- readonly layout: string;
413
- readonly cache?: TtlCacheDecl;
414
- readonly default?: string;
415
- }
416
-
417
- export interface GitVarDecl {
418
- readonly kind: "git";
419
- readonly field: GitField;
420
- readonly cache: CacheDecl;
421
- readonly default?: string;
422
- }
423
-
424
- // [LAW:one-source-of-truth] A `state` variable reads through to the daemon's
425
- // SessionState (the canonical store for per-session toggles, random picks,
426
- // click-mutated values). Reactivity is wired by SessionState's internal MobX
427
- // atom — a click verb that writes into SessionState invalidates this
428
- // variable's downstream computeds automatically. Persistence rides for free
429
- // on SessionState's disk backing.
430
- //
431
- // The session id is resolved from the conventional `session.id` variable —
432
- // that name is the canonical anchor for "which session am I in," declared
433
- // once by DSL configs as an input variable carrying hook_data.session_id.
434
- // [LAW:no-mode-explosion] No per-decl override knob: a single canonical
435
- // session-id source keeps every state var's resolution uniform and removes
436
- // an axis along which configs could drift from each other.
437
- export interface StateVarDecl {
438
- readonly kind: "state";
439
- readonly key: string;
440
- readonly default?: string;
441
- }
442
-
443
- export type GitField =
444
- | "branch"
445
- | "sha"
446
- | "dirty"
447
- | "ahead"
448
- | "behind"
449
- | "stash";
450
-
451
- // [LAW:dataflow-not-control-flow] The discriminator is "which key is present"
452
- // in the user's JSON — not a `kind` field. Encoded as a 5-arm union so the
453
- // type system enforces "exactly one of these." The loader validates the
454
- // runtime invariant (one and only one); the type then carries it forward.
455
- export type CacheDecl =
456
- | TtlCacheDecl
457
- | { readonly watch_file: string }
458
- | { readonly depends_on: readonly string[] }
459
- | { readonly key: string }
460
- | { readonly never: true };
461
-
462
- // [LAW:one-source-of-truth] The ttl arm named once, so the kinds that honor
463
- // only a refresh interval (time) reference the same member the full vocabulary
464
- // is composed from — narrowing is a subset, never a parallel shape.
465
- export interface TtlCacheDecl {
466
- readonly ttl: string;
467
- }
468
-
469
- export const CACHE_KEYS = [
470
- "ttl",
471
- "watch_file",
472
- "depends_on",
473
- "key",
474
- "never",
475
- ] as const;
476
- export type CacheKey = (typeof CACHE_KEYS)[number];
477
-
478
- export const SOURCE_KINDS = [
479
- "literal",
480
- "input",
481
- "env",
482
- "file",
483
- "shell",
484
- "template",
485
- "time",
486
- "git",
487
- "state",
488
- ] as const;
489
- export type SourceKind = (typeof SOURCE_KINDS)[number];
490
-
491
- // [LAW:one-source-of-truth] The "which kinds have a cache field" predicate
492
- // lives here once. The loader's cross-ref and cycle validators narrow via
493
- // this guard instead of repeating the kind list (`!== "literal" && !==
494
- // "input" && ...`) at every site — adding a new no-cache kind only requires
495
- // updating the union and this guard.
496
- export type VariableDeclWithCache =
497
- | FileVarDecl
498
- | ShellVarDecl
499
- | TemplateVarDecl
500
- | TimeVarDecl
501
- | GitVarDecl;
502
-
503
- export function hasCacheField(v: VariableDecl): v is VariableDeclWithCache {
504
- return (
505
- v.kind !== "literal" &&
506
- v.kind !== "input" &&
507
- v.kind !== "env" &&
508
- v.kind !== "state"
509
- );
510
- }
511
-
512
- export const GIT_FIELDS: readonly GitField[] = [
513
- "branch",
514
- "sha",
515
- "dirty",
516
- "ahead",
517
- "behind",
518
- "stash",
519
- ];
520
-
521
- // Source kinds where the user MUST declare a cache policy (no sensible default).
522
- // Aligns with the proposal's cache-invalidation table.
523
- export const SOURCES_REQUIRING_CACHE: readonly SourceKind[] = [
524
- "file",
525
- "shell",
526
- "git",
527
- ];
528
-
529
- export interface SegmentDecl {
530
- readonly template: string;
531
- readonly width?: "auto" | number;
532
- readonly justify?: JustifyMode;
533
- readonly truncate?: TruncateMode;
534
- readonly bg?: string;
535
- readonly fg?: string;
536
- readonly when?: string;
537
- // [LAW:one-source-of-truth] Per-segment palette override (a NAME). Overrides
538
- // globals.palette for this segment only; undefined = inherit the cascade base.
539
- readonly palette?: string;
540
- // Per-segment vars sub-block — lives in the same global MobX store at
541
- // runtime under the namespaced key `<segment>.<var>`. Templates reference a
542
- // segment local ONLY via that namespaced form (`.<segment>.local`), from any
543
- // segment including the owning one; the loader rejects bare refs at load
544
- // with a diagnostic naming the namespaced form. [LAW:one-source-of-truth]
545
- readonly vars?: Readonly<Record<string, VariableDecl>>;
546
- }
547
-
548
- export type JustifyMode = "left" | "center" | "right";
549
- export type TruncateMode = "right" | "left" | "middle";
550
-
551
- export const JUSTIFY_MODES: readonly JustifyMode[] = [
552
- "left",
553
- "center",
554
- "right",
555
- ];
556
- export const TRUNCATE_MODES: readonly TruncateMode[] = [
557
- "right",
558
- "left",
559
- "middle",
560
- ];
561
-
562
- // ─── Conventional render-time variable names ─────────────────────────────────
563
- //
564
- // [LAW:one-source-of-truth] These are not widget types (those live in
565
- // `./action.ts`); they are the conventional variable NAMES the renderer and the
566
- // picker agree on. Kept here, with the other render/config conventions.
567
-
568
- // [LAW:one-source-of-truth] The conventional variable a picker paginates against
569
- // — the usable terminal width renderDsl injects each render. One name shared by
570
- // the declaration (default config) and the picker's read, so they cannot drift.
571
- export const TERM_COLS_VAR = "term.cols";
572
-
573
- // [LAW:one-source-of-truth] The conventional variable per-segment hue rotation
574
- // reads. hueStep is NOT a globals field (that would be a second source for a
575
- // render-time value); it is a value in the store like every other render input.
576
- // A config declares this variable — as a `state` var so a stepper can drive it
577
- // live (session value over the declared default, the same session-over-default
578
- // the theme uses), or as any kind for a fixed value. renderDsl reads it through
579
- // this one name; a bounded stepper action writes the SessionState key it reads.
580
- // Absent ≡ no rotation (step 0) — the degenerate case, not a special branch.
581
- export const HUE_STEP_VAR = "hue.step";