Commit 4188c89d2514

Vincent Demeester <vincent@sbr.pm>
2026-02-18 14:05:41
feat(dots): mode colors from modes.json with hex values
Mode colors are now defined in modes.json as hex values instead of hardcoded theme tokens. Uses 24-bit ANSI color for universal rendering on both light and dark themes. Colors: fast (#2e8b57 sea green), code-work (#b45309 amber). Modes without a color fall back to the dim theme token.
1 parent e84774d
Changed files (3)
dots/pi/agent/extensions/custom-footer.ts
@@ -227,16 +227,21 @@ function getThinkingInfo(pi: any): { emoji: string; level: string } | null {
 // Mode Display
 // =============================================================================
 
-/** Color token for each mode name. Falls back to "dim" for unknown modes. */
-const MODE_COLORS: Record<string, string> = {
-	"fast": "success",
-	"code-work": "warning",
-	"custom": "dim",
-};
+/** Convert hex color (#rrggbb) to 24-bit ANSI foreground escape sequence */
+function hexToAnsi(hex: string): string {
+	const h = hex.replace("#", "");
+	const r = parseInt(h.substring(0, 2), 16);
+	const g = parseInt(h.substring(2, 4), 16);
+	const b = parseInt(h.substring(4, 6), 16);
+	return `\x1b[38;2;${r};${g};${b}m`;
+}
 
-function getModeText(modeName: string, theme: any): string {
-	const color = MODE_COLORS[modeName] || "accent";
-	return theme.fg(color, `[${modeName}]`);
+function getModeText(modeName: string, hexColor: string | undefined, theme: any): string {
+	const label = `[${modeName}]`;
+	if (hexColor && /^#[0-9a-fA-F]{6}$/.test(hexColor)) {
+		return `${hexToAnsi(hexColor)}${label}\x1b[0m`;
+	}
+	return theme.fg("dim", label);
 }
 
 // =============================================================================
@@ -352,11 +357,12 @@ export default function (pi: ExtensionAPI) {
 					
 					// Extract mode separately for custom positioning and coloring
 					const modeRaw = extensionStatuses.get("mode");
-					const modeText = modeRaw ? getModeText(modeRaw, theme) : "";
-					
-					// Remaining statuses (exclude mode)
+					const modeColor = extensionStatuses.get("mode-color");
+					const modeText = modeRaw ? getModeText(modeRaw, modeColor, theme) : "";
+
+					// Remaining statuses (exclude mode internals)
 					const statusTexts = Array.from(extensionStatuses.entries())
-						.filter(([key, val]: [string, string]) => key !== "mode" && Boolean(val))
+						.filter(([key, val]: [string, string]) => key !== "mode" && key !== "mode-color" && Boolean(val))
 						.map(([, val]: [string, string]) => val);
 
 					// Combine components with separators
@@ -448,9 +454,10 @@ export default function (pi: ExtensionAPI) {
 					// Extension statuses
 					const extensionStatuses = footerData.getExtensionStatuses?.() || new Map();
 					const modeRaw = extensionStatuses.get("mode");
-					const modeText = modeRaw ? getModeText(modeRaw, theme) : "";
+					const modeColor = extensionStatuses.get("mode-color");
+					const modeText = modeRaw ? getModeText(modeRaw, modeColor, theme) : "";
 					const statusTexts = Array.from(extensionStatuses.entries())
-						.filter(([key, val]: [string, string]) => key !== "mode" && Boolean(val))
+						.filter(([key, val]: [string, string]) => key !== "mode" && key !== "mode-color" && Boolean(val))
 						.map(([, val]: [string, string]) => val);
 					// Combine components with separators
 					// Format: 16:10  🖥️ kyushu  ~/s/home  main  google-vertex/sonnet-4.5  🧠 ext  R11M W313k $5.289  76.2%/200k (auto)
dots/pi/agent/extensions/prompt-editor.ts
@@ -448,14 +448,17 @@ const runtime: ModeRuntime = {
 let requestEditorRender: (() => void) | undefined;
 
 // Update the mode status in the footer via ctx.ui.setStatus
-// Sets raw mode name — custom-footer handles formatting and color
+// Sets "mode" (name) and "mode-color" (hex color from modes.json)
 function updateModeStatus(ctx: ExtensionContext): void {
 	if (!ctx.hasUI) return;
 	const mode = runtime.currentMode;
 	if (mode && mode !== "default") {
+		const spec = runtime.data.modes[mode];
 		ctx.ui.setStatus("mode", mode);
+		ctx.ui.setStatus("mode-color", spec?.color || undefined);
 	} else {
 		ctx.ui.setStatus("mode", undefined);
+		ctx.ui.setStatus("mode-color", undefined);
 	}
 }
 
dots/pi/agent/modes.json
@@ -10,12 +10,14 @@
     "fast": {
       "provider": "google-vertex-claude",
       "modelId": "claude-sonnet-4-5@20250929",
-      "thinkingLevel": "off"
+      "thinkingLevel": "off",
+      "color": "#2e8b57"
     },
     "code-work": {
       "provider": "google-vertex-claude",
       "modelId": "claude-opus-4-6",
-      "thinkingLevel": "low"
+      "thinkingLevel": "low",
+      "color": "#b45309"
     }
   }
 }