@promptctl/cc-candybar 1.30.0 → 1.32.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.
@@ -28,7 +28,13 @@ 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_CONFIG, VERB_SET_STATE } from "../click/wire.js";
31
+ import {
32
+ effectsUrl,
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";
32
38
  import {
33
39
  linkFragment,
34
40
  readVar,
@@ -136,26 +142,33 @@ function requireKind<K extends CompiledActionDecl["kind"]>(
136
142
  return action as Extract<CompiledActionDecl, { kind: K }>;
137
143
  }
138
144
 
139
- // [LAW:one-source-of-truth] The apply action a picker grid binds to is EITHER
140
- // of set-option's two durability twins (src/render/action.ts's persist-*
141
- // mirrors set-*'s shapes one for one)a picker over `persist("charset",
142
- // from:"charsets")` is exactly as legal as one over `set("theme",
143
- // from:"themes")`, differing only in which wire verb the option click emits
144
- // (VERB_SET_CONFIG vs VERB_SET_STATE), never in shape (both carry key,
145
- // stateVar, options). Rejecting persist-option here would be an artificial
146
- // gap: the same option-domain gate (deriveActionValidators) covers both kinds
147
- // identically, so there is nothing about "picker" that's set-only.
145
+ // [LAW:one-source-of-truth] The apply action a picker grid binds to is one of
146
+ // THREE option-domain-driven kinds (src/render/action.ts): set-option/
147
+ // persist-option (a picked value is WRITTEN VERBATIM set-option's two
148
+ // durability twins, differing only in wire verb VERB_SET_CONFIG vs
149
+ // VERB_SET_STATE) or layout-op-option (a picked value is ENCODED into a
150
+ // structural LayoutOp before writing brandon-layout-edit-2gc.3's
151
+ // `insertSegmentFrom`). All three share the same option-domain gate
152
+ // (deriveActionValidators/deriveConfigActionValidators) and the same "pick a
153
+ // cell, apply it" shape; only WHAT the click writes differs, which is
154
+ // realize()'s job, not the picker's. Rejecting any of the three here would be
155
+ // an artificial gap — there is nothing about "picker" that excludes one kind.
148
156
  function requireOptionKind(
149
157
  runtime: ActionRuntime,
150
158
  name: string,
151
- ): Extract<CompiledActionDecl, { kind: "set-option" | "persist-option" }> {
159
+ ): Extract<
160
+ CompiledActionDecl,
161
+ { kind: "set-option" | "persist-option" | "layout-op-option" }
162
+ > {
152
163
  const action = runtime.compiled.get(name);
153
164
  if (
154
165
  !action ||
155
- (action.kind !== "set-option" && action.kind !== "persist-option")
166
+ (action.kind !== "set-option" &&
167
+ action.kind !== "persist-option" &&
168
+ action.kind !== "layout-op-option")
156
169
  ) {
157
170
  throw new Error(
158
- `picker references action "${name}" which must be a set-option or persist-option action ({ set, from } or { persist, from }), got ${action ? `a ${action.kind} action` : "no such action"}`,
171
+ `picker references action "${name}" which must be a set-option, persist-option, or layout-op-option action ({ set, from }, { persist, from }, or { persist, insertSegmentFrom, anchor, relation }), got ${action ? `a ${action.kind} action` : "no such action"}`,
159
172
  );
160
173
  }
161
174
  return action;
@@ -183,7 +196,13 @@ export function renderPicker(
183
196
  const apply = requireOptionKind(runtime, applyName);
184
197
  const store = runtime.store;
185
198
  const sessionId = readVar(store, "session.id");
186
- const current = readVar(store, apply.stateVar);
199
+ // [LAW:no-defensive-null-guards] layout-op-option carries no `stateVar` —
200
+ // a structural insert is a one-shot trigger, not a persisted single value,
201
+ // so there is no "current selection" to mark. `undefined` here (never a
202
+ // magic sentinel string) makes every option's `option === current` compare
203
+ // false below, structurally rather than by accident.
204
+ const current =
205
+ "stateVar" in apply ? readVar(store, apply.stateVar) : undefined;
187
206
  const widths = apply.options.map(cellWidth);
188
207
 
189
208
  // ✕ is always present; ←/→ appear only on a multi-page menu. Reserve arrow
@@ -243,30 +262,44 @@ export function renderPicker(
243
262
  { verb: VERB_SET_STATE, args: [sessionId, ...closeFlat] },
244
263
  ]);
245
264
  // [LAW:one-source-of-truth] A set-option apply folds its closeOnPick pairs
246
- // into ONE set-state batch (setState is variadic — see daemon/verbs). A
247
- // persist-option apply cannot: setConfig takes exactly one (key, value), so
248
- // its close pairs (always SessionState open/page live there regardless of
249
- // the apply's durability) ride as a SECOND effect in the same dispatch,
250
- // still one atomic click via effectsUrl's array.
251
- const optionUrl = (option: string): string =>
252
- apply.kind === "persist-option"
253
- ? effectsUrl([
254
- { verb: VERB_SET_CONFIG, args: [sessionId, apply.key, option] },
255
- ...(closeOnPick
256
- ? [{ verb: VERB_SET_STATE, args: [sessionId, ...closeFlat] }]
257
- : []),
258
- ])
259
- : effectsUrl([
260
- {
261
- verb: VERB_SET_STATE,
262
- args: [
263
- sessionId,
264
- apply.key,
265
- option,
266
- ...(closeOnPick ? closeFlat : []),
267
- ],
268
- },
269
- ]);
265
+ // into ONE set-state batch (setState is variadic — see daemon/verbs).
266
+ // persist-option and layout-op-option cannot: setConfig/apply-layout-op
267
+ // each take exactly one (key, value) pair, so their close pairs (always
268
+ // SessionState open/page live there regardless of the apply's
269
+ // durability) ride as a SECOND effect in the same dispatch, still one
270
+ // atomic click via effectsUrl's array. layout-op-option's "value" is the
271
+ // ENCODED op (segment=option, anchor/relation from the compiled action),
272
+ // not the option verbatim — the one place this kind's write differs from
273
+ // persist-option's.
274
+ const closeEffect = closeOnPick
275
+ ? [{ verb: VERB_SET_STATE, args: [sessionId, ...closeFlat] }]
276
+ : [];
277
+ const optionUrl = (option: string): string => {
278
+ if (apply.kind === "persist-option") {
279
+ return effectsUrl([
280
+ { verb: VERB_SET_CONFIG, args: [sessionId, apply.key, option] },
281
+ ...closeEffect,
282
+ ]);
283
+ }
284
+ if (apply.kind === "layout-op-option") {
285
+ const op = encodeLayoutOp({
286
+ op: "insert",
287
+ segment: option,
288
+ anchor: apply.anchor,
289
+ relation: apply.relation,
290
+ });
291
+ return effectsUrl([
292
+ { verb: VERB_APPLY_LAYOUT_OP, args: [sessionId, apply.key, op] },
293
+ ...closeEffect,
294
+ ]);
295
+ }
296
+ return effectsUrl([
297
+ {
298
+ verb: VERB_SET_STATE,
299
+ args: [sessionId, apply.key, option, ...(closeOnPick ? closeFlat : [])],
300
+ },
301
+ ]);
302
+ };
270
303
 
271
304
  const frags: RichText[] = [linkFragment(PICKER_CLOSE, closeUrl, false)];
272
305
  if (pageIdx > 0) {