@promptctl/cc-candybar 1.37.0 → 1.39.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 +96 -93
- package/package.json +5 -5
- package/schema/cc-candybar.schema.json +124 -0
- package/src/click/wire.ts +10 -1
- package/src/config/action.ts +110 -1
- package/src/config/default-dsl-config.ts +77 -246
- package/src/config/disclosure.ts +54 -0
- package/src/config/edit-chrome.ts +15 -1
- package/src/config/loader/actions.ts +138 -61
- package/src/config/loader/cross-ref.ts +48 -10
- package/src/config/loader/globals.ts +40 -0
- package/src/config/loader/menu-synth.ts +69 -18
- package/src/config/settings-menu.ts +339 -16
- package/src/daemon/verbs/config-validators.ts +14 -11
- package/src/daemon/verbs/index.ts +39 -3
- package/src/daemon/verbs/state-validators.ts +26 -8
- package/src/help-text.ts +6 -3
- package/src/install/index.ts +2 -2
- package/src/render/action.ts +210 -54
- package/src/render/menu.ts +75 -21
- package/src/render/picker.ts +39 -35
- package/src/themes/policy.ts +21 -4
package/src/render/picker.ts
CHANGED
|
@@ -28,21 +28,17 @@ import type { FuncMap } from "@promptctl/go-template-js";
|
|
|
28
28
|
import { toNumber } from "../var-system/types.js";
|
|
29
29
|
import { stripChromeCols } from "./strip.js";
|
|
30
30
|
import { TERM_COLS_VAR } from "../config/dsl-types.js";
|
|
31
|
+
import { effectsUrl, VERB_SET_STATE } from "../click/wire.js";
|
|
31
32
|
import {
|
|
32
|
-
|
|
33
|
-
VERB_APPLY_LAYOUT_OP,
|
|
34
|
-
VERB_SET_CONFIG,
|
|
35
|
-
VERB_SET_STATE,
|
|
36
|
-
} from "../click/wire.js";
|
|
37
|
-
import { encodeLayoutOp } from "../config/layout-ops.js";
|
|
38
|
-
import {
|
|
33
|
+
activeDestination,
|
|
39
34
|
linkFragment,
|
|
35
|
+
realize,
|
|
40
36
|
readVar,
|
|
41
37
|
type ActionRuntime,
|
|
42
38
|
type CompiledActionDecl,
|
|
43
39
|
} from "./action.js";
|
|
40
|
+
import { DISCLOSURE_GLYPH_CLOSE } from "../config/disclosure.js";
|
|
44
41
|
|
|
45
|
-
const PICKER_CLOSE = "✕";
|
|
46
42
|
const PICKER_PREV = "←";
|
|
47
43
|
const PICKER_NEXT = "→";
|
|
48
44
|
|
|
@@ -160,7 +156,15 @@ function requireOptionKind(
|
|
|
160
156
|
CompiledActionDecl,
|
|
161
157
|
{ kind: "set-option" | "persist-option" | "layout-op-option" }
|
|
162
158
|
> {
|
|
163
|
-
|
|
159
|
+
// [LAW:dataflow-not-control-flow] A dual-destination action resolves to the
|
|
160
|
+
// half its selector names BEFORE the kind check, so a picker over a dual is
|
|
161
|
+
// a picker over whichever option kind is live — the grid, its current-mark,
|
|
162
|
+
// and its close folding are the code they already were. The check below then
|
|
163
|
+
// still names a real, single-destination kind in its error.
|
|
164
|
+
const declared = runtime.compiled.get(name);
|
|
165
|
+
const action = declared
|
|
166
|
+
? activeDestination(declared, runtime.store)
|
|
167
|
+
: declared;
|
|
164
168
|
if (
|
|
165
169
|
!action ||
|
|
166
170
|
(action.kind !== "set-option" &&
|
|
@@ -194,6 +198,13 @@ export function renderPicker(
|
|
|
194
198
|
runtime: ActionRuntime,
|
|
195
199
|
): RichText {
|
|
196
200
|
const apply = requireOptionKind(runtime, applyName);
|
|
201
|
+
// [LAW:one-source-of-truth] The GRID reads the resolved half above (its
|
|
202
|
+
// options, its current-mark); the CLICK is realized from the declaration
|
|
203
|
+
// itself, through the same fold `{{ action }}` uses. That is what carries a
|
|
204
|
+
// dual's session clear into a picked option — the picker never learns what a
|
|
205
|
+
// dual is, and there is no second projection of "what does this option
|
|
206
|
+
// write" to drift from realize's.
|
|
207
|
+
const declared = runtime.compiled.get(applyName)!;
|
|
197
208
|
const store = runtime.store;
|
|
198
209
|
const sessionId = readVar(store, "session.id");
|
|
199
210
|
// [LAW:no-defensive-null-guards] layout-op-option carries no `stateVar` —
|
|
@@ -231,7 +242,7 @@ export function renderPicker(
|
|
|
231
242
|
2 * runtime.padding,
|
|
232
243
|
)
|
|
233
244
|
: Infinity;
|
|
234
|
-
const closeReserve = cellWidth(
|
|
245
|
+
const closeReserve = cellWidth(DISCLOSURE_GLYPH_CLOSE) + 1;
|
|
235
246
|
const arrowReserve = cellWidth(PICKER_PREV) + 1 + cellWidth(PICKER_NEXT) + 1;
|
|
236
247
|
const firstPass = paginate(widths, available, closeReserve);
|
|
237
248
|
const pages =
|
|
@@ -275,33 +286,26 @@ export function renderPicker(
|
|
|
275
286
|
? [{ verb: VERB_SET_STATE, args: [sessionId, ...closeFlat] }]
|
|
276
287
|
: [];
|
|
277
288
|
const optionUrl = (option: string): string => {
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
...closeEffect,
|
|
294
|
-
]);
|
|
295
|
-
}
|
|
296
|
-
return effectsUrl([
|
|
297
|
-
{
|
|
298
|
-
verb: VERB_SET_STATE,
|
|
299
|
-
args: [sessionId, apply.key, option, ...(closeOnPick ? closeFlat : [])],
|
|
300
|
-
},
|
|
301
|
-
]);
|
|
289
|
+
const { effects } = realize(declared, option, option, store, sessionId);
|
|
290
|
+
// [LAW:one-source-of-truth] A plain session pick folds its close pairs into
|
|
291
|
+
// the SAME set-state (setState is variadic), so closing and applying are
|
|
292
|
+
// one write. Every other shape — a durable write, a structural op, a dual
|
|
293
|
+
// carrying its session clear — takes more than one effect already, so its
|
|
294
|
+
// close rides as its own effect in the same atomic dispatch.
|
|
295
|
+
const solo = effects.length === 1 ? effects[0]! : undefined;
|
|
296
|
+
return solo?.verb === VERB_SET_STATE
|
|
297
|
+
? effectsUrl([
|
|
298
|
+
{
|
|
299
|
+
verb: VERB_SET_STATE,
|
|
300
|
+
args: [...solo.args, ...(closeOnPick ? closeFlat : [])],
|
|
301
|
+
},
|
|
302
|
+
])
|
|
303
|
+
: effectsUrl([...effects, ...closeEffect]);
|
|
302
304
|
};
|
|
303
305
|
|
|
304
|
-
const frags: RichText[] = [
|
|
306
|
+
const frags: RichText[] = [
|
|
307
|
+
linkFragment(DISCLOSURE_GLYPH_CLOSE, closeUrl, false),
|
|
308
|
+
];
|
|
305
309
|
if (pageIdx > 0) {
|
|
306
310
|
frags.push(linkFragment(PICKER_PREV, pageUrl(pageIdx - 1), false));
|
|
307
311
|
}
|
package/src/themes/policy.ts
CHANGED
|
@@ -270,7 +270,16 @@ export const DEFAULT_WRAP = true;
|
|
|
270
270
|
// boolean globals field — the same two members the bundled default's
|
|
271
271
|
// `cycle: [...]` toggle writes and the parse below reads. Spelled once so a
|
|
272
272
|
// toggle cannot write a member the resolver refuses to parse.
|
|
273
|
-
|
|
273
|
+
// [LAW:one-source-of-truth] The two members, named, because their ORDER is
|
|
274
|
+
// meaningful and differs per control: a cycle's members are ordered
|
|
275
|
+
// default-state-first (an unwritten key counts as the first member and clicks
|
|
276
|
+
// to the second), so `autoWrap` — on by default — cycles ["true","false"]
|
|
277
|
+
// while `persist?` — off by default — cycles [BOOLEAN_FALSE, BOOLEAN_TRUE].
|
|
278
|
+
// Spelling the members rather than reversing the pair keeps each declaration's
|
|
279
|
+
// default state readable at its own site.
|
|
280
|
+
export const BOOLEAN_TRUE = "true";
|
|
281
|
+
export const BOOLEAN_FALSE = "false";
|
|
282
|
+
export const BOOLEAN_MEMBERS = [BOOLEAN_TRUE, BOOLEAN_FALSE] as const;
|
|
274
283
|
|
|
275
284
|
// [LAW:one-source-of-truth] The one statement of the globals.padding default
|
|
276
285
|
// (one space per side inside each segment cell — current behavior, matching the
|
|
@@ -287,8 +296,16 @@ export const PADDING_RANGE = { min: 0, max: 16 } as const;
|
|
|
287
296
|
// [LAW:parse-dont-validate] A SessionState string to a boolean, or null for
|
|
288
297
|
// anything else. `??` in effectiveGlobal (never `||`) is what keeps a parsed
|
|
289
298
|
// `false` a real answer rather than falling through to the default.
|
|
290
|
-
|
|
291
|
-
|
|
299
|
+
//
|
|
300
|
+
// [LAW:one-source-of-truth] Exported because SessionState holds strings and
|
|
301
|
+
// BOOLEAN_MEMBERS above is the one spelling of a boolean in that store — so
|
|
302
|
+
// every reader of a boolean session key parses it HERE, not with its own
|
|
303
|
+
// truthiness rule. The second reader is the dual-destination action's
|
|
304
|
+
// `persistWhen` selector (src/render/action.ts): "is persist? checked" is the
|
|
305
|
+
// same question `autoWrap`'s toggle asks of its own key, and a bespoke
|
|
306
|
+
// `raw !== ""` there would accept values this parse rejects.
|
|
307
|
+
export function parseSessionBoolean(raw: string): boolean | null {
|
|
308
|
+
return raw === BOOLEAN_TRUE ? true : raw === BOOLEAN_FALSE ? false : null;
|
|
292
309
|
}
|
|
293
310
|
|
|
294
311
|
// [LAW:parse-dont-validate] A SessionState string to a padding value inside the
|
|
@@ -313,7 +330,7 @@ export function effectiveAutoWrap(
|
|
|
313
330
|
sessionAutoWrap,
|
|
314
331
|
globalsAutoWrap,
|
|
315
332
|
DEFAULT_WRAP,
|
|
316
|
-
|
|
333
|
+
parseSessionBoolean,
|
|
317
334
|
);
|
|
318
335
|
}
|
|
319
336
|
|