@promptctl/cc-candybar 1.33.0 → 1.34.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 +26 -26
- package/package.json +5 -5
- package/src/check.ts +99 -30
- package/src/config/default-dsl-config.ts +11 -0
- package/src/config/edit-chrome.ts +93 -10
- package/src/config/ident.ts +22 -0
- package/src/config/loader/cross-ref.ts +51 -0
- package/src/config/loader/persist-target.ts +20 -1
- package/src/config/loader/presets.ts +14 -2
- package/src/config/menu-keys.ts +2 -8
- package/src/config/presets.ts +72 -0
- package/src/daemon/cache/render.ts +31 -1
- package/src/daemon/render-payload.ts +20 -2
- package/src/daemon/server.ts +14 -1
- package/src/daemon/verbs/config-validators.ts +47 -3
|
@@ -28,6 +28,7 @@ import { configOverridesPath } from "../paths.js";
|
|
|
28
28
|
import {
|
|
29
29
|
applyPresetRootOpsOverrides,
|
|
30
30
|
sanitizePersistedPresetOverride,
|
|
31
|
+
sanitizePersistedPresetRootOps,
|
|
31
32
|
} from "../../config/presets.js";
|
|
32
33
|
import { VariableStore } from "../../var-system/store.js";
|
|
33
34
|
import { SourceRegistry } from "../../var-system/sources.js";
|
|
@@ -85,6 +86,21 @@ export interface DslRenderState {
|
|
|
85
86
|
readonly compiled: CompiledConfig;
|
|
86
87
|
readonly neededInputPaths: ReadonlySet<string>;
|
|
87
88
|
readonly lastRenderCellsBySegment: Map<string, readonly RichText[]>;
|
|
89
|
+
// [LAW:one-source-of-truth] The accumulated-ops record this reload read
|
|
90
|
+
// from the overrides file, sanitized against THIS config's declared
|
|
91
|
+
// presets (sanitizePersistedPresetRootOps — never the raw file content: a
|
|
92
|
+
// stale entry naming a preset from a different project must not surface
|
|
93
|
+
// as "customized" here either) — the exact input
|
|
94
|
+
// applyPresetRootOpsOverrides replayed into `config.presets[name].root`
|
|
95
|
+
// above. Carried alongside the replayed config because the replay
|
|
96
|
+
// CONSUMES the op count: by the time a preset's tree is spliced/
|
|
97
|
+
// validated, nothing about it says how many ops (if any) produced it.
|
|
98
|
+
// brandon-layout-edit-2gc.5 reads this per render (keyed by the active
|
|
99
|
+
// preset name) to answer "does the bar's current arrangement differ from
|
|
100
|
+
// what's literally in the user's file" without a second overrides read
|
|
101
|
+
// (this entry rebuilds on the SAME watcher that rebuilds `config`, so the
|
|
102
|
+
// two never drift).
|
|
103
|
+
readonly presetRootOps: Readonly<Record<string, readonly string[]>>;
|
|
88
104
|
// [LAW:single-enforcer] Disposers for the SessionState validators this config
|
|
89
105
|
// installed (derived from its action table). Disposed on swap/eviction in the
|
|
90
106
|
// same dispose-before-swap transaction as the SourceRegistry, so a reload
|
|
@@ -327,6 +343,19 @@ export class RenderCache {
|
|
|
327
343
|
withGlobalsOverrides,
|
|
328
344
|
overrides.segmentPalette,
|
|
329
345
|
);
|
|
346
|
+
// [LAW:no-silent-failure] Drop any entry naming a preset THIS config
|
|
347
|
+
// never declared before replay ever sees it — the shared overrides file
|
|
348
|
+
// outlives any one project's preset names (brandon-layout-edit-2gc.5 PR
|
|
349
|
+
// review: without this, a stale entry from a DIFFERENT project reaches
|
|
350
|
+
// applyPresetRootOpsOverrides -> presetRoot -> presetByName, which
|
|
351
|
+
// throws for an undeclared name, failing this unrelated project's
|
|
352
|
+
// ENTIRE render). Sanitized against `merged.presets` — the SAME config
|
|
353
|
+
// sanitizePersistedPresetOverride checks `globals.preset` against, two
|
|
354
|
+
// lines up, for the identical reason.
|
|
355
|
+
const sanitizedPresetRootOps = sanitizePersistedPresetRootOps(
|
|
356
|
+
overrides.presetRootOps,
|
|
357
|
+
merged.presets,
|
|
358
|
+
);
|
|
330
359
|
// [LAW:one-source-of-truth] brandon-layout-edit-2gc.1's replay step —
|
|
331
360
|
// the SAME "patch an already-merged config" cascade as the segment-
|
|
332
361
|
// palette overlay above, one field over (a preset's `root` instead of a
|
|
@@ -337,7 +366,7 @@ export class RenderCache {
|
|
|
337
366
|
// be.
|
|
338
367
|
const withPresetRootOps = applyPresetRootOpsOverrides(
|
|
339
368
|
withOverrides,
|
|
340
|
-
|
|
369
|
+
sanitizedPresetRootOps,
|
|
341
370
|
);
|
|
342
371
|
const config = validateConfig(
|
|
343
372
|
withPresetRootOps,
|
|
@@ -414,6 +443,7 @@ export class RenderCache {
|
|
|
414
443
|
neededInputPaths: buildNeededPrefixes(config),
|
|
415
444
|
lastRenderCellsBySegment: new Map<string, readonly RichText[]>(),
|
|
416
445
|
validatorDisposers,
|
|
446
|
+
presetRootOps: sanitizedPresetRootOps,
|
|
417
447
|
};
|
|
418
448
|
}
|
|
419
449
|
|
|
@@ -61,6 +61,15 @@ export interface EffectiveGlobals {
|
|
|
61
61
|
// carried alongside so a menu label states the arrangement that actually
|
|
62
62
|
// rendered [LAW:one-source-of-truth].
|
|
63
63
|
readonly preset: string;
|
|
64
|
+
// [LAW:one-source-of-truth] brandon-layout-edit-2gc.5 — the SAME "does the
|
|
65
|
+
// active preset have accumulated rootOps right now" fact
|
|
66
|
+
// presetIsCustomized derives, resolved alongside `preset` (not a second
|
|
67
|
+
// lookup later) because both come from the SAME entry.state read: the
|
|
68
|
+
// resolved name and the resolved op-log presence must trace to one
|
|
69
|
+
// rebuild, or a diagnostic could name the wrong preset after a reload
|
|
70
|
+
// lands mid-render. Not itself a display value either — see `preset`'s
|
|
71
|
+
// own comment.
|
|
72
|
+
readonly presetCustomized: boolean;
|
|
64
73
|
readonly style: StripStyle;
|
|
65
74
|
readonly charset: Charset;
|
|
66
75
|
readonly colorCompatibility: ColorCompatibility;
|
|
@@ -108,7 +117,13 @@ export interface RenderPayload extends ClaudeHookData {
|
|
|
108
117
|
// and look's twin one level up: the SAME name that selected the layout this
|
|
109
118
|
// render walked and the globals it rendered with, surfaced so a preset
|
|
110
119
|
// trigger's label can never claim an arrangement the bar is not in.
|
|
111
|
-
|
|
120
|
+
// [LAW:one-source-of-truth] brandon-layout-edit-2gc.5 — `customized` rides
|
|
121
|
+
// alongside `effective` rather than as a sibling top-level field, mirroring
|
|
122
|
+
// the shape a `when`-gated status segment reads (`.preset.customized`)
|
|
123
|
+
// beside the trigger's own `.preset.effective` label. Required for the
|
|
124
|
+
// same reason as `effective`: resolved unconditionally per render from
|
|
125
|
+
// presetIsCustomized, never absent.
|
|
126
|
+
readonly preset: { readonly effective: string; readonly customized: boolean };
|
|
112
127
|
// [LAW:one-type-per-behavior] style/charset/colorCompatibility/autoWrap/
|
|
113
128
|
// padding are theme/look's twins over the remaining persistable globals
|
|
114
129
|
// (candybar-config-engine-71o.3) — each REQUIRED and unconditionally
|
|
@@ -858,7 +873,10 @@ export async function buildRenderPayload(
|
|
|
858
873
|
// hand) and a config reading e.g. `.padding.effective` must always find it.
|
|
859
874
|
theme: { effective: effective.theme },
|
|
860
875
|
look: { effective: effective.look },
|
|
861
|
-
preset: {
|
|
876
|
+
preset: {
|
|
877
|
+
effective: effective.preset,
|
|
878
|
+
customized: effective.presetCustomized,
|
|
879
|
+
},
|
|
862
880
|
style: { effective: effective.style },
|
|
863
881
|
charset: { effective: effective.charset },
|
|
864
882
|
colorCompatibility: { effective: effective.colorCompatibility },
|
package/src/daemon/server.ts
CHANGED
|
@@ -70,7 +70,11 @@ import {
|
|
|
70
70
|
lookKeyByName,
|
|
71
71
|
paletteForThemeName,
|
|
72
72
|
} from "../themes/index.js";
|
|
73
|
-
import {
|
|
73
|
+
import {
|
|
74
|
+
effectivePresetName,
|
|
75
|
+
presetGlobals,
|
|
76
|
+
presetIsCustomized,
|
|
77
|
+
} from "../config/presets.js";
|
|
74
78
|
import {
|
|
75
79
|
renderStripCells,
|
|
76
80
|
DEFAULT_CHARSET,
|
|
@@ -888,6 +892,15 @@ async function handleRequest(req: Request): Promise<HandledRequest> {
|
|
|
888
892
|
const globals = presetGlobals(entry.state.config, preset);
|
|
889
893
|
const effective: EffectiveGlobals = {
|
|
890
894
|
preset,
|
|
895
|
+
// [LAW:one-source-of-truth] brandon-layout-edit-2gc.5 — read from
|
|
896
|
+
// THIS entry's own presetRootOps (the record that fed the SAME
|
|
897
|
+
// reload that produced entry.state.config), never a fresh
|
|
898
|
+
// loadOverrides() here — a second read could race a concurrent
|
|
899
|
+
// write and disagree with the tree that actually rendered.
|
|
900
|
+
presetCustomized: presetIsCustomized(
|
|
901
|
+
entry.state.presetRootOps,
|
|
902
|
+
preset,
|
|
903
|
+
),
|
|
891
904
|
theme: effectiveThemeName(
|
|
892
905
|
sessionState.get(req.hookData.session_id, "theme"),
|
|
893
906
|
globals.palette,
|
|
@@ -19,6 +19,10 @@ import { addableSegmentDomains } from "../../config/edit-chrome";
|
|
|
19
19
|
import type { DslConfig } from "../../config/dsl-types";
|
|
20
20
|
import { isGlobalsField } from "../config-overrides-store";
|
|
21
21
|
import { encodeLayoutOp } from "../../config/layout-ops";
|
|
22
|
+
import {
|
|
23
|
+
parsePersistTarget,
|
|
24
|
+
presetRootOpsKey,
|
|
25
|
+
} from "../../config/loader/persist-target";
|
|
22
26
|
import {
|
|
23
27
|
clampSeed,
|
|
24
28
|
createValidatorRegistry,
|
|
@@ -168,6 +172,43 @@ function configKeySeeds(config: DslConfig): ReadonlyMap<string, number> {
|
|
|
168
172
|
return seeds;
|
|
169
173
|
}
|
|
170
174
|
|
|
175
|
+
// [LAW:one-source-of-truth] Every preset a config's action table ALREADY
|
|
176
|
+
// targets via a `presets.<name>.rootOps` key (persist OR reset — the
|
|
177
|
+
// contribution is keyed off intent to use structural editing for that
|
|
178
|
+
// preset, not off "this config has any presets block") stays a
|
|
179
|
+
// registered key EVEN when that preset's CURRENT tree has no
|
|
180
|
+
// addable/removable segment for removeChrome/insertChrome to contribute
|
|
181
|
+
// from. Without this, a preset edited down to zero non-exempt segments
|
|
182
|
+
// (spliceContainer then contributes nothing for it at all) would orphan
|
|
183
|
+
// its OWN reset action: the one affordance meant to undo a fully-emptied
|
|
184
|
+
// preset would throw "unknown config key" at the exact moment it's needed
|
|
185
|
+
// most (brandon-layout-edit-2gc.5 PR review). An EMPTY allow-list
|
|
186
|
+
// registers the key (so `reset-config`'s membership check —
|
|
187
|
+
// src/daemon/verbs/index.ts's resetConfig — passes) without granting any
|
|
188
|
+
// illegitimate WRITE: a real persist write still needs a real
|
|
189
|
+
// removeSegment/insertSegment/insertSegmentFrom action elsewhere;
|
|
190
|
+
// mergeContributions unions an empty array with whatever those contribute.
|
|
191
|
+
//
|
|
192
|
+
// [LAW:no-mode-explosion] Deliberately narrower than "every declared
|
|
193
|
+
// preset" — a config with a `presets` block but ZERO persist/reset actions
|
|
194
|
+
// over it has no structural-editing surface at all, so it registers
|
|
195
|
+
// nothing here, preserving this module's own "zero baseline keys" floor
|
|
196
|
+
// (a globals field, and now a preset's rootOps, is writable only because
|
|
197
|
+
// SOME action names it).
|
|
198
|
+
function presetRootOpsContributions(config: DslConfig): KeySpecContribution[] {
|
|
199
|
+
const presets = new Set<string>();
|
|
200
|
+
for (const a of Object.values(config.actions)) {
|
|
201
|
+
const key = "persist" in a ? a.persist : "reset" in a ? a.reset : null;
|
|
202
|
+
if (key === null) continue;
|
|
203
|
+
const target = parsePersistTarget(key);
|
|
204
|
+
if (target?.scope === "preset-root-ops") presets.add(target.preset);
|
|
205
|
+
}
|
|
206
|
+
return [...presets].map((name) => ({
|
|
207
|
+
key: presetRootOpsKey(name),
|
|
208
|
+
spec: { kind: "allow-list", allowed: [] },
|
|
209
|
+
}));
|
|
210
|
+
}
|
|
211
|
+
|
|
171
212
|
function actionContributions(config: DslConfig): KeySpecContribution[] {
|
|
172
213
|
const seeds = configKeySeeds(config);
|
|
173
214
|
// [LAW:one-source-of-truth] The "addable segment" domains
|
|
@@ -180,9 +221,12 @@ function actionContributions(config: DslConfig): KeySpecContribution[] {
|
|
|
180
221
|
...perConfigDomainsFor(config),
|
|
181
222
|
...addableSegmentDomains(config),
|
|
182
223
|
]);
|
|
183
|
-
return
|
|
184
|
-
|
|
185
|
-
|
|
224
|
+
return [
|
|
225
|
+
...presetRootOpsContributions(config),
|
|
226
|
+
...Object.values(config.actions).flatMap((a) =>
|
|
227
|
+
actionKeySpecs(a, seeds, perConfigDomains),
|
|
228
|
+
),
|
|
229
|
+
];
|
|
186
230
|
}
|
|
187
231
|
|
|
188
232
|
// [LAW:single-enforcer] The SOLE install-site derivation: a config's
|