@promptctl/cc-candybar 1.26.0 → 1.27.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 +86 -85
- package/package.json +6 -6
- package/schema/cc-candybar.schema.json +193 -4
- package/src/check.ts +49 -27
- package/src/click/wire.ts +16 -0
- package/src/config/action.ts +57 -22
- package/src/config/default-dsl-config.ts +424 -55
- package/src/config/dsl-loader.ts +14 -2
- package/src/config/dsl-types.ts +59 -0
- package/src/config/loader/actions.ts +283 -109
- package/src/config/loader/cross-ref.ts +148 -28
- package/src/config/loader/emit-schema.ts +2 -0
- package/src/config/loader/globals.ts +118 -31
- package/src/config/loader/merge.ts +58 -1
- package/src/config/loader/persist-target.ts +32 -0
- package/src/config/loader/presets.ts +107 -0
- package/src/config/option-domain.ts +164 -0
- package/src/config/presets.ts +156 -0
- package/src/daemon/cache/git.ts +1 -1
- package/src/daemon/cache/render.ts +61 -7
- package/src/daemon/config-overrides-store.ts +322 -0
- package/src/daemon/paths.ts +10 -0
- package/src/daemon/render-payload.ts +84 -19
- package/src/daemon/server.ts +68 -55
- package/src/daemon/verbs/config-validators.ts +127 -0
- package/src/daemon/verbs/index.ts +129 -2
- package/src/daemon/verbs/state-validators.ts +98 -586
- package/src/daemon/verbs/validator-registry.ts +457 -0
- package/src/demo/dsl.ts +17 -10
- package/src/dsl/node-registry.ts +54 -39
- package/src/dsl/render.ts +158 -46
- package/src/help-text.ts +3 -3
- package/src/install/index.ts +2 -2
- package/src/render/action.ts +155 -33
- package/src/render/active-segment.ts +78 -0
- package/src/render/menu.ts +16 -11
- package/src/render/picker.ts +51 -13
- package/src/render/segment-color.ts +74 -0
- package/src/segments/git.ts +389 -48
- package/src/template-engine/colors.ts +67 -45
- package/src/template-engine/engine.ts +11 -12
- package/src/themes/index.ts +1 -4
- package/src/themes/palette-resolvers.ts +22 -30
- package/src/themes/policy.ts +37 -16
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@promptctl/cc-candybar",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.27.0",
|
|
4
4
|
"description": "Statusline renderer for Claude Code — a JSON5-configurable DSL with daemon-cached data sources, byte-clean palette-aware composition, and OSC8 click verbs.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.mjs",
|
|
@@ -86,14 +86,14 @@
|
|
|
86
86
|
"tsx": "^4.21.0",
|
|
87
87
|
"typescript": "^5.0.0",
|
|
88
88
|
"@promptctl/go-template-js": "^0.7.0",
|
|
89
|
-
"@promptctl/rich-js": "^0.
|
|
89
|
+
"@promptctl/rich-js": "^0.7.0",
|
|
90
90
|
"json5": "^2.2.3",
|
|
91
91
|
"mobx": "^6.15.0"
|
|
92
92
|
},
|
|
93
93
|
"optionalDependencies": {
|
|
94
|
-
"@promptctl/cc-candybar-darwin-arm64": "1.
|
|
95
|
-
"@promptctl/cc-candybar-darwin-x64": "1.
|
|
96
|
-
"@promptctl/cc-candybar-linux-x64": "1.
|
|
97
|
-
"@promptctl/cc-candybar-linux-arm64": "1.
|
|
94
|
+
"@promptctl/cc-candybar-darwin-arm64": "1.27.0",
|
|
95
|
+
"@promptctl/cc-candybar-darwin-x64": "1.27.0",
|
|
96
|
+
"@promptctl/cc-candybar-linux-x64": "1.27.0",
|
|
97
|
+
"@promptctl/cc-candybar-linux-arm64": "1.27.0"
|
|
98
98
|
}
|
|
99
99
|
}
|
|
@@ -29,6 +29,9 @@
|
|
|
29
29
|
"look": {
|
|
30
30
|
"type": "string"
|
|
31
31
|
},
|
|
32
|
+
"preset": {
|
|
33
|
+
"type": "string"
|
|
34
|
+
},
|
|
32
35
|
"style": {
|
|
33
36
|
"enum": [
|
|
34
37
|
"powerline",
|
|
@@ -1141,10 +1144,20 @@
|
|
|
1141
1144
|
"type": "string"
|
|
1142
1145
|
},
|
|
1143
1146
|
"from": {
|
|
1144
|
-
"
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1147
|
+
"anyOf": [
|
|
1148
|
+
{
|
|
1149
|
+
"type": "string",
|
|
1150
|
+
"minLength": 1
|
|
1151
|
+
},
|
|
1152
|
+
{
|
|
1153
|
+
"type": "array",
|
|
1154
|
+
"items": {
|
|
1155
|
+
"type": "string",
|
|
1156
|
+
"minLength": 1
|
|
1157
|
+
},
|
|
1158
|
+
"minItems": 1,
|
|
1159
|
+
"uniqueItems": true
|
|
1160
|
+
}
|
|
1148
1161
|
]
|
|
1149
1162
|
}
|
|
1150
1163
|
},
|
|
@@ -1216,6 +1229,98 @@
|
|
|
1216
1229
|
],
|
|
1217
1230
|
"additionalProperties": false
|
|
1218
1231
|
},
|
|
1232
|
+
{
|
|
1233
|
+
"type": "object",
|
|
1234
|
+
"properties": {
|
|
1235
|
+
"persist": {
|
|
1236
|
+
"type": "string"
|
|
1237
|
+
},
|
|
1238
|
+
"to": {
|
|
1239
|
+
"type": "string"
|
|
1240
|
+
}
|
|
1241
|
+
},
|
|
1242
|
+
"required": [
|
|
1243
|
+
"persist",
|
|
1244
|
+
"to"
|
|
1245
|
+
],
|
|
1246
|
+
"additionalProperties": false
|
|
1247
|
+
},
|
|
1248
|
+
{
|
|
1249
|
+
"type": "object",
|
|
1250
|
+
"properties": {
|
|
1251
|
+
"persist": {
|
|
1252
|
+
"type": "string"
|
|
1253
|
+
},
|
|
1254
|
+
"from": {
|
|
1255
|
+
"anyOf": [
|
|
1256
|
+
{
|
|
1257
|
+
"type": "string",
|
|
1258
|
+
"minLength": 1
|
|
1259
|
+
},
|
|
1260
|
+
{
|
|
1261
|
+
"type": "array",
|
|
1262
|
+
"items": {
|
|
1263
|
+
"type": "string",
|
|
1264
|
+
"minLength": 1
|
|
1265
|
+
},
|
|
1266
|
+
"minItems": 1,
|
|
1267
|
+
"uniqueItems": true
|
|
1268
|
+
}
|
|
1269
|
+
]
|
|
1270
|
+
}
|
|
1271
|
+
},
|
|
1272
|
+
"required": [
|
|
1273
|
+
"persist",
|
|
1274
|
+
"from"
|
|
1275
|
+
],
|
|
1276
|
+
"additionalProperties": false
|
|
1277
|
+
},
|
|
1278
|
+
{
|
|
1279
|
+
"type": "object",
|
|
1280
|
+
"properties": {
|
|
1281
|
+
"persist": {
|
|
1282
|
+
"type": "string"
|
|
1283
|
+
},
|
|
1284
|
+
"min": {
|
|
1285
|
+
"type": "integer"
|
|
1286
|
+
},
|
|
1287
|
+
"max": {
|
|
1288
|
+
"type": "integer"
|
|
1289
|
+
},
|
|
1290
|
+
"by": {
|
|
1291
|
+
"type": "integer"
|
|
1292
|
+
}
|
|
1293
|
+
},
|
|
1294
|
+
"required": [
|
|
1295
|
+
"persist",
|
|
1296
|
+
"min",
|
|
1297
|
+
"max",
|
|
1298
|
+
"by"
|
|
1299
|
+
],
|
|
1300
|
+
"additionalProperties": false
|
|
1301
|
+
},
|
|
1302
|
+
{
|
|
1303
|
+
"type": "object",
|
|
1304
|
+
"properties": {
|
|
1305
|
+
"persist": {
|
|
1306
|
+
"type": "string"
|
|
1307
|
+
},
|
|
1308
|
+
"cycle": {
|
|
1309
|
+
"type": "array",
|
|
1310
|
+
"items": {
|
|
1311
|
+
"type": "string",
|
|
1312
|
+
"minLength": 1
|
|
1313
|
+
},
|
|
1314
|
+
"minItems": 2,
|
|
1315
|
+
"uniqueItems": true
|
|
1316
|
+
}
|
|
1317
|
+
},
|
|
1318
|
+
"required": [
|
|
1319
|
+
"persist",
|
|
1320
|
+
"cycle"
|
|
1321
|
+
],
|
|
1322
|
+
"additionalProperties": false
|
|
1323
|
+
},
|
|
1219
1324
|
{
|
|
1220
1325
|
"type": "object",
|
|
1221
1326
|
"properties": {
|
|
@@ -1239,6 +1344,18 @@
|
|
|
1239
1344
|
"open"
|
|
1240
1345
|
],
|
|
1241
1346
|
"additionalProperties": false
|
|
1347
|
+
},
|
|
1348
|
+
{
|
|
1349
|
+
"type": "object",
|
|
1350
|
+
"properties": {
|
|
1351
|
+
"reset": {
|
|
1352
|
+
"type": "string"
|
|
1353
|
+
}
|
|
1354
|
+
},
|
|
1355
|
+
"required": [
|
|
1356
|
+
"reset"
|
|
1357
|
+
],
|
|
1358
|
+
"additionalProperties": false
|
|
1242
1359
|
}
|
|
1243
1360
|
]
|
|
1244
1361
|
}
|
|
@@ -1265,6 +1382,78 @@
|
|
|
1265
1382
|
"additionalProperties": false
|
|
1266
1383
|
}
|
|
1267
1384
|
},
|
|
1385
|
+
"presets": {
|
|
1386
|
+
"type": "object",
|
|
1387
|
+
"additionalProperties": {
|
|
1388
|
+
"type": "object",
|
|
1389
|
+
"properties": {
|
|
1390
|
+
"root": {
|
|
1391
|
+
"$ref": "#/definitions/LayoutNode"
|
|
1392
|
+
},
|
|
1393
|
+
"globals": {
|
|
1394
|
+
"type": "object",
|
|
1395
|
+
"properties": {
|
|
1396
|
+
"default_bg": {
|
|
1397
|
+
"type": "string"
|
|
1398
|
+
},
|
|
1399
|
+
"default_fg": {
|
|
1400
|
+
"type": "string"
|
|
1401
|
+
},
|
|
1402
|
+
"default_empty_value": {
|
|
1403
|
+
"type": "string"
|
|
1404
|
+
},
|
|
1405
|
+
"default_separator": {
|
|
1406
|
+
"type": "string"
|
|
1407
|
+
},
|
|
1408
|
+
"default_truncate_marker": {
|
|
1409
|
+
"type": "string"
|
|
1410
|
+
},
|
|
1411
|
+
"palette": {
|
|
1412
|
+
"type": "string"
|
|
1413
|
+
},
|
|
1414
|
+
"look": {
|
|
1415
|
+
"type": "string"
|
|
1416
|
+
},
|
|
1417
|
+
"preset": {
|
|
1418
|
+
"not": {},
|
|
1419
|
+
"description": "not allowed inside a preset — a preset cannot select a preset"
|
|
1420
|
+
},
|
|
1421
|
+
"style": {
|
|
1422
|
+
"enum": [
|
|
1423
|
+
"powerline",
|
|
1424
|
+
"capsule",
|
|
1425
|
+
"plain"
|
|
1426
|
+
]
|
|
1427
|
+
},
|
|
1428
|
+
"autoWrap": {
|
|
1429
|
+
"type": "boolean"
|
|
1430
|
+
},
|
|
1431
|
+
"padding": {
|
|
1432
|
+
"type": "integer",
|
|
1433
|
+
"minimum": 0,
|
|
1434
|
+
"maximum": 16
|
|
1435
|
+
},
|
|
1436
|
+
"charset": {
|
|
1437
|
+
"enum": [
|
|
1438
|
+
"unicode",
|
|
1439
|
+
"ascii"
|
|
1440
|
+
]
|
|
1441
|
+
},
|
|
1442
|
+
"colorCompatibility": {
|
|
1443
|
+
"enum": [
|
|
1444
|
+
"truecolor",
|
|
1445
|
+
"256",
|
|
1446
|
+
"ansi",
|
|
1447
|
+
"none"
|
|
1448
|
+
]
|
|
1449
|
+
}
|
|
1450
|
+
},
|
|
1451
|
+
"additionalProperties": false
|
|
1452
|
+
}
|
|
1453
|
+
},
|
|
1454
|
+
"additionalProperties": false
|
|
1455
|
+
}
|
|
1456
|
+
},
|
|
1268
1457
|
"helpers": {
|
|
1269
1458
|
"type": "object",
|
|
1270
1459
|
"additionalProperties": {
|
package/src/check.ts
CHANGED
|
@@ -37,13 +37,15 @@ import {
|
|
|
37
37
|
lookKeyByName,
|
|
38
38
|
effectiveStripStyle,
|
|
39
39
|
} from "./themes/policy.js";
|
|
40
|
-
import {
|
|
40
|
+
import { paletteForThemeName } from "./themes/palette-resolvers.js";
|
|
41
|
+
import { effectivePresetName, presetGlobals } from "./config/presets.js";
|
|
41
42
|
import {
|
|
42
43
|
DEFAULT_CHARSET,
|
|
43
44
|
DEFAULT_COLOR_COMPATIBILITY,
|
|
44
45
|
DEFAULT_PADDING,
|
|
45
46
|
DEFAULT_WRAP,
|
|
46
47
|
} from "./render/strip.js";
|
|
48
|
+
import type { EffectiveGlobals } from "./daemon/render-payload.js";
|
|
47
49
|
|
|
48
50
|
// [LAW:no-ambient-temporal-coupling] A fixed width keeps the verdict a function
|
|
49
51
|
// of the config alone, not of whichever terminal invoked the check. Templates
|
|
@@ -60,16 +62,16 @@ const CHECK_WIDTH = 200;
|
|
|
60
62
|
// field-name typo in the git/directory/metrics/budget branches slip through —
|
|
61
63
|
// those branches only run when their data is present.
|
|
62
64
|
//
|
|
63
|
-
// `
|
|
64
|
-
// resolves
|
|
65
|
-
//
|
|
65
|
+
// `effective` is threaded in exactly as the daemon threads it (server.ts
|
|
66
|
+
// resolves one EffectiveGlobals struct per render and feeds BOTH the
|
|
67
|
+
// payload's `*.effective` fields and BuildLineOptions/basePalette below)
|
|
68
|
+
// [LAW:one-source-of-truth].
|
|
66
69
|
//
|
|
67
70
|
// test/example-configs.test.ts asserts rendered content against these literal
|
|
68
71
|
// values (780s → "◷ 13m", cost $0.39, version 1.15.0, …); changing one here
|
|
69
72
|
// fails that suite loudly rather than drifting silently.
|
|
70
73
|
export function checkPayload(
|
|
71
|
-
|
|
72
|
-
effectiveLook: string,
|
|
74
|
+
effective: EffectiveGlobals,
|
|
73
75
|
): Record<string, unknown> {
|
|
74
76
|
const home = "/home/tester";
|
|
75
77
|
const nowSec = Math.floor(Date.now() / 1000);
|
|
@@ -87,6 +89,7 @@ export function checkPayload(
|
|
|
87
89
|
},
|
|
88
90
|
git: {
|
|
89
91
|
repoName: "cc-candybar",
|
|
92
|
+
repoUrl: "https://github.com/promptctl/cc-candybar",
|
|
90
93
|
branch: "main",
|
|
91
94
|
sha: "abc1234",
|
|
92
95
|
ahead: 2,
|
|
@@ -116,8 +119,13 @@ export function checkPayload(
|
|
|
116
119
|
weekly: { percentage: 21, resetsAt: nowSec + 5 * 86400 },
|
|
117
120
|
cache: { expiresAt: nowSec + 15 * 60 },
|
|
118
121
|
tmux: { session: "work" },
|
|
119
|
-
theme: { effective:
|
|
120
|
-
look: { effective:
|
|
122
|
+
theme: { effective: effective.theme },
|
|
123
|
+
look: { effective: effective.look },
|
|
124
|
+
style: { effective: effective.style },
|
|
125
|
+
charset: { effective: effective.charset },
|
|
126
|
+
colorCompatibility: { effective: effective.colorCompatibility },
|
|
127
|
+
autoWrap: { effective: effective.autoWrap },
|
|
128
|
+
padding: { effective: effective.padding },
|
|
121
129
|
};
|
|
122
130
|
}
|
|
123
131
|
|
|
@@ -271,18 +279,30 @@ function loadRegisterRender(
|
|
|
271
279
|
// validator registry, which a one-shot check has no wire to serve.
|
|
272
280
|
deriveActionValidators(config);
|
|
273
281
|
|
|
274
|
-
// Fresh session (no clicked theme/style), so the session half of
|
|
275
|
-
// resolution is null — the config default over the floor, exactly
|
|
276
|
-
// daemon renders for a session that has never clicked.
|
|
277
|
-
|
|
278
|
-
//
|
|
279
|
-
//
|
|
280
|
-
//
|
|
281
|
-
const
|
|
282
|
+
// Fresh session (no clicked theme/style/look), so the session half of
|
|
283
|
+
// each resolution is null — the config default over the floor, exactly
|
|
284
|
+
// what the daemon renders for a session that has never clicked.
|
|
285
|
+
// [LAW:one-source-of-truth] The preset resolves first and its fragment's
|
|
286
|
+
// globals feed every field below, the SAME order the daemon resolves in
|
|
287
|
+
// (server.ts) — so `check` renders the arrangement a fresh session actually
|
|
288
|
+
// opens in, not the config's un-presetted root.
|
|
289
|
+
const preset = effectivePresetName(
|
|
282
290
|
null,
|
|
283
|
-
config.globals.
|
|
284
|
-
config.
|
|
291
|
+
config.globals.preset,
|
|
292
|
+
config.presets,
|
|
285
293
|
);
|
|
294
|
+
const globals = presetGlobals(config, preset);
|
|
295
|
+
const effective: EffectiveGlobals = {
|
|
296
|
+
preset,
|
|
297
|
+
theme: effectiveThemeName(null, globals.palette),
|
|
298
|
+
look: effectiveLookName(null, globals.look, config.looks),
|
|
299
|
+
style: effectiveStripStyle(null, globals.style),
|
|
300
|
+
autoWrap: globals.autoWrap ?? DEFAULT_WRAP,
|
|
301
|
+
padding: globals.padding ?? DEFAULT_PADDING,
|
|
302
|
+
charset: globals.charset ?? DEFAULT_CHARSET,
|
|
303
|
+
colorCompatibility:
|
|
304
|
+
globals.colorCompatibility ?? DEFAULT_COLOR_COMPATIBILITY,
|
|
305
|
+
};
|
|
286
306
|
// [LAW:no-silent-failure] A segment whose template THROWS while evaluating
|
|
287
307
|
// (an `{{ action }}` display-arity mismatch, a MissingFieldError from a
|
|
288
308
|
// partially-declared variable) renders as a visible ⚠ error cell — partial
|
|
@@ -296,22 +316,24 @@ function loadRegisterRender(
|
|
|
296
316
|
compiled,
|
|
297
317
|
store,
|
|
298
318
|
registry,
|
|
299
|
-
checkPayload(
|
|
300
|
-
|
|
319
|
+
checkPayload(effective),
|
|
320
|
+
paletteForThemeName(effective.theme),
|
|
301
321
|
{
|
|
302
|
-
style:
|
|
322
|
+
style: effective.style,
|
|
303
323
|
width: CHECK_WIDTH,
|
|
304
|
-
colorCompatibility:
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
charset: config.globals.charset ?? DEFAULT_CHARSET,
|
|
324
|
+
colorCompatibility: effective.colorCompatibility,
|
|
325
|
+
wrap: effective.autoWrap,
|
|
326
|
+
padding: effective.padding,
|
|
327
|
+
charset: effective.charset,
|
|
309
328
|
},
|
|
310
329
|
{
|
|
311
330
|
onSegmentError: (segName, message) =>
|
|
312
331
|
segmentErrors.push(`segment "${segName}": ${message}`),
|
|
313
332
|
},
|
|
314
|
-
|
|
333
|
+
{
|
|
334
|
+
look: lookKeyByName(config.looks, effective.look),
|
|
335
|
+
preset: effective.preset,
|
|
336
|
+
},
|
|
315
337
|
);
|
|
316
338
|
if (segmentErrors.length > 0) {
|
|
317
339
|
throw new Error(
|
package/src/click/wire.ts
CHANGED
|
@@ -50,6 +50,22 @@ export const VERB_SHOW_CONFIG_WARNING = "show-config-warning";
|
|
|
50
50
|
// reads it at the cache-lookup boundary. Clicking a different config is a
|
|
51
51
|
// side-effect isolated to the verb handler; the renderer only sees the result.
|
|
52
52
|
export const VERB_LOAD_CONFIG = "load-config";
|
|
53
|
+
// [LAW:one-source-of-truth] `persist`'s twin of set-state/step-state: writes
|
|
54
|
+
// land in the daemon-owned config-overrides layer (never the hand-authored
|
|
55
|
+
// config file), which RenderCache merges on top of the user file every
|
|
56
|
+
// reload — the SAME file-watcher path a hand edit already takes
|
|
57
|
+
// (candybar-config-engine-71o.2). Args: `[sessionId, key, value]` — the
|
|
58
|
+
// sessionId is carried only for click.error surfacing, exactly like
|
|
59
|
+
// set-state; the write itself is daemon-global, not session-scoped.
|
|
60
|
+
export const VERB_SET_CONFIG = "set-config";
|
|
61
|
+
// [LAW:types-are-the-program] A RELATIVE nudge to a bounded config-overrides
|
|
62
|
+
// key (e.g. a padding stepper) — the config twin of step-state. Args:
|
|
63
|
+
// `[sessionId, key, by]`.
|
|
64
|
+
export const VERB_STEP_CONFIG = "step-config";
|
|
65
|
+
// [LAW:one-source-of-truth] The gated undo for `persist`: clears one
|
|
66
|
+
// config-overrides key, restoring the user-file/bundled-default value on the
|
|
67
|
+
// next reload. Args: `[sessionId, key]`.
|
|
68
|
+
export const VERB_RESET_CONFIG = "reset-config";
|
|
53
69
|
|
|
54
70
|
// [LAW:types-are-the-program] An effect to EMIT: a verb plus its raw (unencoded)
|
|
55
71
|
// positional args. The wire owns all encoding — callers never percent-encode.
|
package/src/config/action.ts
CHANGED
|
@@ -24,24 +24,26 @@
|
|
|
24
24
|
// These are the shapes a picker draws options from and the set/copy/open/int
|
|
25
25
|
// discriminator the loader and the validator-derivation match on.
|
|
26
26
|
|
|
27
|
-
// [LAW:one-source-of-truth] The domain
|
|
28
|
-
//
|
|
29
|
-
//
|
|
30
|
-
//
|
|
31
|
-
//
|
|
32
|
-
//
|
|
33
|
-
|
|
34
|
-
export type
|
|
35
|
-
export const OPTION_SOURCES: readonly OptionSource[] = [
|
|
36
|
-
"themes",
|
|
37
|
-
"styles",
|
|
38
|
-
"looks",
|
|
39
|
-
];
|
|
27
|
+
// [LAW:one-source-of-truth] The domain a picker draws options from. Resolved
|
|
28
|
+
// through option-domain.ts's registry — themes/styles are registry-backed
|
|
29
|
+
// static lists, "looks" is the one PER-CONFIG domain (the merged `looks`
|
|
30
|
+
// block's names, threaded as data rather than consulted from a module
|
|
31
|
+
// constant), and an inline array is its own domain, needing no registration
|
|
32
|
+
// at all. Re-exported here so ActionDecl stays self-contained to read.
|
|
33
|
+
import type { OptionDomain } from "./option-domain.js";
|
|
34
|
+
export type { OptionDomain } from "./option-domain.js";
|
|
40
35
|
|
|
41
36
|
// [LAW:types-are-the-program] The top-level discriminator of an ActionDecl — the
|
|
42
37
|
// click effect is keyed by which of these is present. The loader proves
|
|
43
38
|
// exactly-one-of; the renderer and validator-derivation match with no fallthrough.
|
|
44
|
-
|
|
39
|
+
//
|
|
40
|
+
// [LAW:one-source-of-truth] `persist` is `set`'s PERSISTENT twin
|
|
41
|
+
// (candybar-config-engine-71o.2): `set` mutates per-session SessionState,
|
|
42
|
+
// `persist` mutates the config's `globals` DEFAULT through the daemon-owned
|
|
43
|
+
// overrides layer (never the hand-authored config file). `reset` clears one
|
|
44
|
+
// persisted override — the gated undo `persist` needs, since a machine-owned
|
|
45
|
+
// write with no way back would be a one-way ratchet.
|
|
46
|
+
export const ACTION_KEYS = ["set", "persist", "copy", "open", "reset"] as const;
|
|
45
47
|
export type ActionKey = (typeof ACTION_KEYS)[number];
|
|
46
48
|
|
|
47
49
|
// [LAW:types-are-the-program] An ActionDecl is the click effect a named action
|
|
@@ -53,7 +55,9 @@ export type ActionKey = (typeof ACTION_KEYS)[number];
|
|
|
53
55
|
//
|
|
54
56
|
// set + to — write a literal value -> allow-list {to}
|
|
55
57
|
// set + from — write the option the template binds at render
|
|
56
|
-
// (a picker ranges the domain
|
|
58
|
+
// (a picker ranges the domain — a registered name like
|
|
59
|
+
// "themes", or an inline literal array) -> allow-list
|
|
60
|
+
// {options}
|
|
57
61
|
// set + min/max/by — write wrap(current ± by) clamped to [min,max]
|
|
58
62
|
// (a stepper affordance) -> range [min,max]
|
|
59
63
|
// set + int — write any integer the render binds (a paged cursor:
|
|
@@ -72,12 +76,20 @@ export type ActionKey = (typeof ACTION_KEYS)[number];
|
|
|
72
76
|
// copy — copy templated text to the clipboard -> no gate
|
|
73
77
|
// open — open a templated target in the editor -> no gate
|
|
74
78
|
//
|
|
75
|
-
// [LAW:one-source-of-truth]
|
|
76
|
-
//
|
|
77
|
-
//
|
|
79
|
+
// [LAW:one-source-of-truth] `set` writes SessionState and `persist` writes
|
|
80
|
+
// the config-overrides layer, so only those two derive a validator (through
|
|
81
|
+
// the SAME shared registry algebra — see validator-registry.ts). copy/open/
|
|
82
|
+
// reset write nothing SPEC-shaped (reset's target is gated by key membership,
|
|
83
|
+
// not a value domain) — they derive nothing. The vocabulary grows by arms (a
|
|
84
|
+
// future `run`/`open-url`), not by validator plumbing.
|
|
85
|
+
//
|
|
86
|
+
// [LAW:one-type-per-behavior] `persist` mirrors `set`'s four value-source
|
|
87
|
+
// arms verbatim (to/from/min-max-by/cycle) MINUS `int`: an unbounded page
|
|
88
|
+
// cursor is a UI-only paging concept (a picker's own navigation state) with
|
|
89
|
+
// no meaning as a persisted config default.
|
|
78
90
|
export type ActionDecl =
|
|
79
91
|
| { readonly set: string; readonly to: string }
|
|
80
|
-
| { readonly set: string; readonly from:
|
|
92
|
+
| { readonly set: string; readonly from: OptionDomain }
|
|
81
93
|
| {
|
|
82
94
|
readonly set: string;
|
|
83
95
|
readonly min: number;
|
|
@@ -86,13 +98,36 @@ export type ActionDecl =
|
|
|
86
98
|
}
|
|
87
99
|
| { readonly set: string; readonly int: true }
|
|
88
100
|
| { readonly set: string; readonly cycle: readonly string[] }
|
|
101
|
+
| { readonly persist: string; readonly to: string }
|
|
102
|
+
| { readonly persist: string; readonly from: OptionDomain }
|
|
103
|
+
| {
|
|
104
|
+
readonly persist: string;
|
|
105
|
+
readonly min: number;
|
|
106
|
+
readonly max: number;
|
|
107
|
+
readonly by: number;
|
|
108
|
+
}
|
|
109
|
+
| { readonly persist: string; readonly cycle: readonly string[] }
|
|
89
110
|
| { readonly copy: string }
|
|
90
|
-
| { readonly open: string }
|
|
111
|
+
| { readonly open: string }
|
|
112
|
+
| { readonly reset: string };
|
|
91
113
|
|
|
92
114
|
// [LAW:dataflow-not-control-flow] Does this action write a SessionState key? A
|
|
93
115
|
// `set` action composes a set-state click URL whose first segment is session.id;
|
|
94
|
-
// copy/open embed none. One predicate the loader's session.id
|
|
95
|
-
// over — no per-arm branching at the callsite.
|
|
116
|
+
// copy/open/persist/reset embed none. One predicate the loader's session.id
|
|
117
|
+
// requirement folds over — no per-arm branching at the callsite.
|
|
96
118
|
export function actionBindsSet(a: ActionDecl): boolean {
|
|
97
119
|
return "set" in a;
|
|
98
120
|
}
|
|
121
|
+
|
|
122
|
+
// [LAW:dataflow-not-control-flow] Does this action write the config-overrides
|
|
123
|
+
// layer? Mirrors actionBindsSet for the `persist` arm.
|
|
124
|
+
export function actionBindsPersist(a: ActionDecl): boolean {
|
|
125
|
+
return "persist" in a;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// [LAW:dataflow-not-control-flow] Does this action clear a config-overrides
|
|
129
|
+
// key? `reset` carries session.id on the wire too (for click-error surfacing,
|
|
130
|
+
// same as set/persist), so it joins the same requirement.
|
|
131
|
+
export function actionBindsReset(a: ActionDecl): boolean {
|
|
132
|
+
return "reset" in a;
|
|
133
|
+
}
|