@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,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
|
-
}
|