@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.
- package/dist/index.mjs +72 -71
- package/package.json +5 -6
- package/src/check.ts +0 -478
- package/src/cli-flags.ts +0 -8
- package/src/click/wire.ts +0 -158
- package/src/config/action.ts +0 -329
- package/src/config/cli.ts +0 -71
- package/src/config/default-dsl-config.ts +0 -1645
- package/src/config/disclosure.ts +0 -170
- package/src/config/dsl-loader.ts +0 -339
- package/src/config/dsl-types.ts +0 -581
- package/src/config/edit-chrome.ts +0 -559
- package/src/config/help.ts +0 -151
- package/src/config/ident.ts +0 -22
- package/src/config/layout-ops.ts +0 -177
- package/src/config/loader/actions.ts +0 -972
- package/src/config/loader/cache.ts +0 -206
- package/src/config/loader/cross-ref.ts +0 -714
- package/src/config/loader/cycles.ts +0 -148
- package/src/config/loader/diagnostics.ts +0 -99
- package/src/config/loader/discovery.ts +0 -182
- package/src/config/loader/edit-mode.ts +0 -137
- package/src/config/loader/emit-schema.ts +0 -68
- package/src/config/loader/globals.ts +0 -269
- package/src/config/loader/helpers.ts +0 -48
- package/src/config/loader/layout.ts +0 -693
- package/src/config/loader/looks.ts +0 -96
- package/src/config/loader/menu-synth.ts +0 -435
- package/src/config/loader/merge.ts +0 -115
- package/src/config/loader/persist-target.ts +0 -67
- package/src/config/loader/presets.ts +0 -119
- package/src/config/loader/refs.ts +0 -100
- package/src/config/loader/reserved-namespace.ts +0 -38
- package/src/config/loader/segments.ts +0 -120
- package/src/config/loader/validate-core.ts +0 -737
- package/src/config/loader/variables.ts +0 -260
- package/src/config/menu-keys.ts +0 -139
- package/src/config/option-domain.ts +0 -164
- package/src/config/presets.ts +0 -326
- package/src/config/settings-menu.ts +0 -775
- package/src/daemon/acquire.ts +0 -684
- package/src/daemon/cache/git.ts +0 -649
- package/src/daemon/cache/render.ts +0 -623
- package/src/daemon/cache/session-usage-store.ts +0 -720
- package/src/daemon/cache/watchers.ts +0 -249
- package/src/daemon/client-debug.ts +0 -120
- package/src/daemon/client-stats.ts +0 -130
- package/src/daemon/client-transport.ts +0 -273
- package/src/daemon/client.ts +0 -78
- package/src/daemon/config-overrides-store.ts +0 -663
- package/src/daemon/debug-types.ts +0 -91
- package/src/daemon/debug.ts +0 -264
- package/src/daemon/fork-bomb-breaker.ts +0 -351
- package/src/daemon/limits.ts +0 -211
- package/src/daemon/log.ts +0 -81
- package/src/daemon/parent-watchdog.ts +0 -87
- package/src/daemon/paths.ts +0 -211
- package/src/daemon/process-fingerprint.ts +0 -146
- package/src/daemon/protocol.ts +0 -292
- package/src/daemon/render-payload.ts +0 -1256
- package/src/daemon/server.ts +0 -1330
- package/src/daemon/session-state-file.ts +0 -108
- package/src/daemon/session-state.ts +0 -237
- package/src/daemon/socket-lease.ts +0 -209
- package/src/daemon/socket-ownership.ts +0 -209
- package/src/daemon/stats.ts +0 -235
- package/src/daemon/verbs/config-validators.ts +0 -250
- package/src/daemon/verbs/index.ts +0 -706
- package/src/daemon/verbs/state-validators.ts +0 -249
- package/src/daemon/verbs/validator-registry.ts +0 -457
- package/src/demo/dsl.ts +0 -143
- package/src/demo/mock-data.ts +0 -67
- package/src/demo/statusline.json5 +0 -94
- package/src/dsl/node-registry.ts +0 -374
- package/src/dsl/render.ts +0 -803
- package/src/help-text.ts +0 -90
- package/src/index.ts +0 -210
- package/src/install/currency.ts +0 -197
- package/src/install/index.ts +0 -557
- package/src/proc/launch.ts +0 -459
- package/src/proc/stats-handle.ts +0 -13
- package/src/render/action.ts +0 -883
- package/src/render/active-segment.ts +0 -78
- package/src/render/diagnostic-style.ts +0 -23
- package/src/render/diagnostic-text.ts +0 -77
- package/src/render/error-glyph.ts +0 -53
- package/src/render/menu.ts +0 -257
- package/src/render/outcome-plan.ts +0 -45
- package/src/render/picker.ts +0 -372
- package/src/render/segment-color.ts +0 -74
- package/src/render/split-lines.ts +0 -51
- package/src/render/strip.ts +0 -228
- package/src/segments/cache.ts +0 -131
- package/src/segments/context.ts +0 -190
- package/src/segments/git.ts +0 -1084
- package/src/segments/metrics.ts +0 -187
- package/src/segments/pricing.ts +0 -452
- package/src/segments/session.ts +0 -23
- package/src/segments/tmux.ts +0 -74
- package/src/template-engine/cells.ts +0 -90
- package/src/template-engine/colors.ts +0 -124
- package/src/template-engine/engine.ts +0 -108
- package/src/template-engine/funcs.ts +0 -232
- package/src/template-engine/index.ts +0 -11
- package/src/template-engine/layout.ts +0 -133
- package/src/template-engine/scope.ts +0 -62
- package/src/template-engine/sparkline.ts +0 -79
- package/src/themes/index.ts +0 -20
- package/src/themes/palette-resolvers.ts +0 -84
- package/src/themes/policy.ts +0 -393
- package/src/utils/cache.ts +0 -206
- package/src/utils/claude.ts +0 -683
- package/src/utils/color-support.ts +0 -118
- package/src/utils/formatters.ts +0 -99
- package/src/utils/logger.ts +0 -5
- package/src/utils/outcome.ts +0 -33
- package/src/utils/schema-validator.ts +0 -126
- package/src/utils/single-flight.ts +0 -57
- package/src/utils/terminal-width.ts +0 -51
- package/src/utils/terminal.ts +0 -11
- package/src/utils/transcript-fs.ts +0 -279
- package/src/var-system/index.ts +0 -24
- package/src/var-system/sources.ts +0 -1047
- package/src/var-system/store.ts +0 -223
- package/src/var-system/types.ts +0 -57
- package/src/version.ts +0 -17
|
@@ -1,133 +0,0 @@
|
|
|
1
|
-
// [LAW:single-enforcer] All per-segment width/justify/truncate enforcement
|
|
2
|
-
// runs through applySegmentLayout. RichText owns the slice/pad/truncate
|
|
3
|
-
// primitives; this function chooses which to call from the segment-level
|
|
4
|
-
// options. No second path exists.
|
|
5
|
-
//
|
|
6
|
-
// [LAW:dataflow-not-control-flow] Every step is unconditional in shape;
|
|
7
|
-
// option values (width, justify, truncate) decide what the output is, not
|
|
8
|
-
// whether the step runs. "auto" width is not a branch that skips logic —
|
|
9
|
-
// it is a value that selects "collapse only, no resize" over "collapse then
|
|
10
|
-
// size to width".
|
|
11
|
-
//
|
|
12
|
-
// [LAW:types-are-the-program] With RichText as the cell type, every layout
|
|
13
|
-
// operation is span-preserving by construction. There is no rebuild path,
|
|
14
|
-
// no slice-then-restyle dance: `richText.truncate({width, mode, marker})`
|
|
15
|
-
// and `richText.align(justify, width)` clip and shift spans through every
|
|
16
|
-
// cut. The bzh.9 limitation (truncation drops per-part fg) cannot be
|
|
17
|
-
// expressed in this shape — its preconditions don't exist.
|
|
18
|
-
|
|
19
|
-
import { RichText } from "@promptctl/rich-js";
|
|
20
|
-
import type { Style } from "@promptctl/rich-js";
|
|
21
|
-
import type { Template } from "@promptctl/go-template-js";
|
|
22
|
-
|
|
23
|
-
export type JustifyMode = "left" | "center" | "right";
|
|
24
|
-
export type TruncateMode = "right" | "left" | "middle";
|
|
25
|
-
|
|
26
|
-
export interface SegmentLayoutOptions {
|
|
27
|
-
/** "auto" → content-sized; a positive integer → fixed terminal-cell width. */
|
|
28
|
-
width: "auto" | number;
|
|
29
|
-
/** Alignment within a fixed-width segment. Ignored when width is "auto". */
|
|
30
|
-
justify: JustifyMode;
|
|
31
|
-
/** Overflow strategy when content exceeds a fixed width. Ignored when "auto". */
|
|
32
|
-
truncate: TruncateMode;
|
|
33
|
-
/**
|
|
34
|
-
* Spaces synthesized inside the collapsed cell on each side (the resolved
|
|
35
|
-
* globals.padding). [LAW:types-are-the-program] Required so every layout
|
|
36
|
-
* site states the value — the walk threads the one render-wide resolution;
|
|
37
|
-
* there is no per-site re-default. Applied BEFORE width sizing, so padding
|
|
38
|
-
* sits inside a fixed `width` (the legacy intra-cell semantics: the joiners
|
|
39
|
-
* sit between cells; padding sits inside the bg fill).
|
|
40
|
-
*/
|
|
41
|
-
padding: number;
|
|
42
|
-
/** Glyph inserted at the overflow cut point. Default "…". */
|
|
43
|
-
truncateMarker?: string;
|
|
44
|
-
/**
|
|
45
|
-
* Style for synthesized whitespace — RichText pads using plain spaces.
|
|
46
|
-
* The padding inherits the cell's wrapping style at render time, so the
|
|
47
|
-
* segment bg/fg is continuous across padded gaps without a second style
|
|
48
|
-
* assignment here.
|
|
49
|
-
*/
|
|
50
|
-
baseStyle?: Style;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Evaluate a `when` predicate template against `scope`.
|
|
55
|
-
* Returns false only when the evaluated text equals the string "false".
|
|
56
|
-
* A missing template means the segment is always visible.
|
|
57
|
-
*
|
|
58
|
-
* [LAW:dataflow-not-control-flow] Visibility is a value that flows out of the
|
|
59
|
-
* template engine. The engine always runs; the output value decides visibility.
|
|
60
|
-
*/
|
|
61
|
-
export function evaluateWhen(
|
|
62
|
-
template: Template<RichText> | undefined,
|
|
63
|
-
scope: object,
|
|
64
|
-
): boolean {
|
|
65
|
-
if (template === undefined) return true;
|
|
66
|
-
const fragments = template.evaluate(scope);
|
|
67
|
-
return fragments.map((f) => f.plain).join("") !== "false";
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Collapse a visual line's cells into the ONE strip item the unit contributes
|
|
72
|
-
* for that line. [LAW:single-enforcer] A unit (segment, or an inline leaf) is
|
|
73
|
-
* one strip item — the powerline joiner caps BETWEEN units, never inside one,
|
|
74
|
-
* so a unit's interior bg/fg variation is paint, not a structural seam the
|
|
75
|
-
* joiner reads. Each input cell's wrapping style becomes a span over its range
|
|
76
|
-
* and its interior spans (including OSC-8 links) carry through, so every
|
|
77
|
-
* clickable region survives as its own span — serialized as one OSC-8 region
|
|
78
|
-
* each. `baseStyle` is the wrapping default so synthesized padding and gaps
|
|
79
|
-
* inherit the unit's bg.
|
|
80
|
-
*/
|
|
81
|
-
function collapseToCell(
|
|
82
|
-
cells: readonly RichText[],
|
|
83
|
-
baseStyle?: Style,
|
|
84
|
-
): RichText {
|
|
85
|
-
const merged = RichText.fromFragments(cells);
|
|
86
|
-
merged.end = "";
|
|
87
|
-
merged.noWrap = true;
|
|
88
|
-
if (baseStyle !== undefined && !baseStyle.isNull) merged.style = baseStyle;
|
|
89
|
-
return merged;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Lay out one segment visual line: collapse its cells into a single strip
|
|
94
|
-
* item, then size that item to the requested width. Returns `[]` for an empty
|
|
95
|
-
* line (a unit that rendered nothing contributes no strip item) or `[cell]`
|
|
96
|
-
* for one — never more, so the caller's branchless spread handles both.
|
|
97
|
-
*
|
|
98
|
-
* [LAW:dataflow-not-control-flow] `width` is the value that selects the sizing
|
|
99
|
-
* op: "auto" keeps the content-sized cell as-is; a fixed width truncates when
|
|
100
|
-
* over and pad-aligns when under. Truncation/align are span-preserving, so the
|
|
101
|
-
* collapsed link structure survives every cut.
|
|
102
|
-
*/
|
|
103
|
-
export function applySegmentLayout(
|
|
104
|
-
cells: readonly RichText[],
|
|
105
|
-
options: SegmentLayoutOptions,
|
|
106
|
-
): RichText[] {
|
|
107
|
-
const {
|
|
108
|
-
width,
|
|
109
|
-
justify,
|
|
110
|
-
truncate,
|
|
111
|
-
truncateMarker = "…",
|
|
112
|
-
baseStyle,
|
|
113
|
-
padding,
|
|
114
|
-
} = options;
|
|
115
|
-
|
|
116
|
-
if (cells.length === 0) return [];
|
|
117
|
-
|
|
118
|
-
// [LAW:one-source-of-truth] The DSL path's ONE intra-cell padding
|
|
119
|
-
// application — the same rich-js pad primitive toCell (render/strip.ts)
|
|
120
|
-
// uses on the legacy shape, threading the same resolved globals.padding.
|
|
121
|
-
// pad() shifts spans, so OSC-8 link regions survive; the spaces inherit
|
|
122
|
-
// the cell's wrapping style, so the segment bg is continuous.
|
|
123
|
-
const cell = collapseToCell(cells, baseStyle).pad(padding);
|
|
124
|
-
if (width === "auto") return [cell];
|
|
125
|
-
|
|
126
|
-
if (cell.cellLength > width) {
|
|
127
|
-
cell.truncate(width, { mode: truncate, marker: truncateMarker });
|
|
128
|
-
} else if (cell.cellLength < width) {
|
|
129
|
-
cell.align(justify, width);
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
return [cell];
|
|
133
|
-
}
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
// [LAW:dataflow-not-control-flow] The scope Proxy converts a flat variable
|
|
2
|
-
// store (keys like "session.id", "git.branch", "cwd") into the nested object
|
|
3
|
-
// shape the template engine expects for ".session.id" field access.
|
|
4
|
-
// No data is materialised — the proxy navigates namespace prefixes lazily and
|
|
5
|
-
// calls store.read() only when a leaf is reached, so MobX dependency tracking
|
|
6
|
-
// fires at the point of actual reads inside a computed body.
|
|
7
|
-
//
|
|
8
|
-
// The engine's getField() uses `name in obj` before `obj[name]`, so the
|
|
9
|
-
// proxy must define a `has` trap as well as `get`.
|
|
10
|
-
|
|
11
|
-
import type { VariableStore } from "../var-system/store.js";
|
|
12
|
-
|
|
13
|
-
// Build the scope object the engine receives as `.` (dot).
|
|
14
|
-
// Call once per render; the returned object is a read-only view of the store
|
|
15
|
-
// at evaluation time — do not cache across renders.
|
|
16
|
-
export function buildScope(store: VariableStore): object {
|
|
17
|
-
const names = new Set(store.names());
|
|
18
|
-
return makeProxy(store, names, "");
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function makeProxy(
|
|
22
|
-
store: VariableStore,
|
|
23
|
-
names: Set<string>,
|
|
24
|
-
prefix: string,
|
|
25
|
-
): object {
|
|
26
|
-
return new Proxy(Object.create(null) as object, {
|
|
27
|
-
has(_, key: string | symbol): boolean {
|
|
28
|
-
if (typeof key !== "string") return false;
|
|
29
|
-
const fullKey = prefix ? `${prefix}.${key}` : key;
|
|
30
|
-
// Leaf: exact variable.
|
|
31
|
-
if (names.has(fullKey)) return true;
|
|
32
|
-
// Interior: a namespace prefix for at least one stored variable.
|
|
33
|
-
const nsPrefix = `${fullKey}.`;
|
|
34
|
-
for (const n of names) {
|
|
35
|
-
if (n.startsWith(nsPrefix)) return true;
|
|
36
|
-
}
|
|
37
|
-
return false;
|
|
38
|
-
},
|
|
39
|
-
|
|
40
|
-
get(_, key: string | symbol): unknown {
|
|
41
|
-
if (typeof key !== "string") return undefined;
|
|
42
|
-
const fullKey = prefix ? `${prefix}.${key}` : key;
|
|
43
|
-
|
|
44
|
-
// Leaf: exact variable in the store.
|
|
45
|
-
if (names.has(fullKey)) {
|
|
46
|
-
return store.read(fullKey);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// Interior: a namespace prefix for at least one stored variable.
|
|
50
|
-
// Return a nested proxy; MobX tracking fires only at the leaf read.
|
|
51
|
-
const nsPrefix = `${fullKey}.`;
|
|
52
|
-
for (const n of names) {
|
|
53
|
-
if (n.startsWith(nsPrefix)) {
|
|
54
|
-
return makeProxy(store, names, fullKey);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// Unknown: `has` returned false so the engine will throw MissingFieldError.
|
|
59
|
-
return undefined;
|
|
60
|
-
},
|
|
61
|
-
});
|
|
62
|
-
}
|
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
// [LAW:effects-at-boundaries] A sparkline is a PURE projection: a numeric
|
|
2
|
-
// series in, a string of block glyphs out. It reads no clock, touches no
|
|
3
|
-
// store, accumulates nothing — any history it draws is owned by the daemon
|
|
4
|
-
// cache and handed in as data (cf. session-usage-store's burn-rate ring).
|
|
5
|
-
// [LAW:one-source-of-truth] The renderer operates on number[] (the real
|
|
6
|
-
// domain); the var-system can only carry a scalar across the payload→template
|
|
7
|
-
// seam, so `parseSeries` decodes the delimited string form at that one edge —
|
|
8
|
-
// the wire shape and the domain shape have a single, tested conversion.
|
|
9
|
-
|
|
10
|
-
// The eight-level block ramp, lowest→highest. Index into this is the only
|
|
11
|
-
// place a value's normalized height becomes a glyph.
|
|
12
|
-
export const SPARK_LEVELS = [
|
|
13
|
-
"▁", // ▁
|
|
14
|
-
"▂", // ▂
|
|
15
|
-
"▃", // ▃
|
|
16
|
-
"▄", // ▄
|
|
17
|
-
"▅", // ▅
|
|
18
|
-
"▆", // ▆
|
|
19
|
-
"▇", // ▇
|
|
20
|
-
"█", // █
|
|
21
|
-
] as const;
|
|
22
|
-
|
|
23
|
-
const LEVELS = SPARK_LEVELS.length;
|
|
24
|
-
|
|
25
|
-
// Render a numeric series as a unicode mini-graph.
|
|
26
|
-
//
|
|
27
|
-
// `width` caps the glyph count to fit a cell: the MOST RECENT `width` samples
|
|
28
|
-
// are shown (tail slice), so a fixed-width cell tracks the live tail of a
|
|
29
|
-
// growing series. Omitted → every sample renders; `width <= 0` → empty.
|
|
30
|
-
//
|
|
31
|
-
// Heights are RELATIVE to the rendered window's own min/max — a sparkline shows
|
|
32
|
-
// shape, never absolute magnitude, so the full ramp is always used when the
|
|
33
|
-
// window varies. [LAW:dataflow-not-control-flow] The mapping is one unconditional
|
|
34
|
-
// fold over the values; the only data-driven value is `range`, and a flat
|
|
35
|
-
// window (range === 0) falls to the lowest tier by the same formula's limit
|
|
36
|
-
// (height-above-min is 0 for every sample), not a special-cased branch.
|
|
37
|
-
export function renderSparkline(values: number[], width?: number): string {
|
|
38
|
-
const window =
|
|
39
|
-
width === undefined ? values : width <= 0 ? [] : values.slice(-width);
|
|
40
|
-
if (window.length === 0) return "";
|
|
41
|
-
|
|
42
|
-
let min = window[0]!;
|
|
43
|
-
let max = window[0]!;
|
|
44
|
-
for (const v of window) {
|
|
45
|
-
if (v < min) min = v;
|
|
46
|
-
if (v > max) max = v;
|
|
47
|
-
}
|
|
48
|
-
const range = max - min;
|
|
49
|
-
|
|
50
|
-
let out = "";
|
|
51
|
-
for (const v of window) {
|
|
52
|
-
// range === 0 ⇒ every sample sits at its own min ⇒ height 0 ⇒ lowest tier.
|
|
53
|
-
const idx =
|
|
54
|
-
range === 0 ? 0 : Math.round(((v - min) / range) * (LEVELS - 1));
|
|
55
|
-
out += SPARK_LEVELS[idx]!;
|
|
56
|
-
}
|
|
57
|
-
return out;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// Decode the delimited string a series travels as across the scalar var-system
|
|
61
|
-
// seam into the number[] the renderer consumes. Empty / blank tokens are the
|
|
62
|
-
// genuine "no sample" form (an empty payload field is ""), so they drop; a
|
|
63
|
-
// non-empty, non-numeric token is malformed input and fails LOUDLY rather than
|
|
64
|
-
// being silently skipped into a wrong-shaped graph. [LAW:no-silent-failure]
|
|
65
|
-
export function parseSeries(s: string): number[] {
|
|
66
|
-
const out: number[] = [];
|
|
67
|
-
for (const tok of s.split(",")) {
|
|
68
|
-
const t = tok.trim();
|
|
69
|
-
if (t === "") continue;
|
|
70
|
-
const n = Number(t);
|
|
71
|
-
if (!Number.isFinite(n)) {
|
|
72
|
-
throw new TypeError(
|
|
73
|
-
`sparkline: non-numeric series element ${JSON.stringify(tok)}`,
|
|
74
|
-
);
|
|
75
|
-
}
|
|
76
|
-
out.push(n);
|
|
77
|
-
}
|
|
78
|
-
return out;
|
|
79
|
-
}
|
package/src/themes/index.ts
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
// [LAW:single-enforcer] Public barrel for the themes module. Name/string policy
|
|
2
|
-
// lives in policy.ts; memoized resolver construction (name→resolver and
|
|
3
|
-
// transposition) lives in palette-resolvers.ts. rich-js owns palettes,
|
|
4
|
-
// hydration, all color math, and the anchor rule — cc-candybar keeps no color
|
|
5
|
-
// arithmetic of its own.
|
|
6
|
-
export {
|
|
7
|
-
resolvePaletteName,
|
|
8
|
-
effectiveThemeName,
|
|
9
|
-
effectiveLookName,
|
|
10
|
-
lookKeyByName,
|
|
11
|
-
effectiveStripStyle,
|
|
12
|
-
effectiveAutoWrap,
|
|
13
|
-
effectivePadding,
|
|
14
|
-
isStripStyle,
|
|
15
|
-
listResolvablePaletteNames,
|
|
16
|
-
STRIP_STYLES,
|
|
17
|
-
} from "./policy.js";
|
|
18
|
-
export type { StripStyle } from "./policy.js";
|
|
19
|
-
|
|
20
|
-
export { paletteForThemeName, transposedPalette } from "./palette-resolvers.js";
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
// Memoized Palette construction over rich-js. cc-candybar moves theme
|
|
2
|
-
// NAMES and ThemeKey axes (data); rich-js owns every color value operation. Two
|
|
3
|
-
// memos live here: a theme name -> base palette, and a (base, ThemeKey) ->
|
|
4
|
-
// transposed palette. They compose — the per-render base palette feeds the
|
|
5
|
-
// per-segment transposition (the session look's axes + the segment's hue shift,
|
|
6
|
-
// folded into one key by the caller).
|
|
7
|
-
//
|
|
8
|
-
// [LAW:no-shared-mutable-globals] Single owner: this module. Both Maps are pure
|
|
9
|
-
// memos of pure rich-js functions, keyed by immutable inputs (resolved theme
|
|
10
|
-
// name; palette name + the four ThemeKey axes). rich-js palettes are immutable
|
|
11
|
-
// registry singletons, so a cached palette never goes stale. Key spaces are
|
|
12
|
-
// bounded by #themes and #themes × #declared looks × #distinct hueShifts
|
|
13
|
-
// (hueShift = look shift + segIndex*hueStep, segIndex bounded by layout; look
|
|
14
|
-
// axes bounded by the loaded configs' looks blocks) — both small. Shared on
|
|
15
|
-
// purpose: a theme's base palette and its gruvbox+42° transposition are each
|
|
16
|
-
// computed once per process, not once per RenderCache entry or per render.
|
|
17
|
-
// Read/written only through the two functions below.
|
|
18
|
-
|
|
19
|
-
import { transposePalette, getThemePalette } from "@promptctl/rich-js";
|
|
20
|
-
import type { ThemeKey, Palette } from "@promptctl/rich-js";
|
|
21
|
-
import { resolvePaletteName } from "./policy.js";
|
|
22
|
-
|
|
23
|
-
const baseCache = new Map<string, Palette>();
|
|
24
|
-
const transposeCache = new Map<string, Palette>();
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* The Palette for a theme name (aliases resolved). Memoized.
|
|
28
|
-
*
|
|
29
|
-
* [LAW:single-enforcer] The one place a theme name becomes a Palette —
|
|
30
|
-
* the per-render base palette and per-segment `palette:` overrides both flow
|
|
31
|
-
* through here. A name that does not resolve is registry/resolver drift, never
|
|
32
|
-
* user error: the loader validates `globals.palette` and the set-state verb
|
|
33
|
-
* validates session theme values against the resolvable set, so by the time a
|
|
34
|
-
* name reaches here it must resolve. [LAW:no-defensive-null-guards] the throw is
|
|
35
|
-
* the loud failure for that broken invariant, not a fallback.
|
|
36
|
-
*/
|
|
37
|
-
export function paletteForThemeName(name: string): Palette {
|
|
38
|
-
const resolved = resolvePaletteName(name);
|
|
39
|
-
const hit = baseCache.get(resolved);
|
|
40
|
-
if (hit !== undefined) return hit;
|
|
41
|
-
|
|
42
|
-
const palette = getThemePalette(resolved);
|
|
43
|
-
if (palette === null) {
|
|
44
|
-
throw new Error(
|
|
45
|
-
`Palette "${name}" (resolved "${resolved}") did not resolve in the ` +
|
|
46
|
-
`theme registry — allowed names and the registry are inconsistent`,
|
|
47
|
-
);
|
|
48
|
-
}
|
|
49
|
-
baseCache.set(resolved, palette);
|
|
50
|
-
return palette;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* `base` transposed by a full ThemeKey — the adapted-palette constructor:
|
|
55
|
-
* (base palette, key) → palette. The caller
|
|
56
|
-
* composes whatever axes it carries (a look's four axes, the per-segment hue
|
|
57
|
-
* shift) into ONE key and this makes ONE transposePalette call — never chain
|
|
58
|
-
* two transpositions: chaining double-pays OKLCH quantization AND collides this
|
|
59
|
-
* memo (a transposed palette keeps the base palette's name, so a re-transposed
|
|
60
|
-
* gruvbox-with-look and plain gruvbox would share cache keys).
|
|
61
|
-
*
|
|
62
|
-
* [LAW:dataflow-not-control-flow] The key is data; the identity key flows
|
|
63
|
-
* through transposePalette's isIdentityKey fast-path (byte-exact, no
|
|
64
|
-
* round-trip) — no branch here. rich-js hue-locks ANCHORED_ROOTS
|
|
65
|
-
* (error/success/warning), so semantic meaning is preserved by construction —
|
|
66
|
-
* no local exemption list to drift.
|
|
67
|
-
*
|
|
68
|
-
* [LAW:single-enforcer] The sole place a transposed palette is built — a
|
|
69
|
-
* future look `roles` remap is additive at this one seam. The memo miss
|
|
70
|
-
* (undefined) is genuine optionality — not-yet-computed — not a defended
|
|
71
|
-
* invariant. [LAW:one-source-of-truth] The cache key carries every axis of the
|
|
72
|
-
* ThemeKey: two keys differing on any axis are distinct palettes.
|
|
73
|
-
*/
|
|
74
|
-
export function transposedPalette(base: Palette, key: ThemeKey): Palette {
|
|
75
|
-
const cacheKey =
|
|
76
|
-
`${base.name} ${key.hueShift} ${key.chromaScale} ` +
|
|
77
|
-
`${key.lightnessScale} ${key.lightnessShift}`;
|
|
78
|
-
const hit = transposeCache.get(cacheKey);
|
|
79
|
-
if (hit !== undefined) return hit;
|
|
80
|
-
|
|
81
|
-
const transposed = transposePalette(base, key);
|
|
82
|
-
transposeCache.set(cacheKey, transposed);
|
|
83
|
-
return transposed;
|
|
84
|
-
}
|