tickmarkr 1.79.0 → 1.81.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/adapters/kimi.js +23 -6
- package/dist/brand.d.ts +4 -0
- package/dist/brand.js +4 -0
- package/dist/cli/commands/ui.d.ts +1 -1
- package/dist/cli/commands/ui.js +45 -4
- package/dist/drivers/herdr.js +28 -6
- package/dist/tui/cockpit/capture.d.ts +117 -0
- package/dist/tui/cockpit/capture.js +329 -0
- package/dist/tui/cockpit/components.d.ts +142 -0
- package/dist/tui/cockpit/components.js +274 -0
- package/dist/tui/cockpit/demo.d.ts +41 -0
- package/dist/tui/cockpit/demo.js +144 -0
- package/dist/tui/cockpit/derive.d.ts +39 -0
- package/dist/tui/cockpit/derive.js +390 -0
- package/dist/tui/cockpit/keys.d.ts +149 -0
- package/dist/tui/cockpit/keys.js +265 -0
- package/dist/tui/cockpit/layout.d.ts +76 -0
- package/dist/tui/cockpit/layout.js +84 -0
- package/dist/tui/cockpit/live.d.ts +31 -0
- package/dist/tui/cockpit/live.js +135 -0
- package/dist/tui/cockpit/run-cockpit.d.ts +26 -0
- package/dist/tui/cockpit/run-cockpit.js +248 -0
- package/dist/tui/cockpit/setup-cockpit.d.ts +44 -0
- package/dist/tui/cockpit/setup-cockpit.js +532 -0
- package/dist/tui/cockpit/theme.d.ts +161 -0
- package/dist/tui/cockpit/theme.js +170 -0
- package/package.json +2 -1
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
export type HexColour = `#${string}`;
|
|
2
|
+
export type ColourValue = {
|
|
3
|
+
readonly xterm: number;
|
|
4
|
+
readonly hex: HexColour;
|
|
5
|
+
};
|
|
6
|
+
/** The logo remains governed by src/brand.ts; cockpit data never modifies this ramp. */
|
|
7
|
+
export declare const LOGO_RAMP: readonly [84, 78, 41, 35];
|
|
8
|
+
/** Near-black panel fill against which every data shade must remain legible. */
|
|
9
|
+
export declare const COCKPIT_SURFACE: "#1a1a19";
|
|
10
|
+
/** Bright-to-deep, single-hue data ink. Depth communicates magnitude, never tile identity. */
|
|
11
|
+
export declare const COCKPIT_DATA_RAMP: readonly [{
|
|
12
|
+
readonly xterm: 84;
|
|
13
|
+
readonly hex: "#5fff5f";
|
|
14
|
+
}, {
|
|
15
|
+
readonly xterm: 41;
|
|
16
|
+
readonly hex: "#00d75f";
|
|
17
|
+
}, {
|
|
18
|
+
readonly xterm: 35;
|
|
19
|
+
readonly hex: "#00af5f";
|
|
20
|
+
}, {
|
|
21
|
+
readonly xterm: 29;
|
|
22
|
+
readonly hex: "#00875f";
|
|
23
|
+
}];
|
|
24
|
+
/** Documented near-misses retained as guard fixtures, not special cases in the guard. */
|
|
25
|
+
export declare const EXCLUDED_DATA_SHADES: {
|
|
26
|
+
readonly neighbourCollision: {
|
|
27
|
+
readonly xterm: 78;
|
|
28
|
+
readonly hex: "#5fd787";
|
|
29
|
+
};
|
|
30
|
+
readonly insufficientContrast: {
|
|
31
|
+
readonly xterm: 22;
|
|
32
|
+
readonly hex: "#005f00";
|
|
33
|
+
};
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* The only coloured semantic inks outside the green ramp. Shape and text make
|
|
37
|
+
* both meanings survive NO_COLOR and reduced-colour terminals.
|
|
38
|
+
*/
|
|
39
|
+
export declare const COCKPIT_INKS: {
|
|
40
|
+
readonly dataRamp: readonly [{
|
|
41
|
+
readonly xterm: 84;
|
|
42
|
+
readonly hex: "#5fff5f";
|
|
43
|
+
}, {
|
|
44
|
+
readonly xterm: 41;
|
|
45
|
+
readonly hex: "#00d75f";
|
|
46
|
+
}, {
|
|
47
|
+
readonly xterm: 35;
|
|
48
|
+
readonly hex: "#00af5f";
|
|
49
|
+
}, {
|
|
50
|
+
readonly xterm: 29;
|
|
51
|
+
readonly hex: "#00875f";
|
|
52
|
+
}];
|
|
53
|
+
readonly warning: {
|
|
54
|
+
readonly xterm: 214;
|
|
55
|
+
readonly hex: "#ffaf00";
|
|
56
|
+
readonly glyph: "!";
|
|
57
|
+
readonly label: "warning";
|
|
58
|
+
};
|
|
59
|
+
readonly failure: {
|
|
60
|
+
readonly xterm: 203;
|
|
61
|
+
readonly hex: "#ff5f5f";
|
|
62
|
+
readonly glyph: "✗";
|
|
63
|
+
readonly label: "failure";
|
|
64
|
+
};
|
|
65
|
+
};
|
|
66
|
+
/** Every drawable cockpit ink; the panel surface and text emphasis are not inks. */
|
|
67
|
+
export declare const COCKPIT_INK_VOCABULARY: readonly [{
|
|
68
|
+
readonly xterm: 84;
|
|
69
|
+
readonly hex: "#5fff5f";
|
|
70
|
+
}, {
|
|
71
|
+
readonly xterm: 41;
|
|
72
|
+
readonly hex: "#00d75f";
|
|
73
|
+
}, {
|
|
74
|
+
readonly xterm: 35;
|
|
75
|
+
readonly hex: "#00af5f";
|
|
76
|
+
}, {
|
|
77
|
+
readonly xterm: 29;
|
|
78
|
+
readonly hex: "#00875f";
|
|
79
|
+
}, {
|
|
80
|
+
readonly xterm: 214;
|
|
81
|
+
readonly hex: "#ffaf00";
|
|
82
|
+
readonly glyph: "!";
|
|
83
|
+
readonly label: "warning";
|
|
84
|
+
}, {
|
|
85
|
+
readonly xterm: 203;
|
|
86
|
+
readonly hex: "#ff5f5f";
|
|
87
|
+
readonly glyph: "✗";
|
|
88
|
+
readonly label: "failure";
|
|
89
|
+
}];
|
|
90
|
+
/** Text hierarchy is independent of hue: weight and dimming are its only axes. */
|
|
91
|
+
export declare const TEXT_EMPHASIS_TOKENS: {
|
|
92
|
+
readonly strong: {
|
|
93
|
+
readonly weight: "bold";
|
|
94
|
+
readonly dimmed: false;
|
|
95
|
+
};
|
|
96
|
+
readonly normal: {
|
|
97
|
+
readonly weight: "normal";
|
|
98
|
+
readonly dimmed: false;
|
|
99
|
+
};
|
|
100
|
+
readonly dim: {
|
|
101
|
+
readonly weight: "normal";
|
|
102
|
+
readonly dimmed: true;
|
|
103
|
+
};
|
|
104
|
+
};
|
|
105
|
+
/** Deliberate 16-colour choices. Renderers select this map; they do not infer one. */
|
|
106
|
+
export declare const REDUCED_COLOUR_MAPPING: {
|
|
107
|
+
readonly dataRamp: readonly [{
|
|
108
|
+
readonly from: "#5fff5f";
|
|
109
|
+
readonly to: "bright-green";
|
|
110
|
+
}, {
|
|
111
|
+
readonly from: "#00d75f";
|
|
112
|
+
readonly to: "green";
|
|
113
|
+
}, {
|
|
114
|
+
readonly from: "#00af5f";
|
|
115
|
+
readonly to: "green";
|
|
116
|
+
}, {
|
|
117
|
+
readonly from: "#00875f";
|
|
118
|
+
readonly to: "dim-green";
|
|
119
|
+
}];
|
|
120
|
+
readonly warning: {
|
|
121
|
+
readonly from: "#ffaf00";
|
|
122
|
+
readonly to: "yellow";
|
|
123
|
+
};
|
|
124
|
+
readonly failure: {
|
|
125
|
+
readonly from: "#ff5f5f";
|
|
126
|
+
readonly to: "red";
|
|
127
|
+
};
|
|
128
|
+
};
|
|
129
|
+
export type TileMagnitude = 0 | 1 | 2 | 3;
|
|
130
|
+
export type TileColourInput = {
|
|
131
|
+
readonly magnitude: TileMagnitude;
|
|
132
|
+
};
|
|
133
|
+
/**
|
|
134
|
+
* Resolve reusable tile ink from magnitude alone. Tile names and positions are
|
|
135
|
+
* deliberately absent from the contract, so separate tiles cannot acquire identity hues.
|
|
136
|
+
*/
|
|
137
|
+
export declare function resolveTileColour({ magnitude }: TileColourInput): (typeof COCKPIT_DATA_RAMP)[number];
|
|
138
|
+
export declare const DATA_RAMP_GUARD: {
|
|
139
|
+
readonly requiredSteps: 4;
|
|
140
|
+
readonly minimumContrast: 3;
|
|
141
|
+
readonly minimumNeighbourDistance: 0.08;
|
|
142
|
+
readonly maximumLightnessStepSpread: 0.02;
|
|
143
|
+
};
|
|
144
|
+
export type PaletteGuardIssueKind = "step-count" | "invalid-colour" | "lightness-order" | "lightness-spacing" | "neighbour-collision" | "contrast";
|
|
145
|
+
export type PaletteGuardIssue = {
|
|
146
|
+
readonly kind: PaletteGuardIssueKind;
|
|
147
|
+
readonly indexes: readonly number[];
|
|
148
|
+
};
|
|
149
|
+
export type PaletteGuardVerdict = {
|
|
150
|
+
readonly ok: boolean;
|
|
151
|
+
readonly lightness: readonly number[];
|
|
152
|
+
readonly lightnessSteps: readonly number[];
|
|
153
|
+
readonly neighbourDistances: readonly number[];
|
|
154
|
+
readonly contrastRatios: readonly number[];
|
|
155
|
+
readonly issues: readonly PaletteGuardIssue[];
|
|
156
|
+
};
|
|
157
|
+
/**
|
|
158
|
+
* Evaluate any four candidate values. Xterm indexes and declared palette names
|
|
159
|
+
* are intentionally ignored: only parsed colour values can earn a verdict.
|
|
160
|
+
*/
|
|
161
|
+
export declare function guardDataRamp(candidate: readonly ColourValue[], surface?: HexColour): PaletteGuardVerdict;
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { BRAND_RAMP, GLYPHS } from "../../brand.js";
|
|
2
|
+
/** The logo remains governed by src/brand.ts; cockpit data never modifies this ramp. */
|
|
3
|
+
export const LOGO_RAMP = BRAND_RAMP;
|
|
4
|
+
/** Near-black panel fill against which every data shade must remain legible. */
|
|
5
|
+
export const COCKPIT_SURFACE = "#1a1a19";
|
|
6
|
+
/** Bright-to-deep, single-hue data ink. Depth communicates magnitude, never tile identity. */
|
|
7
|
+
export const COCKPIT_DATA_RAMP = [
|
|
8
|
+
{ xterm: 84, hex: "#5fff5f" },
|
|
9
|
+
{ xterm: 41, hex: "#00d75f" },
|
|
10
|
+
{ xterm: 35, hex: "#00af5f" },
|
|
11
|
+
{ xterm: 29, hex: "#00875f" },
|
|
12
|
+
];
|
|
13
|
+
/** Documented near-misses retained as guard fixtures, not special cases in the guard. */
|
|
14
|
+
export const EXCLUDED_DATA_SHADES = {
|
|
15
|
+
neighbourCollision: { xterm: 78, hex: "#5fd787" },
|
|
16
|
+
insufficientContrast: { xterm: 22, hex: "#005f00" },
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* The only coloured semantic inks outside the green ramp. Shape and text make
|
|
20
|
+
* both meanings survive NO_COLOR and reduced-colour terminals.
|
|
21
|
+
*/
|
|
22
|
+
export const COCKPIT_INKS = {
|
|
23
|
+
dataRamp: COCKPIT_DATA_RAMP,
|
|
24
|
+
warning: {
|
|
25
|
+
xterm: 214,
|
|
26
|
+
hex: "#ffaf00",
|
|
27
|
+
glyph: GLYPHS.attention,
|
|
28
|
+
label: "warning",
|
|
29
|
+
},
|
|
30
|
+
failure: {
|
|
31
|
+
xterm: 203,
|
|
32
|
+
hex: "#ff5f5f",
|
|
33
|
+
glyph: GLYPHS.fail,
|
|
34
|
+
label: "failure",
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
/** Every drawable cockpit ink; the panel surface and text emphasis are not inks. */
|
|
38
|
+
export const COCKPIT_INK_VOCABULARY = [
|
|
39
|
+
...COCKPIT_DATA_RAMP,
|
|
40
|
+
COCKPIT_INKS.warning,
|
|
41
|
+
COCKPIT_INKS.failure,
|
|
42
|
+
];
|
|
43
|
+
/** Text hierarchy is independent of hue: weight and dimming are its only axes. */
|
|
44
|
+
export const TEXT_EMPHASIS_TOKENS = {
|
|
45
|
+
strong: { weight: "bold", dimmed: false },
|
|
46
|
+
normal: { weight: "normal", dimmed: false },
|
|
47
|
+
dim: { weight: "normal", dimmed: true },
|
|
48
|
+
};
|
|
49
|
+
/** Deliberate 16-colour choices. Renderers select this map; they do not infer one. */
|
|
50
|
+
export const REDUCED_COLOUR_MAPPING = {
|
|
51
|
+
dataRamp: [
|
|
52
|
+
{ from: COCKPIT_DATA_RAMP[0].hex, to: "bright-green" },
|
|
53
|
+
{ from: COCKPIT_DATA_RAMP[1].hex, to: "green" },
|
|
54
|
+
{ from: COCKPIT_DATA_RAMP[2].hex, to: "green" },
|
|
55
|
+
{ from: COCKPIT_DATA_RAMP[3].hex, to: "dim-green" },
|
|
56
|
+
],
|
|
57
|
+
warning: { from: COCKPIT_INKS.warning.hex, to: "yellow" },
|
|
58
|
+
failure: { from: COCKPIT_INKS.failure.hex, to: "red" },
|
|
59
|
+
};
|
|
60
|
+
/**
|
|
61
|
+
* Resolve reusable tile ink from magnitude alone. Tile names and positions are
|
|
62
|
+
* deliberately absent from the contract, so separate tiles cannot acquire identity hues.
|
|
63
|
+
*/
|
|
64
|
+
export function resolveTileColour({ magnitude }) {
|
|
65
|
+
const ink = COCKPIT_DATA_RAMP[magnitude];
|
|
66
|
+
if (!ink)
|
|
67
|
+
throw new RangeError(`invalid tile magnitude: ${magnitude}`);
|
|
68
|
+
return ink;
|
|
69
|
+
}
|
|
70
|
+
export const DATA_RAMP_GUARD = {
|
|
71
|
+
requiredSteps: 4,
|
|
72
|
+
minimumContrast: 3,
|
|
73
|
+
minimumNeighbourDistance: 0.08,
|
|
74
|
+
maximumLightnessStepSpread: 0.02,
|
|
75
|
+
};
|
|
76
|
+
const parseHex = (hex) => {
|
|
77
|
+
const match = /^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i.exec(hex);
|
|
78
|
+
if (!match)
|
|
79
|
+
return undefined;
|
|
80
|
+
return [
|
|
81
|
+
Number.parseInt(match[1], 16) / 255,
|
|
82
|
+
Number.parseInt(match[2], 16) / 255,
|
|
83
|
+
Number.parseInt(match[3], 16) / 255,
|
|
84
|
+
];
|
|
85
|
+
};
|
|
86
|
+
const linearChannel = (channel) => channel <= 0.04045
|
|
87
|
+
? channel / 12.92
|
|
88
|
+
: ((channel + 0.055) / 1.055) ** 2.4;
|
|
89
|
+
const relativeLuminance = ([red, green, blue]) => (0.2126 * linearChannel(red))
|
|
90
|
+
+ (0.7152 * linearChannel(green))
|
|
91
|
+
+ (0.0722 * linearChannel(blue));
|
|
92
|
+
const toOklab = ([red, green, blue]) => {
|
|
93
|
+
const r = linearChannel(red);
|
|
94
|
+
const g = linearChannel(green);
|
|
95
|
+
const b = linearChannel(blue);
|
|
96
|
+
const l = Math.cbrt((0.4122214708 * r) + (0.5363325363 * g) + (0.0514459929 * b));
|
|
97
|
+
const m = Math.cbrt((0.2119034982 * r) + (0.6806995451 * g) + (0.1073969566 * b));
|
|
98
|
+
const s = Math.cbrt((0.0883024619 * r) + (0.2817188376 * g) + (0.6299787005 * b));
|
|
99
|
+
return [
|
|
100
|
+
(0.2104542553 * l) + (0.793617785 * m) - (0.0040720468 * s),
|
|
101
|
+
(1.9779984951 * l) - (2.428592205 * m) + (0.4505937099 * s),
|
|
102
|
+
(0.0259040371 * l) + (0.7827717662 * m) - (0.808675766 * s),
|
|
103
|
+
];
|
|
104
|
+
};
|
|
105
|
+
const oklabDistance = (left, right) => Math.hypot(left[0] - right[0], left[1] - right[1], left[2] - right[2]);
|
|
106
|
+
const contrastRatio = (foreground, background) => {
|
|
107
|
+
const foregroundLuminance = relativeLuminance(foreground);
|
|
108
|
+
const backgroundLuminance = relativeLuminance(background);
|
|
109
|
+
const lighter = Math.max(foregroundLuminance, backgroundLuminance);
|
|
110
|
+
const darker = Math.min(foregroundLuminance, backgroundLuminance);
|
|
111
|
+
return (lighter + 0.05) / (darker + 0.05);
|
|
112
|
+
};
|
|
113
|
+
/**
|
|
114
|
+
* Evaluate any four candidate values. Xterm indexes and declared palette names
|
|
115
|
+
* are intentionally ignored: only parsed colour values can earn a verdict.
|
|
116
|
+
*/
|
|
117
|
+
export function guardDataRamp(candidate, surface = COCKPIT_SURFACE) {
|
|
118
|
+
const issues = [];
|
|
119
|
+
if (candidate.length !== DATA_RAMP_GUARD.requiredSteps) {
|
|
120
|
+
issues.push({ kind: "step-count", indexes: candidate.map((_, index) => index) });
|
|
121
|
+
}
|
|
122
|
+
const parsed = candidate.map((step) => parseHex(step.hex));
|
|
123
|
+
const invalidIndexes = parsed.flatMap((value, index) => value ? [] : [index]);
|
|
124
|
+
const surfaceRgb = parseHex(surface);
|
|
125
|
+
if (invalidIndexes.length > 0 || !surfaceRgb) {
|
|
126
|
+
issues.push({
|
|
127
|
+
kind: "invalid-colour",
|
|
128
|
+
indexes: !surfaceRgb ? [-1, ...invalidIndexes] : invalidIndexes,
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
const labs = parsed.map((value) => value ? toOklab(value) : undefined);
|
|
132
|
+
const lightness = labs.map((value) => value?.[0] ?? Number.NaN);
|
|
133
|
+
const lightnessSteps = lightness.slice(1).map((value, index) => lightness[index] - value);
|
|
134
|
+
const neighbourDistances = labs.slice(1).map((value, index) => {
|
|
135
|
+
const previous = labs[index];
|
|
136
|
+
return previous && value ? oklabDistance(previous, value) : Number.NaN;
|
|
137
|
+
});
|
|
138
|
+
const contrastRatios = parsed.map((value) => value && surfaceRgb ? contrastRatio(value, surfaceRgb) : Number.NaN);
|
|
139
|
+
const unorderedIndexes = lightnessSteps.flatMap((step, index) => Number.isFinite(step) && step <= 0 ? [index, index + 1] : []);
|
|
140
|
+
if (unorderedIndexes.length > 0) {
|
|
141
|
+
issues.push({ kind: "lightness-order", indexes: [...new Set(unorderedIndexes)] });
|
|
142
|
+
}
|
|
143
|
+
const finiteSteps = lightnessSteps.filter(Number.isFinite);
|
|
144
|
+
if (finiteSteps.length > 1
|
|
145
|
+
&& Math.max(...finiteSteps) - Math.min(...finiteSteps)
|
|
146
|
+
> DATA_RAMP_GUARD.maximumLightnessStepSpread) {
|
|
147
|
+
issues.push({
|
|
148
|
+
kind: "lightness-spacing",
|
|
149
|
+
indexes: lightnessSteps.flatMap((_, index) => [index, index + 1]),
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
const collisionIndexes = neighbourDistances.flatMap((distance, index) => Number.isFinite(distance) && distance < DATA_RAMP_GUARD.minimumNeighbourDistance
|
|
153
|
+
? [index, index + 1]
|
|
154
|
+
: []);
|
|
155
|
+
if (collisionIndexes.length > 0) {
|
|
156
|
+
issues.push({ kind: "neighbour-collision", indexes: [...new Set(collisionIndexes)] });
|
|
157
|
+
}
|
|
158
|
+
const contrastIndexes = contrastRatios.flatMap((ratio, index) => Number.isFinite(ratio) && ratio < DATA_RAMP_GUARD.minimumContrast ? [index] : []);
|
|
159
|
+
if (contrastIndexes.length > 0) {
|
|
160
|
+
issues.push({ kind: "contrast", indexes: contrastIndexes });
|
|
161
|
+
}
|
|
162
|
+
return {
|
|
163
|
+
ok: issues.length === 0,
|
|
164
|
+
lightness,
|
|
165
|
+
lightnessSteps,
|
|
166
|
+
neighbourDistances,
|
|
167
|
+
contrastRatios,
|
|
168
|
+
issues,
|
|
169
|
+
};
|
|
170
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tickmarkr",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.81.0",
|
|
4
4
|
"description": "Spec in, verified work out.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
],
|
|
32
32
|
"scripts": {
|
|
33
33
|
"build": "tsc -p tsconfig.json",
|
|
34
|
+
"build:clean": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\" && npm run build",
|
|
34
35
|
"lint": "oxlint src tests scripts",
|
|
35
36
|
"pretest": "npm run build",
|
|
36
37
|
"test": "vitest run",
|