@promptctl/cc-candybar 1.8.2 → 1.8.3
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 +65 -65
- package/package.json +5 -5
- package/src/config/default-dsl-config.ts +34 -38
- package/src/render/menu.ts +64 -21
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@promptctl/cc-candybar",
|
|
3
|
-
"version": "1.8.
|
|
3
|
+
"version": "1.8.3",
|
|
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",
|
|
@@ -91,10 +91,10 @@
|
|
|
91
91
|
"mobx": "^6.15.0"
|
|
92
92
|
},
|
|
93
93
|
"optionalDependencies": {
|
|
94
|
-
"@promptctl/cc-candybar-darwin-arm64": "1.8.
|
|
95
|
-
"@promptctl/cc-candybar-darwin-x64": "1.8.
|
|
96
|
-
"@promptctl/cc-candybar-linux-x64": "1.8.
|
|
97
|
-
"@promptctl/cc-candybar-linux-arm64": "1.8.
|
|
94
|
+
"@promptctl/cc-candybar-darwin-arm64": "1.8.3",
|
|
95
|
+
"@promptctl/cc-candybar-darwin-x64": "1.8.3",
|
|
96
|
+
"@promptctl/cc-candybar-linux-x64": "1.8.3",
|
|
97
|
+
"@promptctl/cc-candybar-linux-arm64": "1.8.3"
|
|
98
98
|
},
|
|
99
99
|
"pnpm": {
|
|
100
100
|
"supportedArchitectures": {
|
|
@@ -496,10 +496,12 @@ export const DEFAULT_DSL_CONFIG = {
|
|
|
496
496
|
// and the render's read are one value. Empty default ⇒ the daemon's
|
|
497
497
|
// "powerline" floor is in effect and styleControl shows "(default)".
|
|
498
498
|
activeStyle: { kind: "state", key: "style", default: "" },
|
|
499
|
-
// The style
|
|
500
|
-
//
|
|
501
|
-
//
|
|
502
|
-
|
|
499
|
+
// The style picker's page index. Open/closed is the synthesized menus.* key
|
|
500
|
+
// the styleControl's {{ menu }} owns — NOT this var, which is purely the page
|
|
501
|
+
// the picker paginates by (default page 0; the disclosure resets it to 0 on
|
|
502
|
+
// every toggle, so a reopened menu never lands on a stale page). The stylePage
|
|
503
|
+
// action below declares the int gate this var reads back.
|
|
504
|
+
stylePage: { kind: "state", key: "style-page", default: "0" },
|
|
503
505
|
},
|
|
504
506
|
|
|
505
507
|
// ─── Segments ──────────────────────────────────────────────────────────────
|
|
@@ -736,26 +738,22 @@ export const DEFAULT_DSL_CONFIG = {
|
|
|
736
738
|
" .metrics.sessionDuration .metrics.messageCount" +
|
|
737
739
|
" .metrics.linesAdded .metrics.linesRemoved }}",
|
|
738
740
|
},
|
|
739
|
-
// Style control — the powerline-shape switcher
|
|
740
|
-
//
|
|
741
|
-
//
|
|
742
|
-
//
|
|
743
|
-
// row. [LAW:
|
|
744
|
-
//
|
|
741
|
+
// Style control — the powerline-shape switcher. A self-contained {{ menu }}
|
|
742
|
+
// disclosure: the ✦ glyph + current-style label is the REPRESENTATION, and the
|
|
743
|
+
// ▸/▾ disclosure (whose identity is DERIVED from this segment + the applyStyle
|
|
744
|
+
// action, see menu-keys.ts) toggles the picker body, which DROPS full-width
|
|
745
|
+
// onto the line below this row when open. [LAW:one-type-per-behavior] "a menu
|
|
746
|
+
// that opens and closes" is one behavior the substrate already expresses — no
|
|
747
|
+
// bespoke open-action + page-cursor-as-open-state + when-gated reveal row.
|
|
748
|
+
// paged omitted ⇒ false ⇒ one full-width page (the 3 powerline shapes are a
|
|
749
|
+
// small domain; FlexStrip soft-wraps if needed). closeOnPick omitted ⇒ false
|
|
750
|
+
// ⇒ stay-open, so shapes can be tried in a row; the ▾ collapses.
|
|
751
|
+
// [LAW:dataflow-not-control-flow] No display state from the provider — the
|
|
752
|
+
// label is the one "style" value the click writes and the render reads.
|
|
745
753
|
styleControl: {
|
|
746
754
|
template:
|
|
747
755
|
"✦ {{ if .activeStyle }}{{ .activeStyle }}{{ else }}(default){{ end }} " +
|
|
748
|
-
'{{
|
|
749
|
-
bg: "surface",
|
|
750
|
-
fg: "foreground",
|
|
751
|
-
},
|
|
752
|
-
// The expanded style picker: one full-width page (paged=false) over the
|
|
753
|
-
// `applyStyle` option domain — the 3 powerline shapes. closeOnPick defaults
|
|
754
|
-
// false (omitted here), so a pick reshapes the bar live and LEAVES THE ROW
|
|
755
|
-
// OPEN — shapes can be tried in a row; the ✕ affordance closes. The active
|
|
756
|
-
// shape is marked by the picker helper.
|
|
757
|
-
stylePicker: {
|
|
758
|
-
template: '{{ picker "applyStyle" "stylePage" }}',
|
|
756
|
+
'{{ menu "applyStyle" "stylePage" }}',
|
|
759
757
|
bg: "surface",
|
|
760
758
|
fg: "foreground",
|
|
761
759
|
},
|
|
@@ -764,10 +762,11 @@ export const DEFAULT_DSL_CONFIG = {
|
|
|
764
762
|
// Default layout — the canonical LayoutNode tree (`satisfies DslConfig`
|
|
765
763
|
// requires the lowered form here; the terse Option-A `{ h/v/seg }` grammar is
|
|
766
764
|
// the loader's authoring surface for user JSON, not this typed literal).
|
|
767
|
-
// [LAW:dataflow-not-control-flow]
|
|
768
|
-
//
|
|
769
|
-
// row
|
|
770
|
-
// The picker
|
|
765
|
+
// [LAW:dataflow-not-control-flow] One always-on control row. The styleControl's
|
|
766
|
+
// {{ menu }} disclosure drops its picker body onto the line directly below this
|
|
767
|
+
// row when open — openness is the value of the derived menus.* key, NOT a when-
|
|
768
|
+
// gated reveal row. The picker draws the ✕/←/→ affordances from the page + term
|
|
769
|
+
// width; the vertical container stacks the dropped body under row 0.
|
|
771
770
|
root: {
|
|
772
771
|
kind: "container",
|
|
773
772
|
direction: "vertical",
|
|
@@ -786,11 +785,6 @@ export const DEFAULT_DSL_CONFIG = {
|
|
|
786
785
|
{ kind: "segment", name: "styleControl" },
|
|
787
786
|
],
|
|
788
787
|
},
|
|
789
|
-
{
|
|
790
|
-
kind: "segment",
|
|
791
|
-
name: "stylePicker",
|
|
792
|
-
when: "{{ ge (int .stylePage) 0 }}",
|
|
793
|
-
},
|
|
794
788
|
],
|
|
795
789
|
},
|
|
796
790
|
|
|
@@ -814,14 +808,16 @@ export const DEFAULT_DSL_CONFIG = {
|
|
|
814
808
|
openProject: { open: "{{ .project_dir }}" },
|
|
815
809
|
openTranscript: { open: "{{ .transcript_path }}" },
|
|
816
810
|
|
|
817
|
-
// [LAW:locality-or-seam] The style
|
|
818
|
-
//
|
|
819
|
-
//
|
|
820
|
-
//
|
|
821
|
-
//
|
|
822
|
-
//
|
|
823
|
-
//
|
|
824
|
-
|
|
811
|
+
// [LAW:locality-or-seam] The style menu's behaviors, decoupled by NAME from the
|
|
812
|
+
// styleControl {{ menu }} above. The disclosure's open-state toggle + its
|
|
813
|
+
// backing state var are SYNTHESIZED by the menu pass (under the reserved
|
|
814
|
+
// menus.* namespace) — no hand-authored open/close action. These two are the
|
|
815
|
+
// picker body's effects, both gated by derivation (deriveActionValidators):
|
|
816
|
+
// applyStyle writes the chosen shape, gated to the STRIP_STYLES allow-list
|
|
817
|
+
// because its value source is `from: "styles"`; stylePage is the page cursor's
|
|
818
|
+
// unbounded int gate (←/→ navigation + the disclosure's page-0 reset). The
|
|
819
|
+
// rendered click and the wire gate share that one source — a template cannot
|
|
820
|
+
// smuggle an un-gated style write.
|
|
825
821
|
applyStyle: { set: "style", from: "styles" },
|
|
826
822
|
stylePage: { set: "style-page", int: true },
|
|
827
823
|
},
|
package/src/render/menu.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
// [LAW:locality-or-seam] The runtime half of the `{{ menu }}` seam — sibling to
|
|
2
2
|
// `{{ action }}`/`{{ picker }}`. A menu is a self-contained disclosure: an inline
|
|
3
3
|
// glyph that toggles open/closed, and (when open) its body — a picker grid —
|
|
4
|
-
// that DROPS onto the line(s) below the enclosing row.
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
4
|
+
// that DROPS onto the line(s) below the enclosing row. The body is the one picker
|
|
5
|
+
// renderer (`renderPicker`); the glyph is a coupled set-state the menu composes
|
|
6
|
+
// directly (like the picker's closeOnPick) — it toggles the open-state AND resets
|
|
7
|
+
// the page cursor in one atomic batch, gated by the synthesized cycle action.
|
|
8
8
|
//
|
|
9
9
|
// [LAW:effects-at-boundaries] The helper is a PURE function of its inputs (the
|
|
10
10
|
// walk-published placement + the live store): it computes the inline glyph and,
|
|
@@ -36,13 +36,14 @@
|
|
|
36
36
|
import type { RichText } from "@promptctl/rich-js";
|
|
37
37
|
import type { FuncMap } from "@promptctl/go-template-js";
|
|
38
38
|
import {
|
|
39
|
+
MENU_CLOSED,
|
|
39
40
|
MENU_GLYPH_CLOSED,
|
|
40
41
|
MENU_GLYPH_OPEN,
|
|
41
|
-
menuActionName,
|
|
42
42
|
menuMember,
|
|
43
43
|
menuStateKey,
|
|
44
44
|
} from "../config/menu-keys.js";
|
|
45
|
-
import {
|
|
45
|
+
import { effectsUrl, VERB_SET_STATE } from "../click/wire.js";
|
|
46
|
+
import { linkFragment, readVar, type ActionRuntime } from "./action.js";
|
|
46
47
|
import { renderPicker } from "./picker.js";
|
|
47
48
|
|
|
48
49
|
// [LAW:types-are-the-program] One menu placement: the structural fact a context-
|
|
@@ -82,6 +83,22 @@ export function collectMenuDrops(
|
|
|
82
83
|
return fragments.flatMap((f) => (f as GlyphWithDrop)[MENU_DROP] ?? []);
|
|
83
84
|
}
|
|
84
85
|
|
|
86
|
+
// [LAW:single-enforcer] The page cursor's SessionState key, resolved from the page
|
|
87
|
+
// ACTION name the menu binds (its second arg). renderPicker proves pageName is a
|
|
88
|
+
// set-int when it builds the body, but the disclosure must reset the page even
|
|
89
|
+
// while CLOSED (no body is built then), so it resolves the key here too — the SAME
|
|
90
|
+
// set-int action, one source. A non-int page arg is an author error surfaced
|
|
91
|
+
// loudly (composeWithDiagnostics shows it), never a silent skipped reset.
|
|
92
|
+
function pageKeyOf(action: ActionRuntime, pageName: string): string {
|
|
93
|
+
const page = action.compiled.get(pageName);
|
|
94
|
+
if (!page || page.kind !== "set-int") {
|
|
95
|
+
throw new Error(
|
|
96
|
+
`{{ menu }} page action "${pageName}" must be an int action ({ set, int: true })`,
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
return page.key;
|
|
100
|
+
}
|
|
101
|
+
|
|
85
102
|
// Realize a `{{ menu }}` against the live placement + state: return its inline
|
|
86
103
|
// glyph, carrying the (open) body as out-of-band metadata for the boundary.
|
|
87
104
|
function renderMenu(
|
|
@@ -106,25 +123,51 @@ function renderMenu(
|
|
|
106
123
|
const stateKey = menuStateKey(placement.segName, applyName, sharedKey);
|
|
107
124
|
const member = menuMember(applyName);
|
|
108
125
|
|
|
109
|
-
// [LAW:single-enforcer] The disclosure glyph IS the synthesized cycle action —
|
|
110
|
-
// displays bound one-per-member (closed ▸ / open ▾), the current member's glyph
|
|
111
|
-
// renders, the click writes the successor. Same toggle as group sugar.
|
|
112
|
-
const glyph = renderAction(
|
|
113
|
-
menuActionName(stateKey, member),
|
|
114
|
-
[MENU_GLYPH_CLOSED, MENU_GLYPH_OPEN],
|
|
115
|
-
action,
|
|
116
|
-
);
|
|
117
|
-
|
|
118
126
|
// [LAW:dataflow-not-control-flow] Open ⇔ the state key holds this menu's member.
|
|
119
|
-
//
|
|
120
|
-
// closed —
|
|
121
|
-
//
|
|
122
|
-
//
|
|
127
|
+
// A foreign value (an accordion sibling's member under a shared key) reads as
|
|
128
|
+
// closed here — exactly the binary [closed, member] cycle the synthesized action
|
|
129
|
+
// gates. This ONE read drives both the glyph and the body, so what the glyph
|
|
130
|
+
// promises and what drops below cannot disagree.
|
|
123
131
|
const open = readVar(action.store, stateKey) === member;
|
|
124
|
-
|
|
132
|
+
|
|
133
|
+
// [LAW:one-source-of-truth] / [LAW:locality-or-seam] The disclosure click is ONE
|
|
134
|
+
// atomic set-state that keeps the two split keys coherent: it toggles the open-
|
|
135
|
+
// state (the binary cycle — successor is closed when open, the member when
|
|
136
|
+
// closed) AND resets the page cursor to page 0, in one batch. So a reopened menu
|
|
137
|
+
// is never stranded on a stale page left by ←/→ before the last close. This
|
|
138
|
+
// mirrors the picker's closeOnPick page-reset fold: the picker builds its set-
|
|
139
|
+
// state URLs directly (not via renderAction) so it can couple two writes; the
|
|
140
|
+
// menu — the one part that knows BOTH the open-state key and the page key
|
|
141
|
+
// [LAW:decomposition] — does the same. The synthesized cycle action stays the
|
|
142
|
+
// GATE source (deriveActionValidators); both keys are independently gated, so the
|
|
143
|
+
// coupled batch passes the same wire gate every click does [LAW:single-enforcer].
|
|
144
|
+
const sessionId = readVar(action.store, "session.id");
|
|
145
|
+
const successor = open ? MENU_CLOSED : member;
|
|
146
|
+
const glyph = linkFragment(
|
|
147
|
+
open ? MENU_GLYPH_OPEN : MENU_GLYPH_CLOSED,
|
|
148
|
+
effectsUrl([
|
|
149
|
+
{
|
|
150
|
+
verb: VERB_SET_STATE,
|
|
151
|
+
args: [
|
|
152
|
+
sessionId,
|
|
153
|
+
stateKey,
|
|
154
|
+
successor,
|
|
155
|
+
pageKeyOf(action, pageName),
|
|
156
|
+
"0",
|
|
157
|
+
],
|
|
158
|
+
},
|
|
159
|
+
]),
|
|
160
|
+
false,
|
|
161
|
+
) as GlyphWithDrop;
|
|
162
|
+
|
|
163
|
+
// [LAW:effects-at-boundaries] The body is a VALUE whose length carries open/
|
|
164
|
+
// closed — `[body]` open, `[]` closed — attached to the glyph the helper returns.
|
|
165
|
+
// No shared mutation: the boundary reads this metadata to place the body.
|
|
166
|
+
// (renderPicker is pure, so it is only built when open — skipping wasted
|
|
167
|
+
// computation, gating no effect.)
|
|
168
|
+
glyph[MENU_DROP] = open
|
|
125
169
|
? [renderPicker(applyName, pageName, closeOnPick, paged, action)]
|
|
126
170
|
: [];
|
|
127
|
-
(glyph as GlyphWithDrop)[MENU_DROP] = bodyLines;
|
|
128
171
|
return glyph;
|
|
129
172
|
}
|
|
130
173
|
|