claudeup 6.7.1 → 6.8.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/package.json +4 -4
- package/src/__tests__/mate-availability.test.ts +156 -0
- package/src/__tests__/mate-catalog.test.ts +295 -0
- package/src/__tests__/model-visuals.test.tsx +1698 -25
- package/src/__tests__/models-adapter.test.ts +21 -6
- package/src/__tests__/models-cli.test.ts +100 -0
- package/src/__tests__/models-core.test.ts +273 -111
- package/src/__tests__/models-manager.test.ts +15 -12
- package/src/__tests__/models-presets-marketplace.test.ts +29 -2
- package/src/__tests__/models-screen-state.test.ts +57 -1
- package/src/cli/doctor.ts +8 -13
- package/src/cli/models.ts +97 -13
- package/src/data/models-presets.ts +62 -2
- package/src/services/mate-availability.ts +133 -0
- package/src/services/mate-catalog.ts +265 -0
- package/src/services/models-core.ts +371 -30
- package/src/ui/adapters/modelsAdapter.ts +58 -15
- package/src/ui/components/layout/ScreenLayout.tsx +6 -1
- package/src/ui/renderers/modelRenderers.tsx +413 -108
- package/src/ui/renderers/modelVisuals.tsx +694 -145
- package/src/ui/screens/ModelsScreen.tsx +74 -10
- package/src/ui/state/reducer.ts +20 -0
- package/src/ui/state/types.ts +31 -0
- package/src/ui/theme-mode.ts +128 -14
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import React, { useCallback, useEffect, useMemo } from "react";
|
|
2
|
+
import { areMatesAvailable } from "../../services/mate-availability.js";
|
|
3
|
+
import { loadMateCatalog } from "../../services/mate-catalog.js";
|
|
2
4
|
import {
|
|
3
5
|
applyModelPreset,
|
|
4
6
|
clearModels,
|
|
@@ -72,6 +74,65 @@ export function ModelsScreen() {
|
|
|
72
74
|
|
|
73
75
|
const snapshot = asyncValue(modelsState.data) ?? null;
|
|
74
76
|
|
|
77
|
+
/**
|
|
78
|
+
* Whether the mate slots have anything to serve them.
|
|
79
|
+
*
|
|
80
|
+
* Its OWN effect, deliberately outside the `fetchData` pair above. That pair is two file
|
|
81
|
+
* reads and lands in milliseconds; this one goes through the plugin registry and the
|
|
82
|
+
* marketplace resolution behind it, which does network work with its own timeouts.
|
|
83
|
+
* Awaiting it in the same `Promise.all` would hold the whole screen — the presets, the
|
|
84
|
+
* tier table, the status — on a question that decides three rows.
|
|
85
|
+
*
|
|
86
|
+
* App state rather than `useState`, and NOT only to satisfy the rule: `Router` swaps the
|
|
87
|
+
* component type on a tab change, so a local flag dies on `0 → 1 → 0` and this is the one
|
|
88
|
+
* piece of this screen's state that is expensive to fetch again. Held here, the lookup
|
|
89
|
+
* happens once per session instead of once per visit.
|
|
90
|
+
*
|
|
91
|
+
* It starts false and the rows appear when the answer lands. False is also what a failure
|
|
92
|
+
* yields (`areMatesAvailable` fails closed), so both render identically: the screen
|
|
93
|
+
* exactly as it was before mates existed.
|
|
94
|
+
*/
|
|
95
|
+
useEffect(() => {
|
|
96
|
+
let live = true;
|
|
97
|
+
areMatesAvailable(state.projectPath).then((available) => {
|
|
98
|
+
// The screen can be left while this is in flight. Dispatching into a reducer that
|
|
99
|
+
// outlives the component is harmless, but re-dispatching a stale answer after the
|
|
100
|
+
// project path changed is not.
|
|
101
|
+
if (live) dispatch({ type: "MODELS_MATES_AVAILABLE", available });
|
|
102
|
+
});
|
|
103
|
+
return () => {
|
|
104
|
+
live = false;
|
|
105
|
+
};
|
|
106
|
+
}, [state.projectPath, dispatch]);
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* claudish's live model catalogue, for the staleness advisory.
|
|
110
|
+
*
|
|
111
|
+
* A third effect rather than a branch of the second, even though both are about mates:
|
|
112
|
+
* this one spawns a process and the other reads a registry, and chaining them would make
|
|
113
|
+
* the advisory wait on a plugin lookup it does not depend on. Neither blocks the screen.
|
|
114
|
+
*
|
|
115
|
+
* NOT gated on `matesAvailable`. The catalogue answers "is this bound id real", which is
|
|
116
|
+
* worth knowing about a committed config whether or not this particular machine has the
|
|
117
|
+
* plugin installed — and gating it would make the note appear only on machines that need
|
|
118
|
+
* it least. The lookup is cached per session, so it costs one spawn either way.
|
|
119
|
+
*
|
|
120
|
+
* No project path in the deps: a model catalogue is a property of the world, not of the
|
|
121
|
+
* project. `loadMateCatalog` caches it for the session, so this re-runs into a resolved
|
|
122
|
+
* promise on every remount.
|
|
123
|
+
*/
|
|
124
|
+
useEffect(() => {
|
|
125
|
+
let live = true;
|
|
126
|
+
// Failure is already [] — `loadMateCatalog` never rejects — so there is no `.catch`
|
|
127
|
+
// here to swallow. If one becomes necessary the module's contract has been broken.
|
|
128
|
+
loadMateCatalog().then((catalog) => {
|
|
129
|
+
if (live) dispatch({ type: "MODELS_CATALOG_LOADED", catalog });
|
|
130
|
+
});
|
|
131
|
+
return () => {
|
|
132
|
+
live = false;
|
|
133
|
+
};
|
|
134
|
+
}, [dispatch]);
|
|
135
|
+
|
|
75
136
|
// ── Status line ───────────────────────────────────────────────────────────
|
|
76
137
|
|
|
77
138
|
/**
|
|
@@ -333,6 +394,9 @@ export function ModelsScreen() {
|
|
|
333
394
|
</text>
|
|
334
395
|
) : undefined;
|
|
335
396
|
|
|
397
|
+
// The file's `preset` string is deliberately not shown beside the state: it records what
|
|
398
|
+
// was last APPLIED, so a config edited afterwards would wear a built-in's name here while
|
|
399
|
+
// the list correctly calls it `Custom`.
|
|
336
400
|
const routingState = snapshot?.status.state ?? null;
|
|
337
401
|
const statusContent = (
|
|
338
402
|
<text fg={theme.colors.text}>
|
|
@@ -341,18 +405,13 @@ export function ModelsScreen() {
|
|
|
341
405
|
fg={
|
|
342
406
|
routingState === "on"
|
|
343
407
|
? theme.colors.success
|
|
344
|
-
: routingState === "
|
|
345
|
-
? theme.colors.
|
|
346
|
-
:
|
|
347
|
-
? theme.colors.muted
|
|
348
|
-
: theme.colors.danger
|
|
408
|
+
: routingState === "off" || routingState === null
|
|
409
|
+
? theme.colors.muted
|
|
410
|
+
: theme.colors.danger
|
|
349
411
|
}
|
|
350
412
|
>
|
|
351
413
|
{routingState ?? "reading…"}
|
|
352
414
|
</span>
|
|
353
|
-
{snapshot?.status.preset ? (
|
|
354
|
-
<span fg={theme.colors.info}>{` │ ${snapshot.status.preset}`}</span>
|
|
355
|
-
) : null}
|
|
356
415
|
{modelsState.isApplying ? (
|
|
357
416
|
<span fg={theme.colors.warning}> │ writing…</span>
|
|
358
417
|
) : null}
|
|
@@ -450,7 +509,6 @@ export function ModelsScreen() {
|
|
|
450
509
|
while you are choosing. */}
|
|
451
510
|
{allItems.length > 0 &&
|
|
452
511
|
renderModelsSummary({
|
|
453
|
-
items: allItems,
|
|
454
512
|
selected: selectedItem,
|
|
455
513
|
status: snapshot?.status ?? null,
|
|
456
514
|
configPath: snapshot?.path ?? null,
|
|
@@ -468,7 +526,13 @@ export function ModelsScreen() {
|
|
|
468
526
|
);
|
|
469
527
|
}}
|
|
470
528
|
detailPanel={(detailWidth) =>
|
|
471
|
-
renderModelDetail(
|
|
529
|
+
renderModelDetail(
|
|
530
|
+
selectedItem,
|
|
531
|
+
snapshot?.status ?? null,
|
|
532
|
+
detailWidth,
|
|
533
|
+
modelsState.matesAvailable,
|
|
534
|
+
modelsState.mateCatalog,
|
|
535
|
+
)
|
|
472
536
|
}
|
|
473
537
|
detailKey={selectedItem?.id ?? ""}
|
|
474
538
|
/>
|
package/src/ui/state/reducer.ts
CHANGED
|
@@ -84,6 +84,14 @@ export const initialState: AppState = {
|
|
|
84
84
|
data: { status: "idle" },
|
|
85
85
|
status: null,
|
|
86
86
|
isApplying: false,
|
|
87
|
+
// False until the plugin lookup answers, and false again if it fails. Both cases
|
|
88
|
+
// render the screen as it was before mates existed, which is the honest drawing of
|
|
89
|
+
// "we do not know yet" — see `areMatesAvailable`.
|
|
90
|
+
matesAvailable: false,
|
|
91
|
+
// Empty until the catalogue lands, and empty for good if it cannot be read. Both
|
|
92
|
+
// render identically — no advisory at all — which is exactly the contract
|
|
93
|
+
// `unknownBindings` holds to: say nothing about a catalogue you could not reach.
|
|
94
|
+
mateCatalog: [],
|
|
87
95
|
},
|
|
88
96
|
};
|
|
89
97
|
|
|
@@ -748,6 +756,18 @@ export function appReducer(state: AppState, action: AppAction): AppState {
|
|
|
748
756
|
case "MODELS_APPLY_END":
|
|
749
757
|
return { ...state, models: { ...state.models, isApplying: false } };
|
|
750
758
|
|
|
759
|
+
case "MODELS_MATES_AVAILABLE":
|
|
760
|
+
return {
|
|
761
|
+
...state,
|
|
762
|
+
models: { ...state.models, matesAvailable: action.available },
|
|
763
|
+
};
|
|
764
|
+
|
|
765
|
+
case "MODELS_CATALOG_LOADED":
|
|
766
|
+
return {
|
|
767
|
+
...state,
|
|
768
|
+
models: { ...state.models, mateCatalog: action.catalog },
|
|
769
|
+
};
|
|
770
|
+
|
|
751
771
|
// =========================================================================
|
|
752
772
|
// Modals
|
|
753
773
|
// =========================================================================
|
package/src/ui/state/types.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { CatalogModel } from "../../services/mate-catalog.js";
|
|
1
2
|
import type { ModelsConfig, ModelsStatus } from "../../services/models-core.js";
|
|
2
3
|
import type { PluginInfo } from "../../services/plugin-manager.js";
|
|
3
4
|
import type {
|
|
@@ -286,6 +287,33 @@ export interface ModelsScreenState {
|
|
|
286
287
|
* two of those running at once would interleave writes to the same files.
|
|
287
288
|
*/
|
|
288
289
|
isApplying: boolean;
|
|
290
|
+
/**
|
|
291
|
+
* Is `multimodel@magus` installed — i.e. is there anything to serve a mate?
|
|
292
|
+
*
|
|
293
|
+
* App state for the reason the two fields above are, plus one of its own. The screen
|
|
294
|
+
* unmounts on a tab switch, so a local flag would be destroyed by `0 → 1 → 0`; and unlike
|
|
295
|
+
* the two file reads the screen does on mount, THIS answer costs a marketplace resolution
|
|
296
|
+
* with network timeouts behind it. Re-asking it on every visit to the tab would pay that
|
|
297
|
+
* cost again for an answer that cannot change while the TUI is open.
|
|
298
|
+
*
|
|
299
|
+
* Starts false, which is also what a failed lookup yields (`areMatesAvailable` fails
|
|
300
|
+
* closed): both render as the screen exactly as it was before mates existed, rather than
|
|
301
|
+
* as three rows put there by an error.
|
|
302
|
+
*/
|
|
303
|
+
matesAvailable: boolean;
|
|
304
|
+
/**
|
|
305
|
+
* claudish's live model catalogue, or [] when it could not be read.
|
|
306
|
+
*
|
|
307
|
+
* App state for the reasons above, plus the one that matters most here: reading it spawns
|
|
308
|
+
* a process. Held per session, it is spawned once; held in the component it would be
|
|
309
|
+
* spawned on every visit to the tab.
|
|
310
|
+
*
|
|
311
|
+
* [] is deliberately NOT distinguished from "not yet loaded". Every consumer treats an
|
|
312
|
+
* empty catalogue as "unknown" and says nothing, so the loading frame, a machine without
|
|
313
|
+
* claudish, and a lookup that timed out all draw the same screen — bound ids as written,
|
|
314
|
+
* with no annotation. Advisory only: nothing here ever changes what is routed.
|
|
315
|
+
*/
|
|
316
|
+
mateCatalog: CatalogModel[];
|
|
289
317
|
}
|
|
290
318
|
|
|
291
319
|
// ============================================================================
|
|
@@ -467,6 +495,9 @@ export type AppAction =
|
|
|
467
495
|
| { type: "MODELS_STATUS_CLEAR" }
|
|
468
496
|
| { type: "MODELS_APPLY_START" }
|
|
469
497
|
| { type: "MODELS_APPLY_END" }
|
|
498
|
+
// Dispatched once, when the plugin lookup behind the mate slots lands.
|
|
499
|
+
| { type: "MODELS_MATES_AVAILABLE"; available: boolean }
|
|
500
|
+
| { type: "MODELS_CATALOG_LOADED"; catalog: CatalogModel[] }
|
|
470
501
|
|
|
471
502
|
// Data refresh - triggers screens to refetch
|
|
472
503
|
| { type: "DATA_REFRESH_COMPLETE" };
|
package/src/ui/theme-mode.ts
CHANGED
|
@@ -102,11 +102,32 @@ export const COMPONENT_BADGE = {
|
|
|
102
102
|
export type ComponentBadgeKind = keyof typeof COMPONENT_BADGE;
|
|
103
103
|
|
|
104
104
|
/**
|
|
105
|
-
* Badge and chart colours for the four Claude models,
|
|
105
|
+
* Badge and chart colours for the four Claude models, the three external-model slots, and
|
|
106
|
+
* `inherit`.
|
|
106
107
|
*
|
|
107
|
-
*
|
|
108
|
+
* Four strengths, because a badge and a bar cannot share an ink: twenty characters of a
|
|
108
109
|
* colour weigh far more than six, so what reads as a label reads as a slab when it is a
|
|
109
|
-
* chart. `bg`/`fg` are the chip; `bar` is the distribution bar's fill
|
|
110
|
+
* chart. `bg`/`fg` are the chip; `bar` is the distribution bar's fill; `label` is the ink a
|
|
111
|
+
* word takes when it is written ON that fill.
|
|
112
|
+
*
|
|
113
|
+
* ## Why `label` exists, and why `fg` could not do its job
|
|
114
|
+
*
|
|
115
|
+
* A segment of the workflow chart is one block of one colour with its model's name written
|
|
116
|
+
* on it. The name used to be drawn as the CHIP — `fg` on `bg`, inset into the bar — which
|
|
117
|
+
* put a second, visibly different rectangle inside a block that is supposed to read as one
|
|
118
|
+
* thing. Reported exactly that way: "do not add additional background colour to model
|
|
119
|
+
* segments".
|
|
120
|
+
*
|
|
121
|
+
* Deleting the chip and keeping `fg` was not an option: MEASURED, `fg` on the same model's
|
|
122
|
+
* `bar` runs 1.74:1 (sonnet dark) to 2.99:1 (haiku dark), which is a smudge rather than a
|
|
123
|
+
* word. `fg` is built to sit on `bg`, and `bg` is not what a bar is painted with.
|
|
124
|
+
*
|
|
125
|
+
* So each model owns a fourth ink, chosen against its OWN bar and nothing else. Every one is
|
|
126
|
+
* the model's own hue taken deep — the same family, not a fifth palette — and every one
|
|
127
|
+
* clears 4.5:1 with margin; the measured value is on each line below. `inherit` is the one
|
|
128
|
+
* that runs the other way: its bar is a dark recessive grey rather than a bright mid-tone,
|
|
129
|
+
* so its label is near-white on the dark page. That is forced by the fill, not a stylistic
|
|
130
|
+
* exception.
|
|
110
131
|
*
|
|
111
132
|
* ## The two pages are built differently, deliberately
|
|
112
133
|
*
|
|
@@ -129,29 +150,120 @@ export type ComponentBadgeKind = keyof typeof COMPONENT_BADGE;
|
|
|
129
150
|
*
|
|
130
151
|
* Hues stay far apart for the same reason the scope squares' do: one or two characters of
|
|
131
152
|
* colour carry no readable hue SHIFT, only a readable hue DIFFERENCE.
|
|
153
|
+
*
|
|
154
|
+
* ## The mates are ONE hue family, and that is the exception that proves the rule
|
|
155
|
+
*
|
|
156
|
+
* `mate1`, `mate2` and `kangaroo` are three different external models, so they need three
|
|
157
|
+
* distinguishable inks — but they all mean the same thing (someone else's model, reached
|
|
158
|
+
* through claudish), and four unrelated hues would say the opposite. They therefore share a
|
|
159
|
+
* warm outback band nothing else on this screen occupies: measured hue angles 39° / 23° / 6°
|
|
160
|
+
* on the dark page and 41° / 25° / 10° on the light one — ochre, terracotta, red earth. The
|
|
161
|
+
* four Claude models sit at teal 190°, green 150°, purple 265° and grey, so a mate cannot be
|
|
162
|
+
* mistaken for one of them at a glance, and a mate cannot be mistaken for another mate
|
|
163
|
+
* either, because ~17° of hue at full saturation is a readable difference where ~17° of
|
|
164
|
+
* LUMINANCE would not be.
|
|
165
|
+
*
|
|
166
|
+
* Measured contrast, ink on its own fill (the number the chip has to clear):
|
|
167
|
+
*
|
|
168
|
+
* mate1 light 5.60:1 dark 4.82:1
|
|
169
|
+
* mate2 light 5.82:1 dark 4.96:1
|
|
170
|
+
* kangaroo light 5.98:1 dark 5.68:1
|
|
171
|
+
*
|
|
172
|
+
* And the dark fills against every plausible dark ground — pure black,
|
|
173
|
+
* `CONTRAST_REFERENCE_KEYS.dark`, One Dark's `#282C34` — plus the cream page a mis-detected
|
|
174
|
+
* theme would put them on:
|
|
175
|
+
*
|
|
176
|
+
* mate1 4.09 / 3.32 / 2.73 cream 4.81
|
|
177
|
+
* mate2 3.83 / 3.10 / 2.55 cream 5.14
|
|
178
|
+
* kangaroo 3.31 / 2.68 / 2.21 cream 5.94
|
|
179
|
+
*
|
|
180
|
+
* which is the same band the four Claude fills occupy, so a mate chip carries the same weight
|
|
181
|
+
* as the models beside it rather than reading as a second-class one.
|
|
182
|
+
*
|
|
183
|
+
* There is a FOURTH warm entry, `mates`, and it is not a slot: it is the family's own ink,
|
|
184
|
+
* worn by the one collapsed segment a workflow bar draws for all external work at once. Its
|
|
185
|
+
* own note below says why a slot's ink could not do that job.
|
|
132
186
|
*/
|
|
133
187
|
export const MODEL_BADGE = {
|
|
134
188
|
opus: {
|
|
135
|
-
|
|
136
|
-
|
|
189
|
+
/** label 4.88:1 on its bar */
|
|
190
|
+
light: { bg: "#D3E7EC", fg: "#0E5F73", bar: "#92CEDC", label: "#125463" },
|
|
191
|
+
/** label 4.93:1 on its bar */
|
|
192
|
+
dark: { bg: "#0E7C94", fg: "#F0FDFF", bar: "#48C8E0", label: "#044B58" },
|
|
137
193
|
},
|
|
138
194
|
sonnet: {
|
|
139
|
-
|
|
140
|
-
|
|
195
|
+
/** label 4.84:1 on its bar */
|
|
196
|
+
light: { bg: "#D6EEDD", fg: "#166534", bar: "#93D9B2", label: "#145D34" },
|
|
197
|
+
/** label 4.84:1 on its bar */
|
|
198
|
+
dark: { bg: "#12804F", fg: "#EFFFF6", bar: "#4FD98F", label: "#07572C" },
|
|
141
199
|
},
|
|
142
200
|
haiku: {
|
|
143
|
-
|
|
144
|
-
|
|
201
|
+
/** label 4.81:1 on its bar */
|
|
202
|
+
light: { bg: "#E6E6E0", fg: "#4B5563", bar: "#C9CCD2", label: "#4B5362" },
|
|
203
|
+
/** label 4.91:1 on its bar — the dark grey bar is the dimmest of the four, so
|
|
204
|
+
* its label is the closest to black of them. */
|
|
205
|
+
dark: { bg: "#697180", fg: "#F8FAFC", bar: "#8A92A2", label: "#21252E" },
|
|
145
206
|
},
|
|
146
207
|
fable: {
|
|
147
|
-
|
|
148
|
-
|
|
208
|
+
/** label 4.86:1 on its bar */
|
|
209
|
+
light: { bg: "#E9DCF5", fg: "#6B21A8", bar: "#C3A6EC", label: "#520FB0" },
|
|
210
|
+
/** label 4.81:1 on its bar */
|
|
211
|
+
dark: { bg: "#7B4CD6", fg: "#F6F0FF", bar: "#A87BE8", label: "#310570" },
|
|
212
|
+
},
|
|
213
|
+
/** Ochre. The first external slot, and the lightest step of the warm family.
|
|
214
|
+
* label 4.87:1 light / 4.81:1 dark, each on its own bar. */
|
|
215
|
+
mate1: {
|
|
216
|
+
light: { bg: "#F6E6C4", fg: "#7A5210", bar: "#E2C27A", label: "#654909" },
|
|
217
|
+
dark: { bg: "#926511", fg: "#FFF7E8", bar: "#D8A445", label: "#533807" },
|
|
218
|
+
},
|
|
219
|
+
/** Terracotta — the middle step, a clear ~17° of hue from either neighbour.
|
|
220
|
+
* label 4.80:1 light / 4.83:1 dark, each on its own bar. */
|
|
221
|
+
mate2: {
|
|
222
|
+
light: { bg: "#F7DDCB", fg: "#8A3E12", bar: "#EAB08A", label: "#793406" },
|
|
223
|
+
dark: { bg: "#A8501B", fg: "#FFF1E8", bar: "#E08A52", label: "#552404" },
|
|
224
|
+
},
|
|
225
|
+
/** Red earth. The third slot wears the reddest step, and it is still nowhere near
|
|
226
|
+
* `theme.colors.danger` — this is a desaturated earth tone, not a warning.
|
|
227
|
+
* label 4.86:1 light / 4.82:1 dark, each on its own bar. */
|
|
228
|
+
kangaroo: {
|
|
229
|
+
light: { bg: "#F6D6D0", fg: "#8E2F26", bar: "#E5A096", label: "#7B1C0E" },
|
|
230
|
+
dark: { bg: "#A83A2E", fg: "#FFEFEC", bar: "#E07A6C", label: "#571107" },
|
|
231
|
+
},
|
|
232
|
+
/**
|
|
233
|
+
* THE FAMILY, not a fourth slot — the ink of the collapsed `mates` bar segment.
|
|
234
|
+
*
|
|
235
|
+
* A workflow bar draws every mate-routed agent as ONE run, because the question a bar
|
|
236
|
+
* answers is "how much of this workflow leaves Claude Code" and that is one number. That
|
|
237
|
+
* run cannot wear a slot's ink: painting a two-slot segment in `mate1`'s ochre says the
|
|
238
|
+
* work went to `mate1`, and painting it in whichever slot contributes most makes the same
|
|
239
|
+
* segment change colour when a count changes — the identical label in two hues down one
|
|
240
|
+
* chart. So the family gets an ink of its own: honey amber, hue 35° light / 31° dark.
|
|
241
|
+
*
|
|
242
|
+
* It does NOT try to be a fourth distinguishable step, and it could not be. The warm band
|
|
243
|
+
* is 0–50° and already holds three slots at ~17° spacing, so the widest gap left is about
|
|
244
|
+
* 11° — under the separation the slots themselves needed. It does not have to be: the
|
|
245
|
+
* family tone and the slot tones never appear in ONE graphic. The chart and the colour key
|
|
246
|
+
* under it use only this; the tier table and the agent table use only the slots.
|
|
247
|
+
*
|
|
248
|
+
* MEASURED, same battery as the slots above:
|
|
249
|
+
* label on its own bar light 4.97:1 dark 4.78:1
|
|
250
|
+
* fg on its own bg light 5.87:1 dark 4.83:1
|
|
251
|
+
* dark bg vs grounds 4.03 (black) / 3.27 (#1C1C1E) / 2.69 (#282C34) cream 4.88
|
|
252
|
+
*/
|
|
253
|
+
mates: {
|
|
254
|
+
light: { bg: "#F6E3C9", fg: "#80480F", bar: "#E5B77F", label: "#6B3D07" },
|
|
255
|
+
dark: { bg: "#9E5D16", fg: "#FFF5E8", bar: "#DB9A4C", label: "#553106" },
|
|
149
256
|
},
|
|
150
257
|
/** Not a model — the absence of a routing decision. Grey on both pages, so it never
|
|
151
|
-
* reads as
|
|
258
|
+
* reads as another choice sitting beside the real ones. A mate is NOT this: a mate is
|
|
259
|
+
* a decision to route elsewhere, which is why it gets a colour and this does not.
|
|
260
|
+
*
|
|
261
|
+
* Its label is a NEUTRAL grey at 4.82:1 light / 4.83:1 dark, and on the dark page it
|
|
262
|
+
* is the only label that is pale rather than deep — this is the one bar that is itself
|
|
263
|
+
* dark, so nothing darker could be read on it. */
|
|
152
264
|
inherit: {
|
|
153
|
-
light: { bg: "#EFEFE6", fg: "#8A8A80", bar: "#DEDED2" },
|
|
154
|
-
dark: { bg: "#4A4A54", fg: "#D0D2D8", bar: "#4E4E58" },
|
|
265
|
+
light: { bg: "#EFEFE6", fg: "#8A8A80", bar: "#DEDED2", label: "#5E5E57" },
|
|
266
|
+
dark: { bg: "#4A4A54", fg: "#D0D2D8", bar: "#4E4E58", label: "#C6C6CA" },
|
|
155
267
|
},
|
|
156
268
|
} as const;
|
|
157
269
|
|
|
@@ -161,6 +273,8 @@ export interface ModelInk {
|
|
|
161
273
|
bg: string;
|
|
162
274
|
fg: string;
|
|
163
275
|
bar: string;
|
|
276
|
+
/** Ink for a word written ON `bar`. Never `fg` — see the note above `MODEL_BADGE`. */
|
|
277
|
+
label: string;
|
|
164
278
|
}
|
|
165
279
|
|
|
166
280
|
/**
|