Files
clawmates/frontend/src/components/world/palette.ts
T
Omar SobhandClaude Opus 5.5 fca828b5a1
deploy / test (push) Successful in 4m55s
deploy / build (push) Successful in 1m54s
feat(self-audit): a self_audit recipe carries the brief continuous_improvement never had
Role system_prompts are inert on mission turns, so a team without a recipe has
no default brief and every caller must write one (v2 wrote the wrong one: 0/50
calls read the record). The recipe points the phase at
/mission/memory/PROJECT-MEMORY.md, requires verbatim quotes only where a
pattern is reported, and names the clean-record and no-record outcomes as
passes — the v3 condition failed a correct clean audit for paraphrasing.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-22 17:25:36 -05:00

123 lines
4.0 KiB
TypeScript

// Colour, keyed on what the mission is FOR.
//
// A benchmark run and a security sweep were rendered in the same palette as a
// research mission, so the only thing distinguishing them on screen was the
// label. Colour is the cheapest signal a viewer reads before any text, and it
// was carrying no information.
//
// This also collapses two uncoordinated kind→colour maps that had drifted
// apart: `LEVEL_COLOR` in engine.ts (node colour by tier) and the `fireColor`
// if-chain in `onTouch` (beam colour by id prefix). They answered the same
// question in different places with different answers.
import type { Tier } from "./engine";
export type PhaseKind = "research" | "coding" | "benchmark" | "security_scan";
export interface Palette {
/** The mission landmark at the centre. */
mission: string;
/** Station colour per phase kind. */
phase: Record<PhaseKind, string>;
dir: string;
file: string;
tool: string;
finding: string;
comm: { message: string; delegate: string; room: string; web: string };
}
/** The default — what the World looked like before palettes, so an unrecognised
* template renders exactly as it always did rather than as something odd. */
const BASE: Palette = {
mission: "#ff8a7a",
phase: {
research: "#c98af0",
coding: "#8ec5ff",
benchmark: "#f0b866",
security_scan: "#ff5f57",
},
dir: "#5ec8d8",
file: "#5ec8d8",
tool: "#5ec8d8",
finding: "#ff8a7a",
comm: { message: "#ff5eae", delegate: "#ff8a7a", room: "#c98af0", web: "#5ec8d8" },
};
/** Measurement work reads amber/green — instrument tones, not creative ones. */
const BENCHMARK: Palette = {
...BASE,
mission: "#f0b866",
phase: { ...BASE.phase, benchmark: "#5fd08a", coding: "#e8b465" },
dir: "#a8b06a",
file: "#d8c06a",
tool: "#5fd08a",
comm: { ...BASE.comm, web: "#a8b06a" },
};
/** Adversarial work reads crimson/steel. */
const SECURITY: Palette = {
...BASE,
mission: "#ff5f57",
phase: { ...BASE.phase, security_scan: "#ff5f57", coding: "#8a9af0" },
dir: "#7a8a9a",
file: "#9aa8b8",
tool: "#ff8a7a",
finding: "#ff3b30",
comm: { ...BASE.comm, web: "#7a8a9a" },
};
/** Reading and mapping an existing codebase — cooler, quieter. */
const REFACTOR: Palette = {
...BASE,
mission: "#6fd0c0",
phase: { ...BASE.phase, coding: "#6fd0c0" },
dir: "#4aa3b8",
file: "#6fd0c0",
comm: { ...BASE.comm, web: "#4aa3b8" },
};
/** Pure research — violet and sky, the existing landmark tones. */
const RESEARCH: Palette = {
...BASE,
mission: "#c98af0",
phase: { ...BASE.phase, research: "#c98af0" },
dir: "#8a9af0",
file: "#8ec5ff",
};
const PALETTES: Record<string, Palette> = {
research_only: RESEARCH,
research_and_code: BASE,
security_hardening: SECURITY,
refactor: REFACTOR,
benchmark: BENCHMARK,
self_audit: RESEARCH,
custom: BASE,
};
export function paletteFor(templateKind?: string | null): Palette {
return (templateKind && PALETTES[templateKind]) || BASE;
}
/** Node colour for an id + tier. Replaces `LEVEL_COLOR[tier]` for everything a
* mission scope draws; structural tiers keep their own constants. */
export function colorFor(p: Palette, nodeId: string, tier: Tier): string | undefined {
if (tier === "mission") return p.mission;
if (nodeId.startsWith("finding:")) return p.finding;
if (nodeId.startsWith("dir:")) return p.dir;
if (nodeId.startsWith("file:")) return p.file;
if (nodeId.startsWith("tool:")) return p.tool;
// `phase` deliberately absent: a station's colour comes from its KIND, which
// the node id does not carry. `applyMissionPlan` passes it explicitly.
return undefined;
}
/** Beam colour when a pawn touches a node — the old `fireColor` if-chain. */
export function fireFor(p: Palette, nodeId: string, weight: number): string | undefined {
if (nodeId.startsWith("file:")) return weight >= 0.9 ? p.file : p.tool;
if (nodeId.startsWith("tool:")) return p.tool;
if (nodeId.startsWith("phase:")) return p.mission;
if (nodeId.startsWith("finding:")) return p.finding;
return undefined;
}