@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
package/src/config/presets.ts
DELETED
|
@@ -1,326 +0,0 @@
|
|
|
1
|
-
// [LAW:one-source-of-truth] The preset RESOLUTION seam: the three questions a
|
|
2
|
-
// render asks about presets — which one is active, what layout does it stage,
|
|
3
|
-
// what display globals does it carry — answered in one place, from one map.
|
|
4
|
-
//
|
|
5
|
-
// A preset is to configuration what a look is to a theme, and that is the
|
|
6
|
-
// implementation instruction, not an analogy: the selection rides the SAME
|
|
7
|
-
// per-config-member seam looks rides (effectiveMemberName in themes/policy.ts),
|
|
8
|
-
// the domain is threaded as DATA through the same perConfigDomainsFor the click
|
|
9
|
-
// gate and the rendered options both read, and an unknown name collapses to the
|
|
10
|
-
// same kind of always-present floor. Nothing here is parallel machinery
|
|
11
|
-
// [LAW:one-type-per-behavior].
|
|
12
|
-
//
|
|
13
|
-
// WHERE THE PRESET LAYER SITS — the five-layer precedence chain, documented in
|
|
14
|
-
// full in docs/interaction-authoring.md ("persist / reset"), which is the ONE
|
|
15
|
-
// place it is written down:
|
|
16
|
-
//
|
|
17
|
-
// bundled default < user config file < persisted overrides
|
|
18
|
-
// < ACTIVE PRESET < session pick < EDIT MODE
|
|
19
|
-
//
|
|
20
|
-
// The preset's position is forced by its lifetime, not chosen. Everything to
|
|
21
|
-
// its left is resolved once per RenderCache entry (an entry serves many
|
|
22
|
-
// sessions: the user file and the overrides file are both read in buildState);
|
|
23
|
-
// everything from the preset rightward is resolved per render, because the pick
|
|
24
|
-
// is per session. The chain is therefore monotonic in "how late is this
|
|
25
|
-
// decided", which is why a preset overrides a persisted default (switching to a
|
|
26
|
-
// "compact" arrangement must actually change padding, even for a user who once
|
|
27
|
-
// persisted a padding they liked) while a session's own click still wins over
|
|
28
|
-
// the preset (a click is later still).
|
|
29
|
-
//
|
|
30
|
-
// The same rule places the last rung (candybar-settings-ui-aok.5): edit mode's
|
|
31
|
-
// `editGlobals` fragment is decided later than ANY session pick — a user picks
|
|
32
|
-
// a style, and only afterwards enters edit mode — so it is the new rightmost
|
|
33
|
-
// layer, and a session pick of "capsule" cannot survive into a mode whose whole
|
|
34
|
-
// job is to stop segments reading as one continuous strip. It differs from
|
|
35
|
-
// every rung to its left in LIFETIME rather than in kind: nothing writes it to
|
|
36
|
-
// SessionState or the overrides layer, so leaving edit mode restores the
|
|
37
|
-
// previous look with no save/restore path — the session's own pick was never
|
|
38
|
-
// overwritten, only out-ranked [LAW:dataflow-not-control-flow]. The rung itself
|
|
39
|
-
// is the `staged` parameter of effectiveGlobal (themes/policy.ts); this comment
|
|
40
|
-
// is the ONE place the order is written down.
|
|
41
|
-
|
|
42
|
-
// [LAW:one-way-deps] Type-only, so nothing is emitted and option-domain.ts (a
|
|
43
|
-
// leaf that deliberately never imports dsl-types.ts) can import PRESET_NAMES
|
|
44
|
-
// from here without a runtime cycle.
|
|
45
|
-
import type {
|
|
46
|
-
DslConfig,
|
|
47
|
-
Globals,
|
|
48
|
-
LayoutNode,
|
|
49
|
-
PresetDecl,
|
|
50
|
-
} from "./dsl-types.js";
|
|
51
|
-
import { effectiveMemberName } from "../themes/policy.js";
|
|
52
|
-
import { applyLayoutOps, decodeLayoutOp } from "./layout-ops.js";
|
|
53
|
-
|
|
54
|
-
// [LAW:one-source-of-truth] The floor preset's name, spelled once. `looks` has
|
|
55
|
-
// `"none"` (the identity adaptation); presets have `"default"` (the identity
|
|
56
|
-
// fragment — the empty PresetDecl, i.e. the config's own root and globals
|
|
57
|
-
// unchanged). The bundled default declares it and merge-by-name cannot remove
|
|
58
|
-
// it, so every merged DslConfig carries it by construction.
|
|
59
|
-
export const PRESET_FLOOR = "default";
|
|
60
|
-
|
|
61
|
-
// [LAW:dataflow-not-control-flow] The floor's fragment is the EMPTY one — no
|
|
62
|
-
// alternative root, no globals delta — which is exactly what "no preset chosen"
|
|
63
|
-
// already means. It is a value the lookup below starts from rather than a case
|
|
64
|
-
// the lookup handles, so the floor resolves whether or not any config declares
|
|
65
|
-
// it. The bundled default declares `default: {}` for a different job: to put the
|
|
66
|
-
// floor in the DOMAIN, so a `{{ menu }}` lists it and the derived click gate
|
|
67
|
-
// admits a click that returns to it. Domain membership and resolvability are two
|
|
68
|
-
// guarantees, and this one does not lean on the other.
|
|
69
|
-
const FLOOR_FRAGMENT: PresetDecl = {};
|
|
70
|
-
|
|
71
|
-
// [LAW:one-source-of-truth] THE preset domain: every arrangement selectable in
|
|
72
|
-
// this config — the declared alternatives with the floor always among them. The
|
|
73
|
-
// three readers that each need "the preset names" (perConfigDomainsFor, which
|
|
74
|
-
// feeds the rendered options AND the derived click gate; registerDslConfig's
|
|
75
|
-
// per-preset compile; the `presets` template binding) call this rather than
|
|
76
|
-
// spelling `Object.keys(config.presets)` themselves, so the menu you can see,
|
|
77
|
-
// the click the wire admits, and the layouts that were compiled are the same
|
|
78
|
-
// set by construction — a click returning to the floor cannot be rejected by a
|
|
79
|
-
// gate that forgot the floor was selectable.
|
|
80
|
-
//
|
|
81
|
-
// This is deliberately STRONGER than the looks seam it otherwise mirrors, where
|
|
82
|
-
// "none" is in the domain only because the bundled stdlib ships it. The floor's
|
|
83
|
-
// membership is a fact about the resolution, not about any config, so it is
|
|
84
|
-
// stated here once rather than depending on a merge going right.
|
|
85
|
-
export function presetNames(
|
|
86
|
-
presets: Readonly<Record<string, unknown>>,
|
|
87
|
-
): readonly string[] {
|
|
88
|
-
return [...new Set([PRESET_FLOOR, ...Object.keys(presets)])];
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// [LAW:one-type-per-behavior] The preset domain's instance of the shared
|
|
92
|
-
// per-config-member resolver — the same call shape effectiveLookName makes, one
|
|
93
|
-
// dimension over. A stale or deleted name collapses to PRESET_FLOOR rather than
|
|
94
|
-
// throwing, and the caller publishes this RESOLVED name as `preset.effective`
|
|
95
|
-
// so the bar's label and the bar's layout can never disagree
|
|
96
|
-
// [LAW:no-silent-failure].
|
|
97
|
-
export function effectivePresetName(
|
|
98
|
-
sessionPreset: string | null,
|
|
99
|
-
globalsPreset: string | undefined,
|
|
100
|
-
declaredPresets: Readonly<Record<string, PresetDecl>>,
|
|
101
|
-
): string {
|
|
102
|
-
// [LAW:types-are-the-program] No staged rung, and the absence is enforced
|
|
103
|
-
// rather than assumed: every globals fragment that could stage one — a
|
|
104
|
-
// preset's own `globals`, edit mode's `editGlobals` — has `preset` swapped for
|
|
105
|
-
// a rejection in its schema (loader/globals.ts), so "a fragment selected a
|
|
106
|
-
// preset" is unrepresentable and there is nothing here to resolve against.
|
|
107
|
-
// Which preset is active keeps exactly one authority.
|
|
108
|
-
return effectiveMemberName(
|
|
109
|
-
undefined,
|
|
110
|
-
sessionPreset,
|
|
111
|
-
globalsPreset,
|
|
112
|
-
PRESET_FLOOR,
|
|
113
|
-
declaredPresets,
|
|
114
|
-
);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// [LAW:single-enforcer] The one place an effective preset NAME becomes the
|
|
118
|
-
// fragment a render reads — a declared preset, or the floor's identity fragment
|
|
119
|
-
// layered under them so the floor never depends on being declared. By the time
|
|
120
|
-
// a name reaches here it must be one of those: effectivePresetName collapses
|
|
121
|
-
// unknown names to the floor.
|
|
122
|
-
// [LAW:no-defensive-null-guards] the throw is the loud failure for that broken
|
|
123
|
-
// invariant (a caller that skipped the resolution and passed a raw session
|
|
124
|
-
// string), never a silent empty-fragment fallback that would render one
|
|
125
|
-
// arrangement while the bar's label named another — the exact contract
|
|
126
|
-
// lookKeyByName holds for looks.
|
|
127
|
-
export function presetByName(
|
|
128
|
-
presets: Readonly<Record<string, PresetDecl>>,
|
|
129
|
-
name: string,
|
|
130
|
-
): PresetDecl {
|
|
131
|
-
const preset = { [PRESET_FLOOR]: FLOOR_FRAGMENT, ...presets }[name];
|
|
132
|
-
if (preset === undefined) {
|
|
133
|
-
throw new Error(
|
|
134
|
-
`Preset "${name}" is not declared in this config — effectivePresetName ` +
|
|
135
|
-
`collapses unknown names to "${PRESET_FLOOR}", which always resolves; ` +
|
|
136
|
-
`a miss here means a raw name reached this function without going ` +
|
|
137
|
-
`through that resolution`,
|
|
138
|
-
);
|
|
139
|
-
}
|
|
140
|
-
return preset;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
// A preset's layout AND the config path that layout was authored at, as a total
|
|
144
|
-
// function of the name: a preset that declares no `root` stages the config's own
|
|
145
|
-
// root, which lives at `root` and not under this preset's name.
|
|
146
|
-
//
|
|
147
|
-
// [LAW:one-source-of-truth] Both halves come from ONE decision on purpose. The
|
|
148
|
-
// fallback used to be resolved here while the diagnostic path was spelled
|
|
149
|
-
// separately at the compile site as `presets.<name>.root`, so the two disagreed
|
|
150
|
-
// for exactly the configs that never opted into presets at all: a plain config
|
|
151
|
-
// with no `presets:` block reported its own root's template errors under
|
|
152
|
-
// `presets.default.root`, naming a node the author never wrote. Returning the
|
|
153
|
-
// tree together with where it came from makes that drift unrepresentable rather
|
|
154
|
-
// than merely fixed [FRAMING:representation].
|
|
155
|
-
export function presetRoot(
|
|
156
|
-
config: DslConfig,
|
|
157
|
-
name: string,
|
|
158
|
-
): { readonly node: LayoutNode; readonly path: string } {
|
|
159
|
-
// [LAW:dataflow-not-control-flow] A projection returning DATA, not a branch
|
|
160
|
-
// around an operation: both arms yield the same shape, and the discriminator
|
|
161
|
-
// (did this preset declare a root?) is a fact the fragment already carries.
|
|
162
|
-
const own = presetByName(config.presets, name).root;
|
|
163
|
-
return own === undefined
|
|
164
|
-
? { node: config.root, path: "root" }
|
|
165
|
-
: { node: own, path: `presets.${name}.root` };
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
// [LAW:dataflow-not-control-flow] A preset's display globals, as a total
|
|
169
|
-
// function of the name: the config's globals with the preset's shallow-merged
|
|
170
|
-
// over them, per field — the SAME per-field cascade mergeWithDefault applies to
|
|
171
|
-
// `globals` everywhere else in the loader, so a preset naming `padding` says
|
|
172
|
-
// nothing about `charset`. The empty fragment yields the config's globals
|
|
173
|
-
// unchanged, so again no floor-shaped branch.
|
|
174
|
-
export function presetGlobals(config: DslConfig, name: string): Globals {
|
|
175
|
-
return { ...config.globals, ...presetByName(config.presets, name).globals };
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
// [LAW:no-silent-failure] The persisted-overrides analogue of
|
|
179
|
-
// applySegmentPaletteOverrides' stale-segment skip (src/config/loader/
|
|
180
|
-
// merge.ts), one seam over: `configOverridesPath()` is ONE file shared by
|
|
181
|
-
// every project on the machine (candybar-config-engine-71o.2), but `presets`
|
|
182
|
-
// is a PER-CONFIG domain — the one other per-config domain besides `looks`.
|
|
183
|
-
// A `persist: "preset"` click writes a name valid for the config it was
|
|
184
|
-
// clicked against; loading a DIFFERENT project whose config never declared
|
|
185
|
-
// that name must not fail the whole render. validateConfig's cross-ref check
|
|
186
|
-
// (loader/cross-ref.ts) stays fatal for a hand-AUTHORED `globals.preset` —
|
|
187
|
-
// that is a typo in a file the author can see and fix — but a value that
|
|
188
|
-
// only exists because the overrides layer supplied it gets the SAME
|
|
189
|
-
// treatment a stale SessionState pick already gets from effectivePresetName:
|
|
190
|
-
// an undeclared name is not a pick at all, so the resolution moves on to the
|
|
191
|
-
// next rung of the precedence chain — visibly, never a throw. (For a stale
|
|
192
|
-
// SESSION pick that next rung is the config's own `globals.preset`, and only
|
|
193
|
-
// then the floor; for the field dropped here it is whatever the config file
|
|
194
|
-
// itself declares. Same rule, different starting rung —
|
|
195
|
-
// see effectiveGlobal in themes/policy.ts.) Dropping the field here (so it
|
|
196
|
-
// never reaches validateConfig) is what applies that rule to this layer too,
|
|
197
|
-
// instead of a fatal error replacing the whole bar.
|
|
198
|
-
export function sanitizePersistedPresetOverride(
|
|
199
|
-
globalsOverride: Partial<Globals>,
|
|
200
|
-
declaredPresets: Readonly<Record<string, PresetDecl>>,
|
|
201
|
-
): Partial<Globals> {
|
|
202
|
-
const preset = globalsOverride.preset;
|
|
203
|
-
if (preset === undefined || presetNames(declaredPresets).includes(preset)) {
|
|
204
|
-
return globalsOverride;
|
|
205
|
-
}
|
|
206
|
-
// [LAW:no-defensive-null-guards] Not a broken-invariant throw: an
|
|
207
|
-
// overrides file legitimately outlives any one config's preset names, so
|
|
208
|
-
// this IS the expected shape, not a caller bug — a plain drop, mirroring
|
|
209
|
-
// applySegmentPaletteOverrides' `continue` for the identical reason.
|
|
210
|
-
const rest = { ...globalsOverride };
|
|
211
|
-
delete rest.preset;
|
|
212
|
-
return rest;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
// [LAW:no-silent-failure] sanitizePersistedPresetOverride's twin for the
|
|
216
|
-
// SAME machine-global overrides file, one field over: `presets.<name>.
|
|
217
|
-
// rootOps` entries persist by NAME, and that name only has meaning against
|
|
218
|
-
// the config it was clicked against. Loading a DIFFERENT project's config
|
|
219
|
-
// — one that never declared "compact", say — would otherwise reach
|
|
220
|
-
// applyPresetRootOpsOverrides -> presetRoot -> presetByName, which THROWS
|
|
221
|
-
// for an undeclared name (correctly so for a hand-AUTHORED preset root: a
|
|
222
|
-
// typo in a file the author can see). A stale overrides entry is not that
|
|
223
|
-
// — it is the expected shape of a file that outlives any one project's
|
|
224
|
-
// preset names — so it gets the SAME treatment sanitizePersistedPresetOverride
|
|
225
|
-
// already gives `globals.preset`: dropped before replay ever sees it,
|
|
226
|
-
// never a fatal error replacing an unrelated project's entire bar
|
|
227
|
-
// (brandon-layout-edit-2gc.5 PR review).
|
|
228
|
-
export function sanitizePersistedPresetRootOps(
|
|
229
|
-
presetRootOps: Readonly<Record<string, readonly string[]>>,
|
|
230
|
-
declaredPresets: Readonly<Record<string, PresetDecl>>,
|
|
231
|
-
): Readonly<Record<string, readonly string[]>> {
|
|
232
|
-
const known = new Set(presetNames(declaredPresets));
|
|
233
|
-
const entries = Object.entries(presetRootOps).filter(([name]) =>
|
|
234
|
-
known.has(name),
|
|
235
|
-
);
|
|
236
|
-
// [LAW:carrying-cost] Identity return when nothing was dropped — the
|
|
237
|
-
// common case (this daemon serves one project, or every persisted name
|
|
238
|
-
// happens to be declared) costs one Set build and no new allocation.
|
|
239
|
-
if (entries.length === Object.keys(presetRootOps).length) {
|
|
240
|
-
return presetRootOps;
|
|
241
|
-
}
|
|
242
|
-
return Object.fromEntries(entries);
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
// [LAW:one-source-of-truth] brandon-layout-edit-2gc.1's replay step — the
|
|
246
|
-
// SAME "patch an already-merged config" shape applySegmentPaletteOverrides
|
|
247
|
-
// (src/config/loader/merge.ts) uses one field over, run at the SAME point in
|
|
248
|
-
// RenderCache.buildState (after the globals/segment-palette overrides, before
|
|
249
|
-
// validateConfig): for every preset with an accumulated op log, resolve its
|
|
250
|
-
// CURRENT root the normal way (presetRoot — bundled/user root, or the
|
|
251
|
-
// preset's own declared fragment) and replay the ops on top, writing the
|
|
252
|
-
// result back as that preset's `root`. Every later reader (presetRoot,
|
|
253
|
-
// registerDslConfig's per-preset compile, validateConfig's cross-ref walk)
|
|
254
|
-
// sees the patched tree as if it had been authored that way — no second
|
|
255
|
-
// resolution path [LAW:locality-or-seam].
|
|
256
|
-
//
|
|
257
|
-
// [LAW:no-silent-failure] exception: an op whose target/anchor names a
|
|
258
|
-
// segment absent from the CURRENT tree is a no-op (layout-ops.ts's own
|
|
259
|
-
// documented policy) — a validated action can only ever name a segment the
|
|
260
|
-
// config declares at the time it was clicked, so a miss here only happens
|
|
261
|
-
// after a LATER edit (a config change, or an earlier op in the same list)
|
|
262
|
-
// already removed it. A malformed individual token (decodeLayoutOp -> null;
|
|
263
|
-
// can only arise from hand-edited or previous-version state, never from this
|
|
264
|
-
// process's own encodeLayoutOp) is filtered the same way, never applied.
|
|
265
|
-
// [LAW:one-source-of-truth] brandon-layout-edit-2gc.5's diagnostic seam — the
|
|
266
|
-
// ONE definition of "does this preset's rendered layout currently differ
|
|
267
|
-
// from what's literally declared in the config file", read from the SAME
|
|
268
|
-
// raw op-log record applyPresetRootOpsOverrides replays (never re-derived by
|
|
269
|
-
// diffing the replayed tree against a fresh presetRoot() call, which would
|
|
270
|
-
// be a second, weaker definition — a structural coincidence between two
|
|
271
|
-
// unrelated edits could equal zero net ops and still disagree with the raw
|
|
272
|
-
// log's own length). A name absent from the record (never edited) and a
|
|
273
|
-
// name present with an empty token list (edited down to nothing, e.g. by
|
|
274
|
-
// undo) are the SAME "not customized" — presence in the record only ever
|
|
275
|
-
// means "this reload's overrides file had an entry", not "and it's non-
|
|
276
|
-
// empty" [LAW:no-defensive-null-guards].
|
|
277
|
-
//
|
|
278
|
-
// Decodes each token the SAME way applyPresetRootOpsOverrides does, rather
|
|
279
|
-
// than trusting the raw token COUNT — a token list where every token
|
|
280
|
-
// decodes to null (malformed, or written by a previous protocol version)
|
|
281
|
-
// is exactly the case applyPresetRootOpsOverrides itself treats as "no ops
|
|
282
|
-
// to replay" (`ops.length === 0 → continue`, leaving the root untouched).
|
|
283
|
-
// Counting raw tokens would disagree with that: "customized" would read
|
|
284
|
-
// true over a tree that is, in fact, byte-identical to the literal
|
|
285
|
-
// declared root — the inverted form of the drift this diagnostic exists to
|
|
286
|
-
// catch.
|
|
287
|
-
//
|
|
288
|
-
// [LAW:carrying-cost] Verifies DECODE only, not whether the decoded op's
|
|
289
|
-
// target/anchor still resolves against the CURRENT tree — that check would
|
|
290
|
-
// need this to duplicate applyLayoutOps' own stale-target resolution
|
|
291
|
-
// (a real walk of the tree) just to COUNT, on every render, a fact only a
|
|
292
|
-
// LATER hand-authored config edit can produce (removing a segment a stored
|
|
293
|
-
// op still names — see the stale-op comment on applyPresetRootOpsOverrides
|
|
294
|
-
// above). A rare, later-edit-triggered false "customized" is the honest,
|
|
295
|
-
// documented cost of keeping this an O(tokens) check rather than an O(tree)
|
|
296
|
-
// one; the doc at docs/interaction-authoring.md's "Knowing when a preset's
|
|
297
|
-
// layout has been edited" section states this same limit.
|
|
298
|
-
export function presetIsCustomized(
|
|
299
|
-
presetRootOps: Readonly<Record<string, readonly string[]>>,
|
|
300
|
-
name: string,
|
|
301
|
-
): boolean {
|
|
302
|
-
const tokens = presetRootOps[name];
|
|
303
|
-
if (tokens === undefined) return false;
|
|
304
|
-
return tokens.some((t) => decodeLayoutOp(t) !== null);
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
export function applyPresetRootOpsOverrides(
|
|
308
|
-
config: DslConfig,
|
|
309
|
-
presetRootOps: Readonly<Record<string, readonly string[]>>,
|
|
310
|
-
): DslConfig {
|
|
311
|
-
const entries = Object.entries(presetRootOps).filter(
|
|
312
|
-
([, tokens]) => tokens.length > 0,
|
|
313
|
-
);
|
|
314
|
-
if (entries.length === 0) return config;
|
|
315
|
-
const presets: Record<string, PresetDecl> = { ...config.presets };
|
|
316
|
-
for (const [name, tokens] of entries) {
|
|
317
|
-
const ops = tokens.map(decodeLayoutOp).filter((op) => op !== null);
|
|
318
|
-
if (ops.length === 0) continue;
|
|
319
|
-
const { node } = presetRoot(config, name);
|
|
320
|
-
presets[name] = {
|
|
321
|
-
...presetByName(config.presets, name),
|
|
322
|
-
root: applyLayoutOps(node, ops),
|
|
323
|
-
};
|
|
324
|
-
}
|
|
325
|
-
return { ...config, presets };
|
|
326
|
-
}
|