@promptctl/cc-candybar 1.10.0 → 1.12.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 +50 -50
- package/package.json +5 -5
- package/schema/cc-candybar.schema.json +14 -0
- package/src/config/dsl-types.ts +23 -1
- package/src/config/loader/globals.ts +42 -1
- package/src/daemon/server.ts +41 -3
- package/src/demo/dsl.ts +7 -1
- package/src/render/strip.ts +73 -16
- package/src/themes/policy.ts +43 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@promptctl/cc-candybar",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.12.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.12.0",
|
|
95
|
+
"@promptctl/cc-candybar-darwin-x64": "1.12.0",
|
|
96
|
+
"@promptctl/cc-candybar-linux-x64": "1.12.0",
|
|
97
|
+
"@promptctl/cc-candybar-linux-arm64": "1.12.0"
|
|
98
98
|
},
|
|
99
99
|
"pnpm": {
|
|
100
100
|
"supportedArchitectures": {
|
|
@@ -40,6 +40,20 @@
|
|
|
40
40
|
"type": "integer",
|
|
41
41
|
"minimum": 0,
|
|
42
42
|
"maximum": 16
|
|
43
|
+
},
|
|
44
|
+
"charset": {
|
|
45
|
+
"enum": [
|
|
46
|
+
"unicode",
|
|
47
|
+
"ascii"
|
|
48
|
+
]
|
|
49
|
+
},
|
|
50
|
+
"colorCompatibility": {
|
|
51
|
+
"enum": [
|
|
52
|
+
"truecolor",
|
|
53
|
+
"256",
|
|
54
|
+
"ansi",
|
|
55
|
+
"none"
|
|
56
|
+
]
|
|
43
57
|
}
|
|
44
58
|
},
|
|
45
59
|
"additionalProperties": false
|
package/src/config/dsl-types.ts
CHANGED
|
@@ -14,7 +14,11 @@
|
|
|
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 {
|
|
17
|
+
import type {
|
|
18
|
+
Charset,
|
|
19
|
+
ColorCompatibility,
|
|
20
|
+
StripStyle,
|
|
21
|
+
} from "../themes/policy.js";
|
|
18
22
|
|
|
19
23
|
// [LAW:types-are-the-program] Three stages, three names.
|
|
20
24
|
//
|
|
@@ -204,6 +208,24 @@ export interface Globals {
|
|
|
204
208
|
// [config-only] The daemon resolves `globals.padding ?? 1` into
|
|
205
209
|
// renderOpts.padding; no SessionState/click half.
|
|
206
210
|
readonly padding?: number;
|
|
211
|
+
|
|
212
|
+
// The legacy display.charset knob: which glyph vocabulary the strip joiners
|
|
213
|
+
// render with. Default "unicode" (current behavior — rich-js's powerline
|
|
214
|
+
// caps, U+E0Bx). "ascii" swaps the caps for single-column ASCII glyphs so
|
|
215
|
+
// terminals/fonts without powerline glyphs render cleanly instead of tofu.
|
|
216
|
+
// Orthogonal to `style`: style picks the joiner shape, charset the glyphs.
|
|
217
|
+
// [config-only] The daemon resolves `globals.charset ?? "unicode"` into
|
|
218
|
+
// renderOpts.charset; no SessionState/click half.
|
|
219
|
+
readonly charset?: Charset;
|
|
220
|
+
|
|
221
|
+
// The legacy display.colorCompatibility knob: the color depth rich-js
|
|
222
|
+
// downsamples output to. Default "truecolor" (current behavior — NOT the
|
|
223
|
+
// legacy "auto" default, which would change rendering for existing users).
|
|
224
|
+
// The type excludes "auto" entirely: the daemon is detached, so env
|
|
225
|
+
// detection would read the wrong terminal — see COLOR_COMPATIBILITIES.
|
|
226
|
+
// [config-only] The daemon resolves `globals.colorCompatibility ??
|
|
227
|
+
// "truecolor"` into renderOpts.colorCompatibility; no SessionState/click half.
|
|
228
|
+
readonly colorCompatibility?: ColorCompatibility;
|
|
207
229
|
}
|
|
208
230
|
|
|
209
231
|
// [LAW:one-type-per-behavior] One discriminated union covers every source
|
|
@@ -4,19 +4,54 @@
|
|
|
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 {
|
|
7
|
+
import {
|
|
8
|
+
CHARSETS,
|
|
9
|
+
COLOR_COMPATIBILITIES,
|
|
10
|
+
STRIP_STYLES,
|
|
11
|
+
type ColorCompatibility,
|
|
12
|
+
} from "../../themes/policy.js";
|
|
8
13
|
import {
|
|
9
14
|
optionalBooleanSpec,
|
|
15
|
+
optionalEnum,
|
|
10
16
|
optionalEnumSpec,
|
|
11
17
|
optionalIntSpec,
|
|
12
18
|
optionalStringSpec,
|
|
13
19
|
paletteSpec,
|
|
14
20
|
record,
|
|
15
21
|
recordJson,
|
|
22
|
+
type FieldSpec,
|
|
16
23
|
type JsonNode,
|
|
17
24
|
type RecordSchema,
|
|
18
25
|
type ValidateCtx,
|
|
19
26
|
} from "./validate-core.js";
|
|
27
|
+
import { findKeyLine } from "./diagnostics.js";
|
|
28
|
+
|
|
29
|
+
// [LAW:types-are-the-program] Closed enum like `charset`, plus one
|
|
30
|
+
// migration-pointing rejection: "auto" was the LEGACY default, so migrating
|
|
31
|
+
// configs will carry it — but the daemon runs detached, so rich-js env
|
|
32
|
+
// detection would downsample against the daemon's terminal, not the client's.
|
|
33
|
+
// Rather than ship that silent lie [LAW:no-silent-failure], "auto" is outside
|
|
34
|
+
// the ColorCompatibility domain and gets an error that says why (same species
|
|
35
|
+
// as the removed-layout migration errors in layout.ts). The json emit and the
|
|
36
|
+
// membership check derive from the same COLOR_COMPATIBILITIES literal.
|
|
37
|
+
const colorCompatibilitySpec: FieldSpec<ColorCompatibility> = {
|
|
38
|
+
required: false,
|
|
39
|
+
json: { enum: [...COLOR_COMPATIBILITIES] },
|
|
40
|
+
parse: (ctx, path, field, raw) => {
|
|
41
|
+
if (raw[field] === "auto") {
|
|
42
|
+
ctx.issues.push({
|
|
43
|
+
path: `${path}.${field}`,
|
|
44
|
+
message:
|
|
45
|
+
`${path}.${field}: "auto" is not supported — the render daemon runs detached, ` +
|
|
46
|
+
`so terminal detection would read the daemon's environment, not your terminal's. ` +
|
|
47
|
+
`Pick an explicit depth: ${COLOR_COMPATIBILITIES.join(", ")}`,
|
|
48
|
+
line: findKeyLine(ctx.source, [...path.split("."), field]),
|
|
49
|
+
});
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
52
|
+
return optionalEnum(ctx, path, raw, field, COLOR_COMPATIBILITIES);
|
|
53
|
+
},
|
|
54
|
+
};
|
|
20
55
|
|
|
21
56
|
const GLOBALS_SCHEMA: RecordSchema<Globals> = {
|
|
22
57
|
noun: "globals key",
|
|
@@ -37,6 +72,12 @@ const GLOBALS_SCHEMA: RecordSchema<Globals> = {
|
|
|
37
72
|
// [LAW:no-silent-failure] — an absurd value is a loud load error, not a
|
|
38
73
|
// silently-huge render.
|
|
39
74
|
padding: optionalIntSpec({ min: 0, max: 16 }),
|
|
75
|
+
// [LAW:types-are-the-program] Closed enum like `style`: the joiner glyph
|
|
76
|
+
// vocabularies pickJoiner can render — validates by membership, emits a
|
|
77
|
+
// JSON-Schema `enum` from the same CHARSETS literal.
|
|
78
|
+
charset: optionalEnumSpec(CHARSETS),
|
|
79
|
+
// Closed enum with a bespoke "auto" rejection — see colorCompatibilitySpec.
|
|
80
|
+
colorCompatibility: colorCompatibilitySpec,
|
|
40
81
|
},
|
|
41
82
|
};
|
|
42
83
|
|
package/src/daemon/server.ts
CHANGED
|
@@ -46,10 +46,14 @@ import {
|
|
|
46
46
|
} from "../themes/index.js";
|
|
47
47
|
import {
|
|
48
48
|
renderStripCells,
|
|
49
|
+
DEFAULT_CHARSET,
|
|
50
|
+
DEFAULT_COLOR_COMPATIBILITY,
|
|
49
51
|
DEFAULT_PADDING,
|
|
50
52
|
DEFAULT_TERMINAL_WIDTH,
|
|
51
53
|
DEFAULT_WRAP,
|
|
52
54
|
type BuildLineOptions,
|
|
55
|
+
type Charset,
|
|
56
|
+
type ColorCompatibility,
|
|
53
57
|
} from "../render/strip.js";
|
|
54
58
|
import { applyClaudeCodeReserve } from "../utils/terminal-width.js";
|
|
55
59
|
import type { RichText } from "@promptctl/rich-js";
|
|
@@ -780,6 +784,19 @@ async function handleRequest(req: Request): Promise<HandledRequest> {
|
|
|
780
784
|
// reserve) derives from. [LAW:one-source-of-truth]
|
|
781
785
|
renderOpts.padding =
|
|
782
786
|
entry.state.config.globals.padding ?? DEFAULT_PADDING;
|
|
787
|
+
// [config-only] globals.charset, same shape as autoWrap/padding: the
|
|
788
|
+
// config global over the base floor is the whole resolution — the ONE
|
|
789
|
+
// home of the glyph-vocabulary choice pickJoiner derives from.
|
|
790
|
+
// [LAW:one-source-of-truth]
|
|
791
|
+
renderOpts.charset =
|
|
792
|
+
entry.state.config.globals.charset ?? DEFAULT_CHARSET;
|
|
793
|
+
// [config-only] globals.colorCompatibility, same shape as charset:
|
|
794
|
+
// the config global over the base floor is the whole resolution —
|
|
795
|
+
// the ONE home of the color-depth choice rich-js downsamples to.
|
|
796
|
+
// [LAW:one-source-of-truth]
|
|
797
|
+
renderOpts.colorCompatibility =
|
|
798
|
+
entry.state.config.globals.colorCompatibility ??
|
|
799
|
+
DEFAULT_COLOR_COMPATIBILITY;
|
|
783
800
|
// [LAW:single-enforcer] renderDsl internally calls
|
|
784
801
|
// `registry.applyInput(payload)` as its first step (see step 1 in
|
|
785
802
|
// src/dsl/render.ts). The daemon must not pre-apply — doing so
|
|
@@ -884,7 +901,12 @@ async function handleRequest(req: Request): Promise<HandledRequest> {
|
|
|
884
901
|
compiled: dbgEntry.compiled,
|
|
885
902
|
lastRenderBySegment:
|
|
886
903
|
req.what === "segments"
|
|
887
|
-
? serializeSegmentCells(
|
|
904
|
+
? serializeSegmentCells(
|
|
905
|
+
dbgEntry.lastRenderCellsBySegment,
|
|
906
|
+
dbgEntry.config.globals.charset ?? DEFAULT_CHARSET,
|
|
907
|
+
dbgEntry.config.globals.colorCompatibility ??
|
|
908
|
+
DEFAULT_COLOR_COMPATIBILITY,
|
|
909
|
+
)
|
|
888
910
|
: EMPTY_RENDER_MAP,
|
|
889
911
|
};
|
|
890
912
|
return stay({ ok: true, debug: buildDebugSnapshot(req.what, dbgState) });
|
|
@@ -1069,9 +1091,10 @@ const verbCtx = { sessionState, dlog };
|
|
|
1069
1091
|
// is rendered standalone (wrap doesn't apply to a one-segment projection).
|
|
1070
1092
|
const RENDER_OPTS_BASE = {
|
|
1071
1093
|
style: "powerline" as const,
|
|
1072
|
-
colorCompatibility:
|
|
1094
|
+
colorCompatibility: DEFAULT_COLOR_COMPATIBILITY,
|
|
1073
1095
|
wrap: DEFAULT_WRAP,
|
|
1074
1096
|
padding: DEFAULT_PADDING,
|
|
1097
|
+
charset: DEFAULT_CHARSET,
|
|
1075
1098
|
};
|
|
1076
1099
|
const DEBUG_RENDER_OPTS: BuildLineOptions = {
|
|
1077
1100
|
...RENDER_OPTS_BASE,
|
|
@@ -1083,12 +1106,27 @@ const DEBUG_RENDER_OPTS: BuildLineOptions = {
|
|
|
1083
1106
|
// the DaemonDslState type requires the field.
|
|
1084
1107
|
const EMPTY_RENDER_MAP = new Map<string, string>();
|
|
1085
1108
|
|
|
1109
|
+
// [LAW:one-source-of-truth] The joiner glyph vocabulary and the color depth
|
|
1110
|
+
// are serialization-time choices, and both are config-only (no SessionState
|
|
1111
|
+
// half) — so the faithful values are fully derivable from the sampled entry's
|
|
1112
|
+
// config, unlike style, whose live session-over-config resolution needs a
|
|
1113
|
+
// session a debug request doesn't carry. The caller threads the
|
|
1114
|
+
// entry-resolved values; this serializer never re-defaults them.
|
|
1086
1115
|
function serializeSegmentCells(
|
|
1087
1116
|
cells: ReadonlyMap<string, readonly RichText[]>,
|
|
1117
|
+
charset: Charset,
|
|
1118
|
+
colorCompatibility: ColorCompatibility,
|
|
1088
1119
|
): Map<string, string> {
|
|
1089
1120
|
const out = new Map<string, string>();
|
|
1090
1121
|
for (const [name, segCells] of cells) {
|
|
1091
|
-
out.set(
|
|
1122
|
+
out.set(
|
|
1123
|
+
name,
|
|
1124
|
+
renderStripCells(segCells, {
|
|
1125
|
+
...DEBUG_RENDER_OPTS,
|
|
1126
|
+
charset,
|
|
1127
|
+
colorCompatibility,
|
|
1128
|
+
}),
|
|
1129
|
+
);
|
|
1092
1130
|
}
|
|
1093
1131
|
return out;
|
|
1094
1132
|
}
|
package/src/demo/dsl.ts
CHANGED
|
@@ -32,6 +32,8 @@ 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,
|
|
36
|
+
DEFAULT_COLOR_COMPATIBILITY,
|
|
35
37
|
DEFAULT_PADDING,
|
|
36
38
|
DEFAULT_TERMINAL_WIDTH,
|
|
37
39
|
DEFAULT_WRAP,
|
|
@@ -107,7 +109,10 @@ try {
|
|
|
107
109
|
basePalette,
|
|
108
110
|
{
|
|
109
111
|
style: "powerline",
|
|
110
|
-
|
|
112
|
+
// Same resolution the daemon applies: the config global over the
|
|
113
|
+
// truecolor default floor.
|
|
114
|
+
colorCompatibility:
|
|
115
|
+
config.globals.colorCompatibility ?? DEFAULT_COLOR_COMPATIBILITY,
|
|
111
116
|
// [LAW:one-source-of-truth] Demo applies the same Claude-Code-UI
|
|
112
117
|
// reserve the daemon does so demo output matches the bytes a real
|
|
113
118
|
// statusline would emit at the same terminal width.
|
|
@@ -118,6 +123,7 @@ try {
|
|
|
118
123
|
// default-on floor.
|
|
119
124
|
wrap: config.globals.autoWrap ?? DEFAULT_WRAP,
|
|
120
125
|
padding: config.globals.padding ?? DEFAULT_PADDING,
|
|
126
|
+
charset: config.globals.charset ?? DEFAULT_CHARSET,
|
|
121
127
|
},
|
|
122
128
|
);
|
|
123
129
|
process.stdout.write(` ${line}\n`);
|
package/src/render/strip.ts
CHANGED
|
@@ -8,9 +8,14 @@ import {
|
|
|
8
8
|
FlexStrip,
|
|
9
9
|
renderToString,
|
|
10
10
|
type Joiner,
|
|
11
|
-
type
|
|
11
|
+
type PowerlineJoinerOptions,
|
|
12
|
+
type CapsuleJoinerOptions,
|
|
12
13
|
} from "@promptctl/rich-js";
|
|
13
|
-
import type {
|
|
14
|
+
import type {
|
|
15
|
+
Charset,
|
|
16
|
+
ColorCompatibility,
|
|
17
|
+
StripStyle,
|
|
18
|
+
} from "../themes/policy.js";
|
|
14
19
|
|
|
15
20
|
export interface RenderedSegmentLike {
|
|
16
21
|
type: string;
|
|
@@ -19,11 +24,11 @@ export interface RenderedSegmentLike {
|
|
|
19
24
|
fgHex?: string;
|
|
20
25
|
}
|
|
21
26
|
|
|
22
|
-
// [LAW:one-source-of-truth] `StripStyle` and
|
|
23
|
-
// themes/policy.ts (the
|
|
27
|
+
// [LAW:one-source-of-truth] `StripStyle`/`Charset` and their value lists live
|
|
28
|
+
// in themes/policy.ts (the render-identifier policy module, importable by the
|
|
24
29
|
// option-source machinery without a render→template-engine cycle). Re-exported
|
|
25
|
-
// here so render-layer consumers can keep importing
|
|
26
|
-
export type { StripStyle };
|
|
30
|
+
// here so render-layer consumers can keep importing them from the strip module.
|
|
31
|
+
export type { Charset, ColorCompatibility, StripStyle };
|
|
27
32
|
|
|
28
33
|
// [LAW:one-source-of-truth] Raw terminal cols we assume when the wire
|
|
29
34
|
// didn't give us one (older client, env-stripped spawn). RAW — not
|
|
@@ -43,9 +48,27 @@ export const DEFAULT_WRAP = true;
|
|
|
43
48
|
// (`globals.padding ?? DEFAULT_PADDING`) derives from this constant.
|
|
44
49
|
export const DEFAULT_PADDING = 1;
|
|
45
50
|
|
|
51
|
+
// [LAW:one-source-of-truth] The one statement of the globals.charset default
|
|
52
|
+
// (powerline unicode glyphs — current behavior, matching the legacy
|
|
53
|
+
// display.charset). Every resolver of the config global
|
|
54
|
+
// (`globals.charset ?? DEFAULT_CHARSET`) derives from this constant.
|
|
55
|
+
export const DEFAULT_CHARSET: Charset = "unicode";
|
|
56
|
+
|
|
57
|
+
// [LAW:one-source-of-truth] The one statement of the globals.colorCompatibility
|
|
58
|
+
// default (truecolor — the CURRENT pinned value, deliberately NOT the legacy
|
|
59
|
+
// "auto" default, which would change rendering for existing users). Every
|
|
60
|
+
// resolver of the config global (`globals.colorCompatibility ??
|
|
61
|
+
// DEFAULT_COLOR_COMPATIBILITY`) derives from this constant.
|
|
62
|
+
export const DEFAULT_COLOR_COMPATIBILITY: ColorCompatibility = "truecolor";
|
|
63
|
+
|
|
46
64
|
export interface BuildLineOptions {
|
|
47
65
|
style: StripStyle;
|
|
48
|
-
|
|
66
|
+
// [LAW:types-are-the-program] Narrower than rich-js ColorSystemSpec on
|
|
67
|
+
// purpose: the four explicit depths only. "auto"/null never reach a render —
|
|
68
|
+
// the daemon is detached, so env detection would read the wrong terminal;
|
|
69
|
+
// the loader rejects "auto" at the trust boundary (see COLOR_COMPATIBILITIES
|
|
70
|
+
// in themes/policy.ts), and every construction site states a resolved depth.
|
|
71
|
+
colorCompatibility: ColorCompatibility;
|
|
49
72
|
separator?: string;
|
|
50
73
|
// [LAW:types-are-the-program] Every render carries a width. Required (not
|
|
51
74
|
// optional) so callers cannot silently drop the wire's value.
|
|
@@ -65,20 +88,51 @@ export interface BuildLineOptions {
|
|
|
65
88
|
// site states the resolved value; the cell builders derive from it and
|
|
66
89
|
// never re-default.
|
|
67
90
|
padding: number;
|
|
91
|
+
// [LAW:one-source-of-truth] Which glyph vocabulary the joiners render with
|
|
92
|
+
// (globals.charset, default "unicode" — the legacy display.charset).
|
|
93
|
+
// Required for the same reason as padding: the resolved value reaches every
|
|
94
|
+
// render explicitly; pickJoiner derives from it and never re-defaults.
|
|
95
|
+
charset: Charset;
|
|
68
96
|
}
|
|
69
97
|
|
|
70
|
-
|
|
98
|
+
// [LAW:dataflow-not-control-flow] Charset variability lives in these VALUES,
|
|
99
|
+
// not in branches: per style, each charset names the joiner-construction
|
|
100
|
+
// options. The unicode entries are empty on purpose — rich-js owns its
|
|
101
|
+
// canonical powerline glyphs (U+E0B0 / U+E0B6+U+E0B4) and restating them here
|
|
102
|
+
// would be a second source that could drift [LAW:one-source-of-truth]. The
|
|
103
|
+
// ascii glyphs are DELIBERATELY single-column (same display width as the
|
|
104
|
+
// unicode caps) so stripChromeCols stays charset-invariant; the
|
|
105
|
+
// measured-chrome pin in test/picker-pagination.test.ts checks that for every
|
|
106
|
+
// style × charset. Widen a glyph and that pin fails loudly.
|
|
107
|
+
const POWERLINE_GLYPHS: Record<Charset, PowerlineJoinerOptions> = {
|
|
108
|
+
unicode: {},
|
|
109
|
+
ascii: { glyph: ">" },
|
|
110
|
+
};
|
|
111
|
+
const CAPSULE_GLYPHS: Record<Charset, CapsuleJoinerOptions> = {
|
|
112
|
+
unicode: {},
|
|
113
|
+
ascii: { left: "(", right: ")" },
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
function pickJoiner(
|
|
117
|
+
style: StripStyle,
|
|
118
|
+
charset: Charset,
|
|
119
|
+
separator?: string,
|
|
120
|
+
): Joiner {
|
|
71
121
|
// [LAW:dataflow-not-control-flow] joiner choice is data-driven; one arm per
|
|
72
|
-
// shape.
|
|
122
|
+
// shape. Style picks the joiner CLASS, charset indexes the glyph options fed
|
|
123
|
+
// to it — the two dimensions stay orthogonal (any style renders under either
|
|
124
|
+
// charset). Plain takes no charset lookup: its separator is already user
|
|
125
|
+
// data (globals.default_separator) and its default (" | ") is ASCII-safe.
|
|
126
|
+
// [LAW:types-are-the-program] Total over StripStyle — the `never`
|
|
73
127
|
// default makes adding a STRIP_STYLES member a compile error here until it
|
|
74
128
|
// gets a joiner, so the picker's domain can never offer an unrenderable shape.
|
|
75
129
|
switch (style) {
|
|
76
130
|
case "capsule":
|
|
77
|
-
return new CapsuleJoiner();
|
|
131
|
+
return new CapsuleJoiner(CAPSULE_GLYPHS[charset]);
|
|
78
132
|
case "plain":
|
|
79
133
|
return new PlainJoiner(separator !== undefined ? { separator } : {});
|
|
80
134
|
case "powerline":
|
|
81
|
-
return new PowerlineJoiner();
|
|
135
|
+
return new PowerlineJoiner(POWERLINE_GLYPHS[charset]);
|
|
82
136
|
default: {
|
|
83
137
|
const _exhaustive: never = style;
|
|
84
138
|
return _exhaustive;
|
|
@@ -96,10 +150,13 @@ function pickJoiner(style: StripStyle, separator?: string): Joiner {
|
|
|
96
150
|
// [LAW:dataflow-not-control-flow] / [LAW:types-are-the-program] Total over
|
|
97
151
|
// StripStyle — the `never` default makes adding a STRIP_STYLES member a compile
|
|
98
152
|
// error here until its chrome is declared, the same guard pickJoiner carries. The
|
|
99
|
-
// numbers are the
|
|
100
|
-
// trailing separator (
|
|
101
|
-
//
|
|
102
|
-
//
|
|
153
|
+
// numbers are the cap glyphs pickJoiner constructs: powerline appends ONE
|
|
154
|
+
// trailing separator (1 col); capsule brackets BOTH edges (2 cols); plain has no
|
|
155
|
+
// caps. Charset does NOT change these — both glyph vocabularies use
|
|
156
|
+
// single-column caps by construction (see the glyph tables above), which is why
|
|
157
|
+
// this stays total over StripStyle alone. test/picker-pagination.test.ts
|
|
158
|
+
// measures the real rendered chrome against these for every style × charset so
|
|
159
|
+
// the declaration cannot drift from rich-js or from the ascii glyph choice.
|
|
103
160
|
export function stripChromeCols(style: StripStyle): number {
|
|
104
161
|
switch (style) {
|
|
105
162
|
case "powerline":
|
|
@@ -144,7 +201,7 @@ export function renderStripCells(
|
|
|
144
201
|
options: BuildLineOptions,
|
|
145
202
|
): string {
|
|
146
203
|
if (cells.length === 0) return "";
|
|
147
|
-
const joiner = pickJoiner(options.style, options.separator);
|
|
204
|
+
const joiner = pickJoiner(options.style, options.charset, options.separator);
|
|
148
205
|
if (options.wrap && Number.isFinite(options.width)) {
|
|
149
206
|
const flex = new FlexStrip([...cells], { joiner });
|
|
150
207
|
const out = renderToString(flex, {
|
package/src/themes/policy.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// PaletteResolver, no ColorRgba, no hex. The semantic/anchor knowledge
|
|
6
6
|
// (which tokens keep their hue) stays in rich-js (ANCHORED_ROOTS), not here.
|
|
7
7
|
|
|
8
|
-
import { listThemePalettes } from "@promptctl/rich-js";
|
|
8
|
+
import { listThemePalettes, type ColorSystemSpec } from "@promptctl/rich-js";
|
|
9
9
|
|
|
10
10
|
// --- Theme name aliasing ---
|
|
11
11
|
|
|
@@ -79,3 +79,45 @@ 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];
|
|
99
|
+
|
|
100
|
+
// --- Color-depth identifiers ---
|
|
101
|
+
|
|
102
|
+
// [LAW:one-source-of-truth][LAW:types-are-the-program] The single canonical set
|
|
103
|
+
// of color depths a config can pin (the legacy display.colorCompatibility).
|
|
104
|
+
// Same species as CHARSETS: a closed render-vocabulary enum hosted in this leaf
|
|
105
|
+
// policy module so the config loader (validation + JSON-schema emit) and the
|
|
106
|
+
// render layer both derive from one literal without a config↔render cycle
|
|
107
|
+
// [LAW:one-way-deps]. `satisfies` ties every member to rich-js's
|
|
108
|
+
// ColorSystemSpec at compile time WITHOUT widening the derived union — if
|
|
109
|
+
// rich-js renames a depth, this literal fails to compile rather than drifting.
|
|
110
|
+
//
|
|
111
|
+
// Deliberately NARROWER than ColorSystemSpec: "auto" (and null) are excluded.
|
|
112
|
+
// The daemon is long-lived and detached, so its process env is NOT the client
|
|
113
|
+
// terminal's — rich-js env detection would silently downsample against the
|
|
114
|
+
// wrong terminal [LAW:no-silent-failure]. Honoring "auto" needs a client
|
|
115
|
+
// capability hint over the wire (the termCols pattern); until that lands, the
|
|
116
|
+
// loader rejects "auto" with a pointer instead of shipping a lie.
|
|
117
|
+
export const COLOR_COMPATIBILITIES = [
|
|
118
|
+
"truecolor",
|
|
119
|
+
"256",
|
|
120
|
+
"ansi",
|
|
121
|
+
"none",
|
|
122
|
+
] as const satisfies readonly ColorSystemSpec[];
|
|
123
|
+
export type ColorCompatibility = (typeof COLOR_COMPATIBILITIES)[number];
|