@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,67 +0,0 @@
1
- // [LAW:one-source-of-truth] THE parser for what a `persist`/`reset` action's
2
- // key STRING may legally name — the single place a key is classified as a
3
- // Globals field (candybar-config-engine-71o.3's scope) or a per-segment
4
- // palette override (candybar-config-engine-71o.6's scope), so cross-ref
5
- // validation (load time), the config-overrides store (write/read), and the
6
- // daemon's write-gate all classify a key through ONE authority instead of
7
- // three independently-drifting `isGlobalsField` checks.
8
- //
9
- // [LAW:types-are-the-program] A discriminated union, not a bag of optional
10
- // fields: every legal key shape is representable, every illegal one
11
- // collapses to `null` at exactly this one boundary — no downstream consumer
12
- // re-parses the string itself.
13
-
14
- import type { Globals } from "../dsl-types.js";
15
- import { isGlobalsField } from "./globals.js";
16
-
17
- export type PersistTarget =
18
- | { readonly scope: "globals"; readonly field: keyof Globals }
19
- | { readonly scope: "segment-palette"; readonly segment: string }
20
- // [LAW:one-source-of-truth] brandon-layout-edit-2gc.1's structural-edit
21
- // target — the accumulated op LOG for one preset's root (see
22
- // src/config/layout-ops.ts), never the tree itself: a scalar-shaped value
23
- // (a JSON-encoded string[] of op tokens) so it rides the SAME flat-dict
24
- // overrides file with no shape change to that store's core writer.
25
- | { readonly scope: "preset-root-ops"; readonly preset: string };
26
-
27
- // [LAW:locality-or-seam] `segments.<name>.palette` reuses the SAME dotted
28
- // namespacing SegmentDecl.vars already uses for segment-local variables
29
- // (`<segment>.<var>`, declared in src/dsl/render.ts) — one idiom for "a name
30
- // scoped under a segment", not a bespoke second syntax invented for persist
31
- // targets alone. `presets.<name>.rootOps` mirrors it one level up (a name
32
- // scoped under a preset) — deliberately spelled `rootOps`, not `root`, so it
33
- // never reads as the same string as presetRoot()'s load-time diagnostic path
34
- // `presets.<name>.root` (src/config/presets.ts), a different namespace this
35
- // key must never be confused with.
36
- const SEGMENT_PALETTE_KEY = /^segments\.([^.]+)\.palette$/;
37
- // [LAW:one-source-of-truth] GREEDY capture, not `[^.]+` — a preset name is
38
- // validated only non-empty/slash/newline-free (loader/presets.ts), so a dot
39
- // is a legal preset name (e.g. "v1.compact"). presetRootOpsKey always
40
- // appends the literal ".rootOps" suffix, so a greedy `(.+)` backtracks to
41
- // the RIGHTMOST occurrence of that anchored suffix and correctly recovers
42
- // the full name for ANY preset name, dotted or not — round-tripping
43
- // presetRootOpsKey exactly, rather than restricting preset names further
44
- // to dodge the ambiguity a non-greedy/exclusive-dot capture would have.
45
- const PRESET_ROOT_OPS_KEY = /^presets\.(.+)\.rootOps$/;
46
-
47
- // [LAW:one-source-of-truth] THE builder for a preset's rootOps key — the
48
- // inverse of PRESET_ROOT_OPS_KEY's parse. Two independent sites used to
49
- // spell `presets.${name}.rootOps` themselves (edit-chrome.ts's synthesis,
50
- // config-validators.ts's always-registered contribution), with only the
51
- // regex above as a read-side authority and no shared write-side one — they
52
- // happened to agree, but nothing enforced it. One function now; both call
53
- // sites import it.
54
- export function presetRootOpsKey(name: string): string {
55
- return `presets.${name}.rootOps`;
56
- }
57
-
58
- export function parsePersistTarget(key: string): PersistTarget | null {
59
- if (isGlobalsField(key)) return { scope: "globals", field: key };
60
- const segmentMatch = SEGMENT_PALETTE_KEY.exec(key);
61
- if (segmentMatch)
62
- return { scope: "segment-palette", segment: segmentMatch[1]! };
63
- const presetMatch = PRESET_ROOT_OPS_KEY.exec(key);
64
- return presetMatch
65
- ? { scope: "preset-root-ops", preset: presetMatch[1]! }
66
- : null;
67
- }
@@ -1,119 +0,0 @@
1
- // [LAW:types-are-the-program] The `presets` schema: each preset is a named
2
- // config FRAGMENT — an alternative arrangement of the bar the user switches to
3
- // — capped at exactly the two sections a render RESOLVES (`root`, `globals`)
4
- // and closed to the sections the daemon REGISTERS once per process
5
- // (`variables`, `segments`, `actions`, `helpers`). The reasoning for that cap
6
- // lives on PresetDecl in dsl-types.ts; enforcing it is this file's job, and the
7
- // record engine enforces it for free — `rejectUnknownKeys` reports any other
8
- // section as a "preset key" error naming the two that are legal.
9
- //
10
- // This file is the structural pass only. Whether a preset's root names a
11
- // segment that exists, and whether globals.preset names a declared preset, are
12
- // CROSS-REFERENCE concerns (loader/cross-ref.ts), because both run on the
13
- // MERGED config — a preset may stage segments the bundled default provides.
14
-
15
- import {
16
- describeType,
17
- isPlainObject,
18
- record,
19
- recordJson,
20
- type FieldSpec,
21
- type JsonNode,
22
- type RecordSchema,
23
- type ValidateCtx,
24
- } from "./validate-core.js";
25
- import { findKeyLine } from "./diagnostics.js";
26
- import { presetGlobalsJson, validatePresetGlobals } from "./globals.js";
27
- import { LAYOUT_NODE_REF, validateRoot } from "./layout.js";
28
- import type { PresetDecl } from "../dsl-types.js";
29
-
30
- // [LAW:one-source-of-truth] A preset's `root` runs through THE layout validator
31
- // — the same `validateRoot` the top-level `root:` uses, not a reduced copy — so
32
- // the A-grammar, the group sugar, and every migration error read identically
33
- // wherever a layout is authored. Group sugar declared inside a preset therefore
34
- // also lands in `ctx.groups` and synthesizes its state var / cycle action /
35
- // toggle segment into the shared sections, exactly as a top-level group does:
36
- // the synthesized artifacts are process-lifetime (see PresetDecl's cap), the
37
- // preset only chooses whether to stage them.
38
- const presetRootSpec: FieldSpec<NonNullable<PresetDecl["root"]>> = {
39
- required: false,
40
- json: { $ref: LAYOUT_NODE_REF },
41
- parse: (ctx, path, field, raw) =>
42
- raw[field] === undefined
43
- ? undefined
44
- : validateRoot(ctx, `${path}.${field}`, raw[field]),
45
- };
46
-
47
- // [LAW:one-source-of-truth] A preset's `globals` runs through the globals field
48
- // table, minus `preset` itself (a preset cannot select a preset — see
49
- // validatePresetGlobals). A field added to Globals is preset-settable the same
50
- // day, with no edit here.
51
- const presetGlobalsSpec: FieldSpec<NonNullable<PresetDecl["globals"]>> = {
52
- required: false,
53
- json: presetGlobalsJson(),
54
- parse: (ctx, path, field, raw) =>
55
- raw[field] === undefined
56
- ? undefined
57
- : validatePresetGlobals(ctx, `${path}.${field}`, raw[field]),
58
- };
59
-
60
- const PRESET_SCHEMA: RecordSchema<PresetDecl> = {
61
- noun: "preset key",
62
- fields: { root: presetRootSpec, globals: presetGlobalsSpec },
63
- };
64
-
65
- // An absent presets block is handled by the caller (absence survives the parse);
66
- // a non-object is a reported error recovering to empty — parseDslConfig throws
67
- // once any issue exists, so the recovery value never renders.
68
- export function validatePresets(
69
- ctx: ValidateCtx,
70
- raw: unknown,
71
- ): Readonly<Record<string, PresetDecl>> {
72
- if (!isPlainObject(raw)) {
73
- ctx.issues.push({
74
- path: "presets",
75
- message: `presets must be an object mapping preset names to config fragments, got ${describeType(raw)}`,
76
- line: findKeyLine(ctx.source, ["presets"]),
77
- });
78
- return {};
79
- }
80
- const out: Record<string, PresetDecl> = {};
81
- for (const [name, value] of Object.entries(raw)) {
82
- // [LAW:no-silent-failure] A preset name is a deliverable set-state value —
83
- // a preset picker writes it on the wire, which rejects empty values and
84
- // splits on "/". Rejecting the shape HERE surfaces the error on every
85
- // config load, not only once an action ranges the "presets" domain (the
86
- // identical guard looks.ts applies to look names, for the identical
87
- // reason).
88
- //
89
- // [LAW:one-source-of-truth] Newlines are ALSO rejected — the same reason
90
- // loader/layout.ts's groupLabelSpec rejects \n/\r in a group's `label`
91
- // before it ever reaches escapeTemplateLiteral: a preset name is spliced
92
- // as DISPLAY TEXT into a synthesized Go-template string literal
93
- // (edit-chrome.ts's "customized" banner), and that escaper only handles
94
- // backslash/quote — an embedded newline produces an unterminated string
95
- // literal go-template-js forbids, breaking synthesis for the WHOLE
96
- // config, not just this one preset. Unlike a label, a preset name is
97
- // also an identifier used across other seams (the `presets` domain, the
98
- // `presets.<name>.rootOps` wire key), so this belongs in its general
99
- // validity check, not a narrower escape-harder fix at the one splice site.
100
- if (name === "" || name.includes("/") || /[\n\r]/.test(name)) {
101
- ctx.issues.push({
102
- path: `presets.${name}`,
103
- message: `preset name ${JSON.stringify(name)} must be non-empty, slash-free, and newline-free — a preset picker writes the name on the set-state wire (which rejects empty values and splits on "/"), and edit mode splices it into a synthesized template string`,
104
- line: findKeyLine(ctx.source, ["presets", name]),
105
- });
106
- continue;
107
- }
108
- const parsed = record(ctx, PRESET_SCHEMA, `presets.${name}`, value);
109
- if (parsed !== null) out[name] = parsed;
110
- }
111
- return out;
112
- }
113
-
114
- // [LAW:one-source-of-truth] The schema emitter derives from the SAME declaration
115
- // the validator interprets — a map of preset names to the closed two-section
116
- // fragment object.
117
- export function presetsJson(): JsonNode {
118
- return { type: "object", additionalProperties: recordJson(PRESET_SCHEMA) };
119
- }
@@ -1,100 +0,0 @@
1
- // [LAW:dataflow-not-control-flow] Best-effort extraction of the references a
2
- // template string makes — dotted variable refs, `action "name"` refs, and
3
- // `picker "apply" "page"` refs. Pure text walks over `{{ … }}` blocks: no
4
- // full template parse (that is the engine's compile-time job). This file changes
5
- // when the surface grammar of those refs changes; the cross-ref/cycle passes
6
- // consume the sets it returns without re-deriving them.
7
-
8
- const TEMPLATE_BLOCK_RE = /{{([\s\S]*?)}}/g;
9
- const STRING_LITERAL_RE = /"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'|`[^`]*`/g;
10
- const DOTTED_REF_RE =
11
- /(?<![A-Za-z0-9_)])\.([A-Za-z_][\w]*(?:\.[A-Za-z_][\w]*)*)/g;
12
-
13
- // [LAW:dataflow-not-control-flow] Extract every `.<id>(.<id>)*` token inside
14
- // `{{ ... }}` blocks after stripping string literals. The result is a set of
15
- // dotted reference candidates; the caller decides which are valid.
16
- export function extractTemplateRefs(template: string): Set<string> {
17
- const refs = new Set<string>();
18
- let m: RegExpExecArray | null;
19
- TEMPLATE_BLOCK_RE.lastIndex = 0;
20
- while ((m = TEMPLATE_BLOCK_RE.exec(template)) !== null) {
21
- const block = m[1]!.replace(STRING_LITERAL_RE, "");
22
- let r: RegExpExecArray | null;
23
- DOTTED_REF_RE.lastIndex = 0;
24
- while ((r = DOTTED_REF_RE.exec(block)) !== null) {
25
- refs.add(r[1]!);
26
- }
27
- }
28
- return refs;
29
- }
30
-
31
- // [LAW:dataflow-not-control-flow] Extract every `action "name"` call from a
32
- // template, for the load-time existence check. Same best-effort code-span /
33
- // string-literal walk as extractTemplateRefs: the `action` keyword lives in a
34
- // CODE span and its NAME is the very next string literal (the display/boundValue
35
- // literals that follow are preceded by a non-`action` span, so they are never
36
- // misread as the name).
37
- const ACTION_ARG_RE = /\baction\s+$/;
38
- export function extractActionRefs(template: string): Set<string> {
39
- const refs = new Set<string>();
40
- TEMPLATE_BLOCK_RE.lastIndex = 0;
41
- let m: RegExpExecArray | null;
42
- while ((m = TEMPLATE_BLOCK_RE.exec(template)) !== null) {
43
- const block = m[1]!;
44
- let cursor = 0;
45
- let s: RegExpExecArray | null;
46
- STRING_LITERAL_RE.lastIndex = 0;
47
- while ((s = STRING_LITERAL_RE.exec(block)) !== null) {
48
- if (ACTION_ARG_RE.test(block.slice(cursor, s.index))) {
49
- refs.add(s[0].slice(1, -1));
50
- }
51
- cursor = s.index + s[0].length;
52
- }
53
- }
54
- return refs;
55
- }
56
-
57
- // [LAW:dataflow-not-control-flow] Extract the action names a `picker` OR `menu`
58
- // call references, for the load-time existence check. A `picker` binds an
59
- // (apply, page) action pair as its first two string-literal args
60
- // (`{{ picker "applyTheme" "themePage" true true }}`); a `menu` binds ONLY its
61
- // apply action (`{{ menu "applyTheme" (dict …) }}`) — its page cursor is
62
- // synthesized from identity, and the dict's option-name literals must never be
63
- // misread as action refs. A menu's body IS a picker, so the existence check is
64
- // identical; one extractor arms on either keyword with the keyword's own arg
65
- // count [LAW:single-enforcer]. Same code/string-span walk as extractActionRefs.
66
- const PICKER_OR_MENU_ARG_RE = /\b(picker|menu)\s+$/;
67
- export function extractPickerMenuRefs(template: string): Set<string> {
68
- const refs = new Set<string>();
69
- TEMPLATE_BLOCK_RE.lastIndex = 0;
70
- let m: RegExpExecArray | null;
71
- while ((m = TEMPLATE_BLOCK_RE.exec(template)) !== null) {
72
- const block = m[1]!;
73
- let cursor = 0;
74
- let pending = 0; // remaining name args to capture for the current call
75
- let s: RegExpExecArray | null;
76
- STRING_LITERAL_RE.lastIndex = 0;
77
- while ((s = STRING_LITERAL_RE.exec(block)) !== null) {
78
- const kw = PICKER_OR_MENU_ARG_RE.exec(block.slice(cursor, s.index));
79
- if (kw !== null) pending = kw[1] === "picker" ? 2 : 1;
80
- if (pending > 0) {
81
- refs.add(s[0].slice(1, -1));
82
- pending--;
83
- }
84
- cursor = s.index + s[0].length;
85
- }
86
- }
87
- return refs;
88
- }
89
-
90
- // A ref resolves if (a) the full dotted name is a declared variable, OR
91
- // (b) it's a strict prefix of some declared variable (namespace navigation
92
- // like .session in `.session.id` when only `session.id` is declared).
93
- export function refResolves(ref: string, allVars: Set<string>): boolean {
94
- if (allVars.has(ref)) return true;
95
- const prefix = `${ref}.`;
96
- for (const name of allVars) {
97
- if (name.startsWith(prefix)) return true;
98
- }
99
- return false;
100
- }
@@ -1,38 +0,0 @@
1
- // [LAW:one-source-of-truth] The loader-side half of the disclosure primitive
2
- // (src/config/disclosure.ts): the ONE reserved-namespace collision check both
3
- // synthesis passes run. Group sugar reserves `groups.` and the `{{ menu }}`
4
- // helper reserves `menus.`; each synthesizes its `state` var, `cycle` action, and
5
- // (for a group) a toggle segment under its prefix, so a user-authored name under
6
- // that prefix must be a loud load error — never a silent overwrite of a
7
- // synthesized artifact. The check was duplicated verbatim in both passes; it lives
8
- // here now, parameterized by the prefix and a human description of what
9
- // synthesizes it, so the two body-kinds share one enforcer [LAW:single-enforcer].
10
-
11
- import type { Mutable, ValidateCtx } from "./validate-core.js";
12
- import type { RawDslConfig } from "../dsl-types.js";
13
- import { findKeyLine } from "./diagnostics.js";
14
-
15
- // [LAW:no-silent-failure] Reject every user name under the reserved prefix across
16
- // all three declaration sections (a synthesized disclosure lands in each), before
17
- // synthesis writes into them — so a `groups.*`/`menus.*` squatter surfaces as a
18
- // rename-pointing error rather than being silently shadowed. `synthesizedBy`
19
- // names the feature in the message (e.g. "group nodes", "{{ menu }} helpers") so
20
- // the author knows which sugar owns the prefix.
21
- export function reservedNamespaceCollisions(
22
- ctx: ValidateCtx,
23
- out: Mutable<RawDslConfig>,
24
- ns: string,
25
- synthesizedBy: string,
26
- ): void {
27
- for (const section of ["variables", "actions", "segments"] as const) {
28
- for (const name of Object.keys(out[section] ?? {})) {
29
- if (name.startsWith(ns)) {
30
- ctx.issues.push({
31
- path: `${section}.${name}`,
32
- message: `"${name}" is in the reserved "${ns}" namespace (synthesized by ${synthesizedBy}) — rename it`,
33
- line: findKeyLine(ctx.source, ["root"]),
34
- });
35
- }
36
- }
37
- }
38
- }
@@ -1,120 +0,0 @@
1
- // [LAW:types-are-the-program] The segment schema: a required `template` plus
2
- // optional layout/paint/visibility fields and a nested `vars` block (validated by
3
- // the variable schema). Declared as DATA (SEGMENT_SCHEMA) and interpreted by the
4
- // generic `record` engine — a plain record with no cross-field invariant, so two
5
- // value-shaped fields (`width`, `vars`) carry their bespoke parse as field specs.
6
- // This file changes when a segment field is added or removed.
7
-
8
- import {
9
- JUSTIFY_MODES,
10
- TRUNCATE_MODES,
11
- type SegmentDecl,
12
- type VariableDecl,
13
- } from "../dsl-types.js";
14
- import { findKeyLine } from "./diagnostics.js";
15
- import {
16
- describeType,
17
- describeValue,
18
- isPlainObject,
19
- optionalEnumSpec,
20
- optionalStringSpec,
21
- paletteSpec,
22
- record,
23
- recordJson,
24
- requireStringSpec,
25
- type FieldSpec,
26
- type FieldSpecMap,
27
- type JsonNode,
28
- type RecordSchema,
29
- type ValidateCtx,
30
- } from "./validate-core.js";
31
- import { validateVariables, variablesMapJson } from "./variables.js";
32
-
33
- export function validateSegments(
34
- ctx: ValidateCtx,
35
- raw: unknown,
36
- ): Record<string, SegmentDecl> {
37
- if (raw === undefined) return {};
38
- if (!isPlainObject(raw)) {
39
- ctx.issues.push({
40
- path: "segments",
41
- message: `segments must be an object, got ${describeType(raw)}`,
42
- line: findKeyLine(ctx.source, ["segments"]),
43
- });
44
- return {};
45
- }
46
-
47
- const out: Record<string, SegmentDecl> = {};
48
- for (const [name, decl] of Object.entries(raw)) {
49
- const parsed = record(ctx, SEGMENT_SCHEMA, `segments.${name}`, decl);
50
- if (parsed !== null) out[name] = parsed;
51
- }
52
- return out;
53
- }
54
-
55
- // [LAW:types-are-the-program] `width` is `"auto"` or a positive integer — a union
56
- // the generic string/enum specs cannot express, so it carries its own parse and
57
- // bespoke message as DATA. Absent → omitted; present-and-wrong → issue + omitted.
58
- function widthSpec(): FieldSpec<"auto" | number> {
59
- return {
60
- required: false,
61
- json: { anyOf: [{ const: "auto" }, { type: "integer", minimum: 1 }] },
62
- parse: (ctx, path, field, raw) => {
63
- const v = raw[field];
64
- if (v === undefined) return undefined;
65
- if (v === "auto") return "auto";
66
- if (typeof v === "number" && Number.isInteger(v) && v > 0) return v;
67
- ctx.issues.push({
68
- path: `${path}.${field}`,
69
- message: `width must be "auto" or a positive integer, got ${describeValue(v)}`,
70
- line: findKeyLine(ctx.source, [...path.split("."), field]),
71
- });
72
- return undefined;
73
- },
74
- };
75
- }
76
-
77
- // [LAW:decomposition] The nested `vars` block defers to the variable schema —
78
- // recursion by reuse of the already-migrated per-name taggedUnion, not a new
79
- // combinator. Absent → omitted; present → the parsed map (possibly empty, with
80
- // its own issues already reported), mirroring the old `if (raw.vars !== undefined)`.
81
- function varsSpec(): FieldSpec<Readonly<Record<string, VariableDecl>>> {
82
- return {
83
- required: false,
84
- // [LAW:one-source-of-truth] The nested `vars` schema is the SAME name →
85
- // VariableDecl map the top-level `variables` block emits — one source.
86
- json: variablesMapJson(),
87
- parse: (ctx, path, field, raw) => {
88
- const v = raw[field];
89
- if (v === undefined) return undefined;
90
- return validateVariables(ctx, `${path}.${field}`, v);
91
- },
92
- };
93
- }
94
-
95
- // [LAW:dataflow-not-control-flow] The segment's shape as DATA over its fields, in
96
- // declaration order. The engine runs every spec, rejects unknown keys, and fails
97
- // the segment when `template` is absent or invalid — the isPlainObject guard,
98
- // unknown-key loop, result-threading, and optional-omission the old body hand-rolled.
99
- const SEGMENT_FIELDS: FieldSpecMap<SegmentDecl> = {
100
- template: requireStringSpec(),
101
- width: widthSpec(),
102
- justify: optionalEnumSpec(JUSTIFY_MODES),
103
- truncate: optionalEnumSpec(TRUNCATE_MODES),
104
- bg: optionalStringSpec(),
105
- fg: optionalStringSpec(),
106
- when: optionalStringSpec(),
107
- palette: paletteSpec(),
108
- vars: varsSpec(),
109
- };
110
-
111
- const SEGMENT_SCHEMA: RecordSchema<SegmentDecl> = {
112
- noun: "segment key",
113
- fields: SEGMENT_FIELDS,
114
- };
115
-
116
- // [LAW:one-source-of-truth] The `segments` block is a name → SegmentDecl map,
117
- // derived from the SAME SEGMENT_SCHEMA the validator interprets.
118
- export function segmentsJson(): JsonNode {
119
- return { type: "object", additionalProperties: recordJson(SEGMENT_SCHEMA) };
120
- }