@promptctl/cc-candybar 1.3.0 → 1.4.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.
- package/dist/index.mjs +37 -37
- package/package.json +5 -5
- package/schema/cc-candybar.schema.json +7 -0
- package/src/config/default-dsl-config.ts +72 -14
- package/src/config/dsl-types.ts +9 -0
- package/src/config/loader/globals.ts +6 -0
- package/src/daemon/server.ts +15 -1
- package/src/daemon/verbs/state-validators.ts +5 -5
- package/src/demo/dsl.ts +7 -1
- package/src/render/action.ts +2 -2
- package/src/render/strip.ts +21 -7
- package/src/template-engine/funcs.ts +4 -4
- package/src/themes/index.ts +4 -0
- package/src/themes/policy.ts +36 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@promptctl/cc-candybar",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"description": "Statusline renderer for Claude Code — a JSON5-configurable DSL with daemon-cached data sources, byte-clean palette-aware composition, and OSC8 click verbs.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.mjs",
|
|
@@ -91,10 +91,10 @@
|
|
|
91
91
|
"mobx": "^6.15.0"
|
|
92
92
|
},
|
|
93
93
|
"optionalDependencies": {
|
|
94
|
-
"@promptctl/cc-candybar-darwin-arm64": "1.
|
|
95
|
-
"@promptctl/cc-candybar-darwin-x64": "1.
|
|
96
|
-
"@promptctl/cc-candybar-linux-x64": "1.
|
|
97
|
-
"@promptctl/cc-candybar-linux-arm64": "1.
|
|
94
|
+
"@promptctl/cc-candybar-darwin-arm64": "1.4.0",
|
|
95
|
+
"@promptctl/cc-candybar-darwin-x64": "1.4.0",
|
|
96
|
+
"@promptctl/cc-candybar-linux-x64": "1.4.0",
|
|
97
|
+
"@promptctl/cc-candybar-linux-arm64": "1.4.0"
|
|
98
98
|
},
|
|
99
99
|
"pnpm": {
|
|
100
100
|
"supportedArchitectures": {
|
|
@@ -437,6 +437,18 @@ export const DEFAULT_DSL_CONFIG = {
|
|
|
437
437
|
type: "number",
|
|
438
438
|
default: 0,
|
|
439
439
|
},
|
|
440
|
+
|
|
441
|
+
// ── Style picker state (the live powerline-shape switcher) ───────────────
|
|
442
|
+
// [LAW:one-source-of-truth] `activeStyle` reads the SAME "style" SessionState
|
|
443
|
+
// key the daemon resolves into the strip joiner per render (see
|
|
444
|
+
// effectiveStripStyle wiring in src/daemon/server.ts) — the picker's write
|
|
445
|
+
// and the render's read are one value. Empty default ⇒ the daemon's
|
|
446
|
+
// "powerline" floor is in effect and styleControl shows "(default)".
|
|
447
|
+
activeStyle: { kind: "state", key: "style", default: "" },
|
|
448
|
+
// The style menu's page cursor: −1 closed / 0..N open, mirroring how a theme
|
|
449
|
+
// picker's page key gates its reveal row. The stylePage action declares the
|
|
450
|
+
// int gate; this var reads it back for the reveal `when`.
|
|
451
|
+
stylePage: { kind: "state", key: "style-page", default: "-1" },
|
|
440
452
|
},
|
|
441
453
|
|
|
442
454
|
// ─── Segments ──────────────────────────────────────────────────────────────
|
|
@@ -623,25 +635,60 @@ export const DEFAULT_DSL_CONFIG = {
|
|
|
623
635
|
" .metrics.sessionDuration .metrics.messageCount" +
|
|
624
636
|
" .metrics.linesAdded .metrics.linesRemoved }}",
|
|
625
637
|
},
|
|
638
|
+
// Style control — the powerline-shape switcher's trigger. [LAW:locality-or-
|
|
639
|
+
// seam] The ✦ glyph + current-style label is the REPRESENTATION; the
|
|
640
|
+
// `openStyleMenu` action is the BEHAVIOR; the name is the seam. Shows the
|
|
641
|
+
// active shape (or "(default)" when unset) and a ▸ that opens the picker
|
|
642
|
+
// row. [LAW:dataflow-not-control-flow] No display state from the provider —
|
|
643
|
+
// the label is the one "style" value the click writes and the render reads.
|
|
644
|
+
styleControl: {
|
|
645
|
+
template:
|
|
646
|
+
"✦ {{ if .activeStyle }}{{ .activeStyle }}{{ else }}(default){{ end }} " +
|
|
647
|
+
'{{ action "openStyleMenu" "▸" }}',
|
|
648
|
+
bg: "surface",
|
|
649
|
+
fg: "foreground",
|
|
650
|
+
},
|
|
651
|
+
// The expanded style picker: one full-width page (paged=false) over the
|
|
652
|
+
// `applyStyle` option domain — the 3 powerline shapes. closeOnPick folds a
|
|
653
|
+
// page-reset into the apply, so a pick reshapes the bar and closes the row
|
|
654
|
+
// in one click. The active shape is marked by the picker helper.
|
|
655
|
+
stylePicker: {
|
|
656
|
+
template: '{{ picker "applyStyle" "stylePage" true false }}',
|
|
657
|
+
bg: "surface",
|
|
658
|
+
fg: "foreground",
|
|
659
|
+
},
|
|
626
660
|
},
|
|
627
661
|
|
|
628
|
-
// Default layout —
|
|
629
|
-
// A
|
|
630
|
-
//
|
|
631
|
-
//
|
|
632
|
-
//
|
|
633
|
-
//
|
|
662
|
+
// Default layout — the canonical LayoutNode tree (`satisfies DslConfig`
|
|
663
|
+
// requires the lowered form here; the terse Option-A `{ h/v/seg }` grammar is
|
|
664
|
+
// the loader's authoring surface for user JSON, not this typed literal).
|
|
665
|
+
// [LAW:dataflow-not-control-flow] Two rows: an always-on control row, and a
|
|
666
|
+
// picker reveal row that EXISTS only while the style menu cursor is ≥ 0 — the
|
|
667
|
+
// row's presence is a value test on stylePage, not a branch in render code.
|
|
668
|
+
// The picker itself draws the ✕/←/→ affordances from the page + term width.
|
|
634
669
|
root: {
|
|
635
670
|
kind: "container",
|
|
636
|
-
direction: "
|
|
671
|
+
direction: "vertical",
|
|
637
672
|
children: [
|
|
638
|
-
{
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
673
|
+
{
|
|
674
|
+
kind: "container",
|
|
675
|
+
direction: "horizontal",
|
|
676
|
+
children: [
|
|
677
|
+
{ kind: "segment", name: "directory" },
|
|
678
|
+
{ kind: "segment", name: "git" },
|
|
679
|
+
{ kind: "segment", name: "model" },
|
|
680
|
+
{ kind: "segment", name: "session" },
|
|
681
|
+
{ kind: "segment", name: "today" },
|
|
682
|
+
{ kind: "segment", name: "context" },
|
|
683
|
+
{ kind: "segment", name: "toolbar" },
|
|
684
|
+
{ kind: "segment", name: "styleControl" },
|
|
685
|
+
],
|
|
686
|
+
},
|
|
687
|
+
{
|
|
688
|
+
kind: "segment",
|
|
689
|
+
name: "stylePicker",
|
|
690
|
+
when: "{{ ge (int .stylePage) 0 }}",
|
|
691
|
+
},
|
|
645
692
|
],
|
|
646
693
|
},
|
|
647
694
|
|
|
@@ -664,6 +711,17 @@ export const DEFAULT_DSL_CONFIG = {
|
|
|
664
711
|
copyDir: { copy: "{{ .current_dir }}" },
|
|
665
712
|
openProject: { open: "{{ .project_dir }}" },
|
|
666
713
|
openTranscript: { open: "{{ .transcript_path }}" },
|
|
714
|
+
|
|
715
|
+
// [LAW:locality-or-seam] The style picker's behaviors, decoupled by NAME
|
|
716
|
+
// from styleControl/stylePicker above. Three declarations, all gated by
|
|
717
|
+
// derivation (deriveActionValidators): openStyleMenu/stylePage write the
|
|
718
|
+
// page cursor (a literal page-open subsumed by the int gate); applyStyle
|
|
719
|
+
// writes the chosen shape, gated to the STRIP_STYLES allow-list because its
|
|
720
|
+
// value source is `from: "styles"`. The rendered click and the wire gate
|
|
721
|
+
// share that one source — a template cannot smuggle an un-gated style write.
|
|
722
|
+
openStyleMenu: { set: "style-page", to: "0" },
|
|
723
|
+
applyStyle: { set: "style", from: "styles" },
|
|
724
|
+
stylePage: { set: "style-page", int: true },
|
|
667
725
|
},
|
|
668
726
|
|
|
669
727
|
// [LAW:single-enforcer] / [LAW:one-source-of-truth] Display-formatting policy
|
package/src/config/dsl-types.ts
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
// references it here. The dependency is one-way (this file → action.ts), never
|
|
15
15
|
// the reverse, so that shape can be lifted out without a cycle.
|
|
16
16
|
import type { ActionDecl } from "./action.js";
|
|
17
|
+
import type { StripStyle } from "../themes/policy.js";
|
|
17
18
|
|
|
18
19
|
// [LAW:types-are-the-program] Three stages, three names.
|
|
19
20
|
//
|
|
@@ -180,6 +181,14 @@ export interface Globals {
|
|
|
180
181
|
// `sessionState.theme ?? globals.palette ?? default`, and a per-segment
|
|
181
182
|
// `palette` is an explicit override that ignores the session theme.
|
|
182
183
|
readonly palette?: string;
|
|
184
|
+
|
|
185
|
+
// [LAW:one-type-per-behavior] The config default for the powerline cap/
|
|
186
|
+
// separator SHAPE — the exact twin of `palette` one dimension over: the
|
|
187
|
+
// daemon resolves the live strip style per render as
|
|
188
|
+
// `sessionState.style ?? globals.style ?? "powerline"` (effectiveStripStyle),
|
|
189
|
+
// so a style click reshapes the bar live and a config can set the default
|
|
190
|
+
// shape without an edit-per-session.
|
|
191
|
+
readonly style?: StripStyle;
|
|
183
192
|
}
|
|
184
193
|
|
|
185
194
|
// [LAW:one-type-per-behavior] One discriminated union covers every source
|
|
@@ -4,7 +4,9 @@
|
|
|
4
4
|
// add a key to GLOBALS_SCHEMA and Globals; the engine does the rest.
|
|
5
5
|
|
|
6
6
|
import { type Globals } from "../dsl-types.js";
|
|
7
|
+
import { STRIP_STYLES } from "../../themes/policy.js";
|
|
7
8
|
import {
|
|
9
|
+
optionalEnumSpec,
|
|
8
10
|
optionalStringSpec,
|
|
9
11
|
paletteSpec,
|
|
10
12
|
record,
|
|
@@ -23,6 +25,10 @@ const GLOBALS_SCHEMA: RecordSchema<Globals> = {
|
|
|
23
25
|
default_separator: optionalStringSpec(),
|
|
24
26
|
default_truncate_marker: optionalStringSpec(),
|
|
25
27
|
palette: paletteSpec(),
|
|
28
|
+
// [LAW:types-are-the-program] The strip style is a CLOSED enum (the powerline
|
|
29
|
+
// shapes the joiner can render), unlike the open-ended palette NAME — so it
|
|
30
|
+
// validates by membership and emits a JSON-Schema `enum`.
|
|
31
|
+
style: optionalEnumSpec(STRIP_STYLES),
|
|
26
32
|
},
|
|
27
33
|
};
|
|
28
34
|
|
package/src/daemon/server.ts
CHANGED
|
@@ -39,7 +39,11 @@ import { buildDebugSnapshot } from "./debug";
|
|
|
39
39
|
import { DEBUG_WHATS, isDebugWhat } from "./debug-types";
|
|
40
40
|
import { expandHome } from "../config/dsl-loader.js";
|
|
41
41
|
import { renderDsl } from "../dsl/render.js";
|
|
42
|
-
import {
|
|
42
|
+
import {
|
|
43
|
+
effectiveStripStyle,
|
|
44
|
+
effectiveThemeName,
|
|
45
|
+
resolverForThemeName,
|
|
46
|
+
} from "../themes/index.js";
|
|
43
47
|
import {
|
|
44
48
|
renderStripCells,
|
|
45
49
|
DEFAULT_TERMINAL_WIDTH,
|
|
@@ -753,6 +757,16 @@ async function handleRequest(req: Request): Promise<HandledRequest> {
|
|
|
753
757
|
entry.state.config.globals.palette,
|
|
754
758
|
),
|
|
755
759
|
);
|
|
760
|
+
// [LAW:one-type-per-behavior][LAW:dataflow-not-control-flow] The powerline
|
|
761
|
+
// cap/separator SHAPE, resolved per render the exact way the theme is: the
|
|
762
|
+
// session's clicked style (SessionState) over the config default over the
|
|
763
|
+
// "powerline" floor — so a style click reshapes the whole bar on the next
|
|
764
|
+
// render. The base style on renderOpts is only the floor; this is the live
|
|
765
|
+
// override fed to the one joiner dispatch in renderStripCells.
|
|
766
|
+
renderOpts.style = effectiveStripStyle(
|
|
767
|
+
sessionState.get(req.hookData.session_id, "style"),
|
|
768
|
+
entry.state.config.globals.style,
|
|
769
|
+
);
|
|
756
770
|
// [LAW:single-enforcer] renderDsl internally calls
|
|
757
771
|
// `registry.applyInput(payload)` as its first step (see step 1 in
|
|
758
772
|
// src/dsl/render.ts). The daemon must not pre-apply — doing so
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
// numeric stepper with int-range bounds), this same shape extends — the
|
|
25
25
|
// validator becomes the parsing boundary, the verb body the dataflow.
|
|
26
26
|
|
|
27
|
-
import { listResolvablePaletteNames,
|
|
27
|
+
import { listResolvablePaletteNames, STRIP_STYLES } from "../../themes/policy";
|
|
28
28
|
import type { ActionDecl, OptionSource } from "../../config/action";
|
|
29
29
|
import type { DslConfig } from "../../config/dsl-types";
|
|
30
30
|
|
|
@@ -88,13 +88,13 @@ export type DerivedValidatorSpec =
|
|
|
88
88
|
//
|
|
89
89
|
// [LAW:single-enforcer] Each validator's accepted-set is one constant
|
|
90
90
|
// lookup structure — a Set for O(1) `has` (matching the BOOLEAN_*
|
|
91
|
-
// validators below). The theme registry (rich-js THEMES) and
|
|
91
|
+
// validators below). The theme registry (rich-js THEMES) and STRIP_STYLES
|
|
92
92
|
// are module-init-static, so caching at module load is correct by
|
|
93
93
|
// construction; the (list, set) pair is built from the same source so
|
|
94
94
|
// the error-message ordering and the lookup membership cannot drift.
|
|
95
95
|
const RESOLVABLE_THEMES_LIST: readonly string[] = listResolvablePaletteNames();
|
|
96
96
|
const RESOLVABLE_THEMES: ReadonlySet<string> = new Set(RESOLVABLE_THEMES_LIST);
|
|
97
|
-
const RESOLVABLE_STYLES: ReadonlySet<string> = new Set(
|
|
97
|
+
const RESOLVABLE_STYLES: ReadonlySet<string> = new Set(STRIP_STYLES);
|
|
98
98
|
|
|
99
99
|
const validateTheme: KeyValidator = (raw) => {
|
|
100
100
|
if (!raw) return { ok: false, reason: "theme name is required" };
|
|
@@ -112,7 +112,7 @@ const validateStyle: KeyValidator = (raw) => {
|
|
|
112
112
|
if (!RESOLVABLE_STYLES.has(raw)) {
|
|
113
113
|
return {
|
|
114
114
|
ok: false,
|
|
115
|
-
reason: `unknown style "${raw}" (have: ${
|
|
115
|
+
reason: `unknown style "${raw}" (have: ${STRIP_STYLES.join(", ")})`,
|
|
116
116
|
};
|
|
117
117
|
}
|
|
118
118
|
return { ok: true, value: raw };
|
|
@@ -446,7 +446,7 @@ export function makeRangeValidator(
|
|
|
446
446
|
// style validators consult — the rendered options and the derived gate cannot
|
|
447
447
|
// diverge because there is no second enumeration.
|
|
448
448
|
function optionValuesFor(src: OptionSource): readonly string[] {
|
|
449
|
-
return src === "themes" ? RESOLVABLE_THEMES_LIST :
|
|
449
|
+
return src === "themes" ? RESOLVABLE_THEMES_LIST : STRIP_STYLES;
|
|
450
450
|
}
|
|
451
451
|
|
|
452
452
|
// [LAW:types-are-the-program] Collapse one key's spec contributions into the
|
package/src/demo/dsl.ts
CHANGED
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
} from "../config/dsl-loader.js";
|
|
28
28
|
import { VariableStore } from "../var-system/store.js";
|
|
29
29
|
import { SourceRegistry } from "../var-system/sources.js";
|
|
30
|
+
import { SessionState } from "../daemon/session-state.js";
|
|
30
31
|
import { listResolvablePaletteNames } from "../themes/policy.js";
|
|
31
32
|
import { effectiveThemeName, resolverForThemeName } from "../themes/index.js";
|
|
32
33
|
import { registerDslConfig, renderDsl } from "../dsl/render.js";
|
|
@@ -75,7 +76,12 @@ const basePalette = resolverForThemeName(
|
|
|
75
76
|
// registry, so dispose() must run even if registration or rendering throws —
|
|
76
77
|
// otherwise those handles keep the process alive. try/finally guarantees it.
|
|
77
78
|
const store = new VariableStore();
|
|
78
|
-
|
|
79
|
+
// [LAW:no-silent-failure] An EMPTY SessionState — `kind: "state"` variables
|
|
80
|
+
// (the default config's style picker, any interactive config) require one at
|
|
81
|
+
// registration; without it declareState fails and the segment renders an error
|
|
82
|
+
// cell. The demo never clicks, so an empty store is correct: every state var
|
|
83
|
+
// resolves to its declared default (closed pickers, "(default)" labels).
|
|
84
|
+
const registry = new SourceRegistry(store, "", undefined, new SessionState());
|
|
79
85
|
try {
|
|
80
86
|
const compiled = registerDslConfig(config, registry, {
|
|
81
87
|
cwd: process.cwd(),
|
package/src/render/action.ts
CHANGED
|
@@ -26,7 +26,7 @@ import type { VariableStore } from "../var-system/store.js";
|
|
|
26
26
|
import { toString as varToString } from "../var-system/types.js";
|
|
27
27
|
import { buildScope } from "../template-engine/scope.js";
|
|
28
28
|
import type { ActionDecl, OptionSource } from "../config/action.js";
|
|
29
|
-
import { listResolvablePaletteNames,
|
|
29
|
+
import { listResolvablePaletteNames, STRIP_STYLES } from "../themes/policy.js";
|
|
30
30
|
import {
|
|
31
31
|
effectsUrl,
|
|
32
32
|
VERB_COPY,
|
|
@@ -107,7 +107,7 @@ export type CompiledActions = ReadonlyMap<string, CompiledActionDecl>;
|
|
|
107
107
|
// options and the gate cannot diverge. The render-side resolver (the daemon's
|
|
108
108
|
// validator-derivation has its own that must agree, both reading themes/policy).
|
|
109
109
|
export function optionDomain(src: OptionSource): readonly string[] {
|
|
110
|
-
return src === "themes" ? listResolvablePaletteNames() :
|
|
110
|
+
return src === "themes" ? listResolvablePaletteNames() : STRIP_STYLES;
|
|
111
111
|
}
|
|
112
112
|
|
|
113
113
|
// [LAW:locality-or-seam] The runtime holder the `action` template function closes
|
package/src/render/strip.ts
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
type Joiner,
|
|
11
11
|
type ColorSystemSpec,
|
|
12
12
|
} from "@promptctl/rich-js";
|
|
13
|
+
import type { StripStyle } from "../themes/policy.js";
|
|
13
14
|
|
|
14
15
|
export interface RenderedSegmentLike {
|
|
15
16
|
type: string;
|
|
@@ -18,7 +19,11 @@ export interface RenderedSegmentLike {
|
|
|
18
19
|
fgHex?: string;
|
|
19
20
|
}
|
|
20
21
|
|
|
21
|
-
|
|
22
|
+
// [LAW:one-source-of-truth] `StripStyle` and its value list live in
|
|
23
|
+
// themes/policy.ts (the style-identifier policy module, importable by the
|
|
24
|
+
// option-source machinery without a render→template-engine cycle). Re-exported
|
|
25
|
+
// here so render-layer consumers can keep importing it from the strip module.
|
|
26
|
+
export type { StripStyle };
|
|
22
27
|
|
|
23
28
|
// [LAW:one-source-of-truth] Raw terminal cols we assume when the wire
|
|
24
29
|
// didn't give us one (older client, env-stripped spawn). RAW — not
|
|
@@ -38,13 +43,22 @@ export interface BuildLineOptions {
|
|
|
38
43
|
}
|
|
39
44
|
|
|
40
45
|
function pickJoiner(style: StripStyle, separator?: string): Joiner {
|
|
41
|
-
// [LAW:dataflow-not-control-flow] joiner choice is data-driven; one
|
|
42
|
-
//
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
+
// [LAW:dataflow-not-control-flow] joiner choice is data-driven; one arm per
|
|
47
|
+
// shape. [LAW:types-are-the-program] Total over StripStyle — the `never`
|
|
48
|
+
// default makes adding a STRIP_STYLES member a compile error here until it
|
|
49
|
+
// gets a joiner, so the picker's domain can never offer an unrenderable shape.
|
|
50
|
+
switch (style) {
|
|
51
|
+
case "capsule":
|
|
52
|
+
return new CapsuleJoiner();
|
|
53
|
+
case "plain":
|
|
54
|
+
return new PlainJoiner(separator !== undefined ? { separator } : {});
|
|
55
|
+
case "powerline":
|
|
56
|
+
return new PowerlineJoiner();
|
|
57
|
+
default: {
|
|
58
|
+
const _exhaustive: never = style;
|
|
59
|
+
return _exhaustive;
|
|
60
|
+
}
|
|
46
61
|
}
|
|
47
|
-
return new PowerlineJoiner();
|
|
48
62
|
}
|
|
49
63
|
|
|
50
64
|
function toCell(seg: RenderedSegmentLike): RichText {
|
|
@@ -16,22 +16,22 @@ import {
|
|
|
16
16
|
formatModelName,
|
|
17
17
|
shortenModelName,
|
|
18
18
|
} from "../utils/formatters.js";
|
|
19
|
-
import { listResolvablePaletteNames,
|
|
19
|
+
import { listResolvablePaletteNames, STRIP_STYLES } from "../themes/policy.js";
|
|
20
20
|
|
|
21
21
|
// [LAW:one-source-of-truth] The DSL `themes()` and `styles()` bindings
|
|
22
22
|
// project the SAME canonical sources the set-state validator consults
|
|
23
|
-
// (listResolvablePaletteNames /
|
|
23
|
+
// (listResolvablePaletteNames / STRIP_STYLES). A picker (or a config that
|
|
24
24
|
// `range`s over themes() to emit OSC-8 cells) iterates the allow-list the
|
|
25
25
|
// validator will enforce on the resulting click — the list and the gate cannot
|
|
26
26
|
// diverge because there is no second list.
|
|
27
27
|
//
|
|
28
28
|
// Module-init caching is correct by construction: rich-js THEMES is a
|
|
29
29
|
// static import (no dynamic palette registration at runtime) and
|
|
30
|
-
//
|
|
30
|
+
// STRIP_STYLES is a const array. The "reactivity" requirement from the
|
|
31
31
|
// ticket is satisfied vacuously — the lists never change during a
|
|
32
32
|
// daemon lifetime, so a cached snapshot IS the current truth.
|
|
33
33
|
const THEMES_LIST: readonly string[] = listResolvablePaletteNames();
|
|
34
|
-
const STYLES_LIST: readonly string[] = [...
|
|
34
|
+
const STYLES_LIST: readonly string[] = [...STRIP_STYLES];
|
|
35
35
|
|
|
36
36
|
// Normalize an engine-supplied numeric argument. The "number" argType admits
|
|
37
37
|
// both number and bigint (per @promptctl/go-template-js); the underlying
|
package/src/themes/index.ts
CHANGED
|
@@ -6,12 +6,16 @@
|
|
|
6
6
|
export {
|
|
7
7
|
resolvePaletteName,
|
|
8
8
|
effectiveThemeName,
|
|
9
|
+
effectiveStripStyle,
|
|
10
|
+
isStripStyle,
|
|
9
11
|
listResolvablePaletteNames,
|
|
10
12
|
listAvailableThemes,
|
|
11
13
|
pickRandomTheme,
|
|
14
|
+
STRIP_STYLES,
|
|
12
15
|
STYLE_ORDER,
|
|
13
16
|
DISPLAY_STYLES,
|
|
14
17
|
} from "./policy.js";
|
|
18
|
+
export type { StripStyle } from "./policy.js";
|
|
15
19
|
|
|
16
20
|
export {
|
|
17
21
|
resolverForThemeName,
|
package/src/themes/policy.ts
CHANGED
|
@@ -55,8 +55,43 @@ export function listAvailableThemes(): string[] {
|
|
|
55
55
|
return [...allNames].sort();
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
// ---
|
|
58
|
+
// --- Powerline strip-style identifiers ---
|
|
59
59
|
|
|
60
|
+
// [LAW:one-source-of-truth][LAW:types-are-the-program] The single canonical set
|
|
61
|
+
// of powerline cap/separator shapes a render can take. The `StripStyle` type is
|
|
62
|
+
// DERIVED from this const, so the picker's option domain, the SessionState
|
|
63
|
+
// validator, the `styles()` template binding, and `pickJoiner`'s dispatch all
|
|
64
|
+
// trace to one literal — adding a shape here forces a new `pickJoiner` arm at
|
|
65
|
+
// compile time (the joiner switch is total over `StripStyle`). This is where the
|
|
66
|
+
// drift between "what you can pick" and "what actually renders" is closed.
|
|
67
|
+
export const STRIP_STYLES = ["powerline", "capsule", "plain"] as const;
|
|
68
|
+
export type StripStyle = (typeof STRIP_STYLES)[number];
|
|
69
|
+
|
|
70
|
+
// [LAW:types-are-the-program] The trust-boundary narrowing from a raw
|
|
71
|
+
// SessionState string (or a config default) to the closed `StripStyle` union.
|
|
72
|
+
export function isStripStyle(value: string): value is StripStyle {
|
|
73
|
+
return (STRIP_STYLES as readonly string[]).includes(value);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// The strip style a render should use, as data. [LAW:dataflow-not-control-flow]
|
|
77
|
+
// [LAW:one-type-per-behavior] The exact shape of `effectiveThemeName`, one
|
|
78
|
+
// dimension over: session choice over config default over the "powerline" floor,
|
|
79
|
+
// no "if the session has a style" branch. A value outside the domain (a stale
|
|
80
|
+
// SessionState entry from a prior option vocabulary) collapses to the floor —
|
|
81
|
+
// `pickJoiner` would render it as powerline anyway, so the floor keeps the
|
|
82
|
+
// returned type honest rather than silently widening.
|
|
83
|
+
export function effectiveStripStyle(
|
|
84
|
+
sessionStyle: string | null,
|
|
85
|
+
globalsStyle: StripStyle | undefined,
|
|
86
|
+
): StripStyle {
|
|
87
|
+
const chosen = sessionStyle ?? globalsStyle ?? "powerline";
|
|
88
|
+
return isStripStyle(chosen) ? chosen : "powerline";
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// --- Legacy palette-preset identifiers (per-session random feature only) ---
|
|
92
|
+
// [LAW:no-silent-failure] NOT the picker's style domain — these were the
|
|
93
|
+
// pre-DSL bg/fg derivation presets, surviving only as the random pool for
|
|
94
|
+
// session-random.ts. The interactive style picker resolves over STRIP_STYLES.
|
|
60
95
|
export const STYLE_ORDER: readonly string[] = [
|
|
61
96
|
"surface",
|
|
62
97
|
"muted",
|