@promptctl/cc-candybar 1.26.0 → 1.27.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 (44) hide show
  1. package/dist/index.mjs +86 -85
  2. package/package.json +6 -6
  3. package/schema/cc-candybar.schema.json +193 -4
  4. package/src/check.ts +49 -27
  5. package/src/click/wire.ts +16 -0
  6. package/src/config/action.ts +57 -22
  7. package/src/config/default-dsl-config.ts +424 -55
  8. package/src/config/dsl-loader.ts +14 -2
  9. package/src/config/dsl-types.ts +59 -0
  10. package/src/config/loader/actions.ts +283 -109
  11. package/src/config/loader/cross-ref.ts +148 -28
  12. package/src/config/loader/emit-schema.ts +2 -0
  13. package/src/config/loader/globals.ts +118 -31
  14. package/src/config/loader/merge.ts +58 -1
  15. package/src/config/loader/persist-target.ts +32 -0
  16. package/src/config/loader/presets.ts +107 -0
  17. package/src/config/option-domain.ts +164 -0
  18. package/src/config/presets.ts +156 -0
  19. package/src/daemon/cache/git.ts +1 -1
  20. package/src/daemon/cache/render.ts +61 -7
  21. package/src/daemon/config-overrides-store.ts +322 -0
  22. package/src/daemon/paths.ts +10 -0
  23. package/src/daemon/render-payload.ts +84 -19
  24. package/src/daemon/server.ts +68 -55
  25. package/src/daemon/verbs/config-validators.ts +127 -0
  26. package/src/daemon/verbs/index.ts +129 -2
  27. package/src/daemon/verbs/state-validators.ts +98 -586
  28. package/src/daemon/verbs/validator-registry.ts +457 -0
  29. package/src/demo/dsl.ts +17 -10
  30. package/src/dsl/node-registry.ts +54 -39
  31. package/src/dsl/render.ts +158 -46
  32. package/src/help-text.ts +3 -3
  33. package/src/install/index.ts +2 -2
  34. package/src/render/action.ts +155 -33
  35. package/src/render/active-segment.ts +78 -0
  36. package/src/render/menu.ts +16 -11
  37. package/src/render/picker.ts +51 -13
  38. package/src/render/segment-color.ts +74 -0
  39. package/src/segments/git.ts +389 -48
  40. package/src/template-engine/colors.ts +67 -45
  41. package/src/template-engine/engine.ts +11 -12
  42. package/src/themes/index.ts +1 -4
  43. package/src/themes/palette-resolvers.ts +22 -30
  44. package/src/themes/policy.ts +37 -16
@@ -50,6 +50,7 @@ import { synthesizeGroupDecls, validateRoot } from "./loader/layout.js";
50
50
  import { synthesizeMenuDecls } from "./loader/menu-synth.js";
51
51
  import { validateActions } from "./loader/actions.js";
52
52
  import { validateLooks } from "./loader/looks.js";
53
+ import { validatePresets } from "./loader/presets.js";
53
54
  import { validateHelpers } from "./loader/helpers.js";
54
55
  import { validateCrossReferences } from "./loader/cross-ref.js";
55
56
  import { validateNoCycles } from "./loader/cycles.js";
@@ -67,7 +68,10 @@ export {
67
68
  resolveDslConfigPath,
68
69
  detectConfigCollisions,
69
70
  } from "./loader/discovery.js";
70
- export { mergeWithDefault } from "./loader/merge.js";
71
+ export {
72
+ mergeWithDefault,
73
+ applySegmentPaletteOverrides,
74
+ } from "./loader/merge.js";
71
75
  export {
72
76
  extractTemplateRefs,
73
77
  extractActionRefs,
@@ -230,7 +234,7 @@ function validateTopLevel(
230
234
 
231
235
  const out: Mutable<RawDslConfig> = {};
232
236
  if (raw.globals !== undefined)
233
- out.globals = validateGlobals(ctx, raw.globals);
237
+ out.globals = validateGlobals(ctx, "globals", raw.globals);
234
238
  if (raw.variables !== undefined)
235
239
  out.variables = validateVariables(ctx, "variables", raw.variables);
236
240
  if (raw.segments !== undefined)
@@ -252,6 +256,13 @@ function validateTopLevel(
252
256
  if (raw.actions !== undefined)
253
257
  out.actions = validateActions(ctx, raw.actions);
254
258
  if (raw.looks !== undefined) out.looks = validateLooks(ctx, raw.looks);
259
+ // [LAW:one-source-of-truth] Parsed BEFORE the synthesis passes below, because
260
+ // a preset's `root` runs through the same validateRoot and therefore collects
261
+ // its group sugar into the same `ctx.groups` the top-level root does — the
262
+ // synthesized artifacts must see every group the config declares, wherever it
263
+ // was staged from.
264
+ if (raw.presets !== undefined)
265
+ out.presets = validatePresets(ctx, raw.presets);
255
266
  if (raw.helpers !== undefined)
256
267
  out.helpers = validateHelpers(ctx, raw.helpers);
257
268
  // [LAW:one-source-of-truth] Group sugar synthesis runs AFTER every section
@@ -280,5 +291,6 @@ const TOP_LEVEL_KEYS = new Set([
280
291
  "root",
281
292
  "actions",
282
293
  "looks",
294
+ "presets",
283
295
  "helpers",
284
296
  ]);
@@ -129,12 +129,49 @@ export function* walkNodes(node: LayoutNode): IterableIterator<LayoutNode> {
129
129
  }
130
130
  }
131
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
+
132
165
  export interface RawDslConfig {
133
166
  readonly globals?: Partial<Globals>;
134
167
  readonly variables?: Readonly<Record<string, VariableDecl>>;
135
168
  readonly segments?: Readonly<Record<string, SegmentDecl>>;
136
169
  readonly root?: LayoutNode;
137
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>>;
138
175
  // Named theme-adaptation bundles ("looks"): each is a full ThemeKey (the
139
176
  // loader normalizes absent axes to identity at parse). Applied ON TOP of the
140
177
  // active theme at render — a transform composing with every theme, selected
@@ -171,6 +208,14 @@ export interface DslConfig {
171
208
  // config by construction. An action `{ set: …, from: "looks" }` ranges these
172
209
  // names; the derived click gate and the rendered options read this one map.
173
210
  readonly looks: Readonly<Record<string, ThemeKey>>;
211
+ // [LAW:one-source-of-truth] The effective preset set: name → config fragment.
212
+ // Merges by name with the bundled default (user wins per name) like every
213
+ // other section — so the default's `default` preset (the empty fragment, and
214
+ // the resolution floor of effectivePresetName) is present in EVERY merged
215
+ // config by construction, exactly as `looks` guarantees `none`. An action
216
+ // `{ set: …, from: "presets" }` ranges these names; the derived click gate and
217
+ // the rendered options read this one map.
218
+ readonly presets: Readonly<Record<string, PresetDecl>>;
174
219
  // [LAW:single-enforcer] The effective helper set: a name → template-body map
175
220
  // compiled to a defines-preamble at registerDslConfig. Empty when no config
176
221
  // declares helpers — an absent `helpers` key merges to `{}` (same cascade as
@@ -214,6 +259,20 @@ export interface Globals {
214
259
  // default-provided look.
215
260
  readonly look?: string;
216
261
 
262
+ // [LAW:one-type-per-behavior] The config default for the PRESET (a named
263
+ // config fragment from the `presets` block) — the same twin-of-`palette`
264
+ // shape as `look` one dimension over: the daemon resolves the live preset per
265
+ // render as `sessionState.preset ?? globals.preset ?? "default"`
266
+ // (effectivePresetName), so a preset click restages the bar live and a config
267
+ // can pick a default arrangement without an edit-per-session. Membership in
268
+ // the merged `presets` map is validated post-merge (cross-ref) — a user's
269
+ // globals.preset may name a default-provided preset.
270
+ //
271
+ // [LAW:one-source-of-truth] A preset's own `globals` may NOT carry this field
272
+ // (the loader rejects it): a preset selecting a preset is a second authority
273
+ // over which preset is active, and a cyclic one.
274
+ readonly preset?: string;
275
+
217
276
  // [LAW:one-type-per-behavior] The config default for the powerline cap/
218
277
  // separator SHAPE — the exact twin of `palette` one dimension over: the
219
278
  // daemon resolves the live strip style per render as