@promptctl/cc-candybar 1.10.0 → 1.11.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 +36 -36
- package/package.json +5 -5
- package/schema/cc-candybar.schema.json +6 -0
- package/src/config/dsl-types.ts +10 -1
- package/src/config/loader/globals.ts +5 -1
- package/src/daemon/server.ts +24 -2
- package/src/demo/dsl.ts +2 -0
- package/src/render/strip.ts +56 -14
- package/src/themes/policy.ts +17 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@promptctl/cc-candybar",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.11.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",
|
|
@@ -91,10 +91,10 @@
|
|
|
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.11.0",
|
|
95
|
+
"@promptctl/cc-candybar-darwin-x64": "1.11.0",
|
|
96
|
+
"@promptctl/cc-candybar-linux-x64": "1.11.0",
|
|
97
|
+
"@promptctl/cc-candybar-linux-arm64": "1.11.0"
|
|
98
98
|
},
|
|
99
99
|
"pnpm": {
|
|
100
100
|
"supportedArchitectures": {
|
package/src/config/dsl-types.ts
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
// references it here. The dependency is one-way (this file → action.ts), never
|
|
15
15
|
// the reverse, so that shape can be lifted out without a cycle.
|
|
16
16
|
import type { ActionDecl } from "./action.js";
|
|
17
|
-
import type { StripStyle } from "../themes/policy.js";
|
|
17
|
+
import type { Charset, StripStyle } from "../themes/policy.js";
|
|
18
18
|
|
|
19
19
|
// [LAW:types-are-the-program] Three stages, three names.
|
|
20
20
|
//
|
|
@@ -204,6 +204,15 @@ export interface Globals {
|
|
|
204
204
|
// [config-only] The daemon resolves `globals.padding ?? 1` into
|
|
205
205
|
// renderOpts.padding; no SessionState/click half.
|
|
206
206
|
readonly padding?: number;
|
|
207
|
+
|
|
208
|
+
// The legacy display.charset knob: which glyph vocabulary the strip joiners
|
|
209
|
+
// render with. Default "unicode" (current behavior — rich-js's powerline
|
|
210
|
+
// caps, U+E0Bx). "ascii" swaps the caps for single-column ASCII glyphs so
|
|
211
|
+
// terminals/fonts without powerline glyphs render cleanly instead of tofu.
|
|
212
|
+
// Orthogonal to `style`: style picks the joiner shape, charset the glyphs.
|
|
213
|
+
// [config-only] The daemon resolves `globals.charset ?? "unicode"` into
|
|
214
|
+
// renderOpts.charset; no SessionState/click half.
|
|
215
|
+
readonly charset?: Charset;
|
|
207
216
|
}
|
|
208
217
|
|
|
209
218
|
// [LAW:one-type-per-behavior] One discriminated union covers every source
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// add a key to GLOBALS_SCHEMA and Globals; the engine does the rest.
|
|
5
5
|
|
|
6
6
|
import { type Globals } from "../dsl-types.js";
|
|
7
|
-
import { STRIP_STYLES } from "../../themes/policy.js";
|
|
7
|
+
import { CHARSETS, STRIP_STYLES } from "../../themes/policy.js";
|
|
8
8
|
import {
|
|
9
9
|
optionalBooleanSpec,
|
|
10
10
|
optionalEnumSpec,
|
|
@@ -37,6 +37,10 @@ const GLOBALS_SCHEMA: RecordSchema<Globals> = {
|
|
|
37
37
|
// [LAW:no-silent-failure] — an absurd value is a loud load error, not a
|
|
38
38
|
// silently-huge render.
|
|
39
39
|
padding: optionalIntSpec({ min: 0, max: 16 }),
|
|
40
|
+
// [LAW:types-are-the-program] Closed enum like `style`: the joiner glyph
|
|
41
|
+
// vocabularies pickJoiner can render — validates by membership, emits a
|
|
42
|
+
// JSON-Schema `enum` from the same CHARSETS literal.
|
|
43
|
+
charset: optionalEnumSpec(CHARSETS),
|
|
40
44
|
},
|
|
41
45
|
};
|
|
42
46
|
|
package/src/daemon/server.ts
CHANGED
|
@@ -46,10 +46,12 @@ import {
|
|
|
46
46
|
} from "../themes/index.js";
|
|
47
47
|
import {
|
|
48
48
|
renderStripCells,
|
|
49
|
+
DEFAULT_CHARSET,
|
|
49
50
|
DEFAULT_PADDING,
|
|
50
51
|
DEFAULT_TERMINAL_WIDTH,
|
|
51
52
|
DEFAULT_WRAP,
|
|
52
53
|
type BuildLineOptions,
|
|
54
|
+
type Charset,
|
|
53
55
|
} from "../render/strip.js";
|
|
54
56
|
import { applyClaudeCodeReserve } from "../utils/terminal-width.js";
|
|
55
57
|
import type { RichText } from "@promptctl/rich-js";
|
|
@@ -780,6 +782,12 @@ async function handleRequest(req: Request): Promise<HandledRequest> {
|
|
|
780
782
|
// reserve) derives from. [LAW:one-source-of-truth]
|
|
781
783
|
renderOpts.padding =
|
|
782
784
|
entry.state.config.globals.padding ?? DEFAULT_PADDING;
|
|
785
|
+
// [config-only] globals.charset, same shape as autoWrap/padding: the
|
|
786
|
+
// config global over the base floor is the whole resolution — the ONE
|
|
787
|
+
// home of the glyph-vocabulary choice pickJoiner derives from.
|
|
788
|
+
// [LAW:one-source-of-truth]
|
|
789
|
+
renderOpts.charset =
|
|
790
|
+
entry.state.config.globals.charset ?? DEFAULT_CHARSET;
|
|
783
791
|
// [LAW:single-enforcer] renderDsl internally calls
|
|
784
792
|
// `registry.applyInput(payload)` as its first step (see step 1 in
|
|
785
793
|
// src/dsl/render.ts). The daemon must not pre-apply — doing so
|
|
@@ -884,7 +892,10 @@ async function handleRequest(req: Request): Promise<HandledRequest> {
|
|
|
884
892
|
compiled: dbgEntry.compiled,
|
|
885
893
|
lastRenderBySegment:
|
|
886
894
|
req.what === "segments"
|
|
887
|
-
? serializeSegmentCells(
|
|
895
|
+
? serializeSegmentCells(
|
|
896
|
+
dbgEntry.lastRenderCellsBySegment,
|
|
897
|
+
dbgEntry.config.globals.charset ?? DEFAULT_CHARSET,
|
|
898
|
+
)
|
|
888
899
|
: EMPTY_RENDER_MAP,
|
|
889
900
|
};
|
|
890
901
|
return stay({ ok: true, debug: buildDebugSnapshot(req.what, dbgState) });
|
|
@@ -1072,6 +1083,7 @@ const RENDER_OPTS_BASE = {
|
|
|
1072
1083
|
colorCompatibility: "truecolor" as const,
|
|
1073
1084
|
wrap: DEFAULT_WRAP,
|
|
1074
1085
|
padding: DEFAULT_PADDING,
|
|
1086
|
+
charset: DEFAULT_CHARSET,
|
|
1075
1087
|
};
|
|
1076
1088
|
const DEBUG_RENDER_OPTS: BuildLineOptions = {
|
|
1077
1089
|
...RENDER_OPTS_BASE,
|
|
@@ -1083,12 +1095,22 @@ const DEBUG_RENDER_OPTS: BuildLineOptions = {
|
|
|
1083
1095
|
// the DaemonDslState type requires the field.
|
|
1084
1096
|
const EMPTY_RENDER_MAP = new Map<string, string>();
|
|
1085
1097
|
|
|
1098
|
+
// [LAW:one-source-of-truth] The joiner glyph vocabulary is a serialization-time
|
|
1099
|
+
// choice, and charset is config-only (no SessionState half) — so the faithful
|
|
1100
|
+
// value is fully derivable from the sampled entry's config, unlike style, whose
|
|
1101
|
+
// live session-over-config resolution needs a session a debug request doesn't
|
|
1102
|
+
// carry. The caller threads the entry-resolved charset; this serializer never
|
|
1103
|
+
// re-defaults it.
|
|
1086
1104
|
function serializeSegmentCells(
|
|
1087
1105
|
cells: ReadonlyMap<string, readonly RichText[]>,
|
|
1106
|
+
charset: Charset,
|
|
1088
1107
|
): Map<string, string> {
|
|
1089
1108
|
const out = new Map<string, string>();
|
|
1090
1109
|
for (const [name, segCells] of cells) {
|
|
1091
|
-
out.set(
|
|
1110
|
+
out.set(
|
|
1111
|
+
name,
|
|
1112
|
+
renderStripCells(segCells, { ...DEBUG_RENDER_OPTS, charset }),
|
|
1113
|
+
);
|
|
1092
1114
|
}
|
|
1093
1115
|
return out;
|
|
1094
1116
|
}
|
package/src/demo/dsl.ts
CHANGED
|
@@ -32,6 +32,7 @@ import { listResolvablePaletteNames } from "../themes/policy.js";
|
|
|
32
32
|
import { effectiveThemeName, resolverForThemeName } from "../themes/index.js";
|
|
33
33
|
import { registerDslConfig, renderDsl } from "../dsl/render.js";
|
|
34
34
|
import {
|
|
35
|
+
DEFAULT_CHARSET,
|
|
35
36
|
DEFAULT_PADDING,
|
|
36
37
|
DEFAULT_TERMINAL_WIDTH,
|
|
37
38
|
DEFAULT_WRAP,
|
|
@@ -118,6 +119,7 @@ try {
|
|
|
118
119
|
// default-on floor.
|
|
119
120
|
wrap: config.globals.autoWrap ?? DEFAULT_WRAP,
|
|
120
121
|
padding: config.globals.padding ?? DEFAULT_PADDING,
|
|
122
|
+
charset: config.globals.charset ?? DEFAULT_CHARSET,
|
|
121
123
|
},
|
|
122
124
|
);
|
|
123
125
|
process.stdout.write(` ${line}\n`);
|
package/src/render/strip.ts
CHANGED
|
@@ -9,8 +9,10 @@ import {
|
|
|
9
9
|
renderToString,
|
|
10
10
|
type Joiner,
|
|
11
11
|
type ColorSystemSpec,
|
|
12
|
+
type PowerlineJoinerOptions,
|
|
13
|
+
type CapsuleJoinerOptions,
|
|
12
14
|
} from "@promptctl/rich-js";
|
|
13
|
-
import type { StripStyle } from "../themes/policy.js";
|
|
15
|
+
import type { Charset, StripStyle } from "../themes/policy.js";
|
|
14
16
|
|
|
15
17
|
export interface RenderedSegmentLike {
|
|
16
18
|
type: string;
|
|
@@ -19,11 +21,11 @@ export interface RenderedSegmentLike {
|
|
|
19
21
|
fgHex?: string;
|
|
20
22
|
}
|
|
21
23
|
|
|
22
|
-
// [LAW:one-source-of-truth] `StripStyle` and
|
|
23
|
-
// themes/policy.ts (the
|
|
24
|
+
// [LAW:one-source-of-truth] `StripStyle`/`Charset` and their value lists live
|
|
25
|
+
// in themes/policy.ts (the render-identifier policy module, importable by the
|
|
24
26
|
// option-source machinery without a render→template-engine cycle). Re-exported
|
|
25
|
-
// here so render-layer consumers can keep importing
|
|
26
|
-
export type { StripStyle };
|
|
27
|
+
// here so render-layer consumers can keep importing them from the strip module.
|
|
28
|
+
export type { Charset, StripStyle };
|
|
27
29
|
|
|
28
30
|
// [LAW:one-source-of-truth] Raw terminal cols we assume when the wire
|
|
29
31
|
// didn't give us one (older client, env-stripped spawn). RAW — not
|
|
@@ -43,6 +45,12 @@ export const DEFAULT_WRAP = true;
|
|
|
43
45
|
// (`globals.padding ?? DEFAULT_PADDING`) derives from this constant.
|
|
44
46
|
export const DEFAULT_PADDING = 1;
|
|
45
47
|
|
|
48
|
+
// [LAW:one-source-of-truth] The one statement of the globals.charset default
|
|
49
|
+
// (powerline unicode glyphs — current behavior, matching the legacy
|
|
50
|
+
// display.charset). Every resolver of the config global
|
|
51
|
+
// (`globals.charset ?? DEFAULT_CHARSET`) derives from this constant.
|
|
52
|
+
export const DEFAULT_CHARSET: Charset = "unicode";
|
|
53
|
+
|
|
46
54
|
export interface BuildLineOptions {
|
|
47
55
|
style: StripStyle;
|
|
48
56
|
colorCompatibility: ColorSystemSpec;
|
|
@@ -65,20 +73,51 @@ export interface BuildLineOptions {
|
|
|
65
73
|
// site states the resolved value; the cell builders derive from it and
|
|
66
74
|
// never re-default.
|
|
67
75
|
padding: number;
|
|
76
|
+
// [LAW:one-source-of-truth] Which glyph vocabulary the joiners render with
|
|
77
|
+
// (globals.charset, default "unicode" — the legacy display.charset).
|
|
78
|
+
// Required for the same reason as padding: the resolved value reaches every
|
|
79
|
+
// render explicitly; pickJoiner derives from it and never re-defaults.
|
|
80
|
+
charset: Charset;
|
|
68
81
|
}
|
|
69
82
|
|
|
70
|
-
|
|
83
|
+
// [LAW:dataflow-not-control-flow] Charset variability lives in these VALUES,
|
|
84
|
+
// not in branches: per style, each charset names the joiner-construction
|
|
85
|
+
// options. The unicode entries are empty on purpose — rich-js owns its
|
|
86
|
+
// canonical powerline glyphs (U+E0B0 / U+E0B6+U+E0B4) and restating them here
|
|
87
|
+
// would be a second source that could drift [LAW:one-source-of-truth]. The
|
|
88
|
+
// ascii glyphs are DELIBERATELY single-column (same display width as the
|
|
89
|
+
// unicode caps) so stripChromeCols stays charset-invariant; the
|
|
90
|
+
// measured-chrome pin in test/picker-pagination.test.ts checks that for every
|
|
91
|
+
// style × charset. Widen a glyph and that pin fails loudly.
|
|
92
|
+
const POWERLINE_GLYPHS: Record<Charset, PowerlineJoinerOptions> = {
|
|
93
|
+
unicode: {},
|
|
94
|
+
ascii: { glyph: ">" },
|
|
95
|
+
};
|
|
96
|
+
const CAPSULE_GLYPHS: Record<Charset, CapsuleJoinerOptions> = {
|
|
97
|
+
unicode: {},
|
|
98
|
+
ascii: { left: "(", right: ")" },
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
function pickJoiner(
|
|
102
|
+
style: StripStyle,
|
|
103
|
+
charset: Charset,
|
|
104
|
+
separator?: string,
|
|
105
|
+
): Joiner {
|
|
71
106
|
// [LAW:dataflow-not-control-flow] joiner choice is data-driven; one arm per
|
|
72
|
-
// shape.
|
|
107
|
+
// shape. Style picks the joiner CLASS, charset indexes the glyph options fed
|
|
108
|
+
// to it — the two dimensions stay orthogonal (any style renders under either
|
|
109
|
+
// charset). Plain takes no charset lookup: its separator is already user
|
|
110
|
+
// data (globals.default_separator) and its default (" | ") is ASCII-safe.
|
|
111
|
+
// [LAW:types-are-the-program] Total over StripStyle — the `never`
|
|
73
112
|
// default makes adding a STRIP_STYLES member a compile error here until it
|
|
74
113
|
// gets a joiner, so the picker's domain can never offer an unrenderable shape.
|
|
75
114
|
switch (style) {
|
|
76
115
|
case "capsule":
|
|
77
|
-
return new CapsuleJoiner();
|
|
116
|
+
return new CapsuleJoiner(CAPSULE_GLYPHS[charset]);
|
|
78
117
|
case "plain":
|
|
79
118
|
return new PlainJoiner(separator !== undefined ? { separator } : {});
|
|
80
119
|
case "powerline":
|
|
81
|
-
return new PowerlineJoiner();
|
|
120
|
+
return new PowerlineJoiner(POWERLINE_GLYPHS[charset]);
|
|
82
121
|
default: {
|
|
83
122
|
const _exhaustive: never = style;
|
|
84
123
|
return _exhaustive;
|
|
@@ -96,10 +135,13 @@ function pickJoiner(style: StripStyle, separator?: string): Joiner {
|
|
|
96
135
|
// [LAW:dataflow-not-control-flow] / [LAW:types-are-the-program] Total over
|
|
97
136
|
// StripStyle — the `never` default makes adding a STRIP_STYLES member a compile
|
|
98
137
|
// error here until its chrome is declared, the same guard pickJoiner carries. The
|
|
99
|
-
// numbers are the
|
|
100
|
-
// trailing separator (
|
|
101
|
-
//
|
|
102
|
-
//
|
|
138
|
+
// numbers are the cap glyphs pickJoiner constructs: powerline appends ONE
|
|
139
|
+
// trailing separator (1 col); capsule brackets BOTH edges (2 cols); plain has no
|
|
140
|
+
// caps. Charset does NOT change these — both glyph vocabularies use
|
|
141
|
+
// single-column caps by construction (see the glyph tables above), which is why
|
|
142
|
+
// this stays total over StripStyle alone. test/picker-pagination.test.ts
|
|
143
|
+
// measures the real rendered chrome against these for every style × charset so
|
|
144
|
+
// the declaration cannot drift from rich-js or from the ascii glyph choice.
|
|
103
145
|
export function stripChromeCols(style: StripStyle): number {
|
|
104
146
|
switch (style) {
|
|
105
147
|
case "powerline":
|
|
@@ -144,7 +186,7 @@ export function renderStripCells(
|
|
|
144
186
|
options: BuildLineOptions,
|
|
145
187
|
): string {
|
|
146
188
|
if (cells.length === 0) return "";
|
|
147
|
-
const joiner = pickJoiner(options.style, options.separator);
|
|
189
|
+
const joiner = pickJoiner(options.style, options.charset, options.separator);
|
|
148
190
|
if (options.wrap && Number.isFinite(options.width)) {
|
|
149
191
|
const flex = new FlexStrip([...cells], { joiner });
|
|
150
192
|
const out = renderToString(flex, {
|
package/src/themes/policy.ts
CHANGED
|
@@ -79,3 +79,20 @@ export function effectiveStripStyle(
|
|
|
79
79
|
const chosen = sessionStyle ?? globalsStyle ?? "powerline";
|
|
80
80
|
return isStripStyle(chosen) ? chosen : "powerline";
|
|
81
81
|
}
|
|
82
|
+
|
|
83
|
+
// --- Joiner charset identifiers ---
|
|
84
|
+
|
|
85
|
+
// [LAW:one-source-of-truth][LAW:types-are-the-program] The single canonical set
|
|
86
|
+
// of glyph vocabularies the strip joiners can render with (the legacy
|
|
87
|
+
// display.charset). Same species as STRIP_STYLES — a closed render-vocabulary
|
|
88
|
+
// enum hosted in this leaf policy module so the config loader (validation +
|
|
89
|
+
// JSON-schema emit) and the render layer (glyph dispatch) both derive from one
|
|
90
|
+
// literal without a config↔render cycle [LAW:one-way-deps]. "ascii" swaps the
|
|
91
|
+
// powerline-private-use cap glyphs (U+E0Bx — tofu without a Nerd Font) for
|
|
92
|
+
// plain-ASCII equivalents; it is orthogonal to StripStyle: style picks the
|
|
93
|
+
// joiner SHAPE, charset picks the glyph VALUES fed to it.
|
|
94
|
+
// [config-only] Unlike STRIP_STYLES there is no SessionState/click half, so no
|
|
95
|
+
// narrowing guard or effective* resolver — the config global over the default
|
|
96
|
+
// is the whole resolution.
|
|
97
|
+
export const CHARSETS = ["unicode", "ascii"] as const;
|
|
98
|
+
export type Charset = (typeof CHARSETS)[number];
|