# Signal System Redesign Implementation Plan

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** Migrate ZoneGaze Flow to the approved hard Signal design system across the shared web app, Mac Electron launchpad, and Android native shell while leaving upstream-compatible Cline code untouched.

**Architecture:** Preserve the existing app architecture and change the visual system through shared tokens, existing UI primitives, platform shell styles, and platform token mirrors. The web app remains the main product surface; Mac and Android shells align through their own native/static token layers. The plan intentionally avoids `flow/kanban-core` and runtime Cline SDK behavior.

**Tech Stack:** React, Vite, Tailwind CSS v4, Radix UI, Lucide React, Vitest, Electron static shell CSS/JS, Kotlin Android native views, Gradle/JUnit, Playwright/Chrome screenshot verification.

---

## File Structure

Create:
- `flow/web/src/hooks/use-theme.test.ts` - unit coverage for Signal theme ids, legacy theme fallback, DOM theme application, and terminal colors.
- `flow/web/src/components/ui/dialog.test.tsx` - regression coverage for accessible dialog close controls.

Modify:
- `flow/web/src/styles/globals.css` - map existing Tailwind theme tokens to Signal values, remove broad non-Signal theme overrides, keep high-contrast dark if retained, update dialog/board/detail shell CSS that currently uses hardcoded blue/gray border values.
- `flow/web/src/hooks/use-theme.ts` - reduce the theme model to Signal default plus the retained accessibility variant, make legacy stored theme ids fall back to Signal, update terminal color lookup.
- `flow/web/src/terminal/theme-colors.ts` - update static default terminal colors to Signal for existing tests and consumers.
- `flow/web/src/main.tsx` - keep first-paint theme logic but make legacy saved themes harmless.
- `flow/web/public/manifest.json` - update PWA background and theme colors to Signal canvas/panel values.
- `flow/web/src/pwa/pwa-assets.test.ts` - update manifest color assertions.
- `flow/web/src/components/runtime-settings-dialog.tsx` - retire the broad theme picker and replace it with a read-only Signal system appearance block plus existing layout reset controls.
- `flow/web/src/components/runtime-settings-dialog.test.tsx` - replace theme picker tests with Signal appearance and legacy theme fallback tests.
- `flow/web/src/components/ui/button.tsx` - ensure variants read correctly with Signal tokens and retain accessible focus.
- `flow/web/src/components/ui/dialog.tsx` - replace hardcoded dialog border colors with tokenized borders and add `aria-label="Close dialog"` to the close button.
- `flow/web/src/data/column-colors.ts` - remap board lane colors to Signal semantic colors and keep `in_progress` as the only lane using the primary Signal accent.
- `flow/web/src/components/board-card.tsx`, `flow/web/src/components/board-column.tsx`, `flow/web/src/components/card-detail-view.tsx`, `flow/web/src/components/project-navigation-panel.tsx`, `flow/web/src/components/top-bar.tsx`, `flow/web/src/components/startup-onboarding-dialog.tsx` - only where tokenized visual polish, semantic color discipline, or accessible-label fixes are required after the shared token change.
- `platforms/desktop/src/desktop-shell.css` - replace blue shell variables and button styles with Signal tokens.
- `platforms/desktop/src/dashboard.css` - remove blue gradients, blue active nav, and blue primary button treatment in favor of flat Signal panels.
- `platforms/desktop/src/dashboard.html` - add or confirm accessible labels where static launchpad icon actions need them.
- `platforms/desktop/test/desktop-shell-assets.test.ts` - add static assertions for Signal token values and absence of old blue primary shell tokens.
- `platforms/android/app/src/main/java/com/zonegazeflow/app/ZoneUi.kt` - mirror Signal tokens in native colors and reduce default surface depth.
- `platforms/android/app/src/main/java/com/zonegazeflow/app/DashboardBottomNavigationView.kt`, `ServerDashboardView.kt`, `RuntimeStatusPanel.kt`, `DashboardOverviewSection.kt`, `ServerDashboardListRenderer.kt`, `TermuxDashboardSection.kt`, `WebActivity.kt` - adjust only as needed where the existing code assumes older blue/violet or heavy elevation behavior.
- `platforms/android/app/src/test/java/com/zonegazeflow/app/AndroidShellDesignSourceTest.kt` - update design-source assertions for Signal tokens, centralized colors, and reduced elevation.

Do not modify:
- `flow/kanban-core/**`
- `flow/runtime/src/cline-sdk/**`
- Upstream Cline SDK package files
- Runtime/server behavior files unless a visual test proves the shell cannot render correctly without a scoped accessibility or visual-only adjustment

---

### Task 1: Web Signal Theme Model

**Files:**
- Create: `flow/web/src/hooks/use-theme.test.ts`
- Modify: `flow/web/src/hooks/use-theme.ts`
- Modify: `flow/web/src/terminal/theme-colors.ts`
- Modify: `flow/web/src/main.tsx`

- [ ] **Step 1: Write failing theme tests**

Create `flow/web/src/hooks/use-theme.test.ts` with these tests:

```ts
import { afterEach, describe, expect, it } from "vitest";
import {
	applyThemeToDocument,
	getTerminalThemeColors,
	isThemeId,
	readStoredThemeId,
	SIGNAL_THEME_COLORS,
	THEMES,
} from "@/hooks/use-theme";

describe("Signal theme model", () => {
	afterEach(() => {
		window.localStorage.clear();
		document.documentElement.removeAttribute("data-theme");
	});

	it("keeps Signal as the default design system theme", () => {
		expect(THEMES).toEqual([
			{
				id: "default",
				label: "Signal",
				group: "signal",
				accent: "#c6f24e",
				accent2: "#b48ef0",
				surface: "#0a0a0b",
				accentFg: "#0a0a0b",
				accent2Fg: "#0a0a0b",
			},
			{
				id: "high-contrast-dark",
				label: "High Contrast",
				group: "accessibility",
				accent: "#ffffff",
				accent2: "#b48ef0",
				surface: "#000000",
				accentFg: "#000000",
				accent2Fg: "#000000",
			},
		]);
		expect(SIGNAL_THEME_COLORS.signal).toBe("#c6f24e");
		expect(SIGNAL_THEME_COLORS.canvas).toBe("#0a0a0b");
	});

	it("rejects retired theme ids and falls back stored legacy values to Signal", () => {
		expect(isThemeId("graphite")).toBe(false);
		expect(isThemeId("light")).toBe(false);
		window.localStorage.setItem("kanban.theme", "graphite");
		expect(readStoredThemeId()).toBe("default");
	});

	it("applies no document attribute for Signal and keeps one for high contrast", () => {
		applyThemeToDocument("default");
		expect(document.documentElement.getAttribute("data-theme")).toBeNull();
		applyThemeToDocument("high-contrast-dark");
		expect(document.documentElement.getAttribute("data-theme")).toBe("high-contrast-dark");
	});

	it("returns Signal terminal colors for the default theme", () => {
		expect(getTerminalThemeColors("default")).toEqual({
			textPrimary: "#f5f5f5",
			surfacePrimary: "#0a0a0b",
			surfaceRaised: "#0f1013",
			selectionBackground: "#c6f24e33",
			selectionForeground: "#0a0a0b",
			selectionInactiveBackground: "#13141899",
			isLightBackground: false,
		});
	});
});
```

- [ ] **Step 2: Run theme tests and verify failure**

Run:

```bash
npm --prefix flow/web run test -- src/hooks/use-theme.test.ts
```

Expected: FAIL because `SIGNAL_THEME_COLORS` does not exist, retired theme ids are still accepted, and terminal colors still use the old blue/gray palette.

- [ ] **Step 3: Implement the Signal theme model**

In `flow/web/src/hooks/use-theme.ts`, replace the broad theme union and theme arrays with the Signal/accessibility model:

```ts
export const SIGNAL_THEME_COLORS = {
	canvas: "#0a0a0b",
	canvas2: "#0c0c0e",
	panel: "#0f1013",
	panel2: "#131418",
	border: "#1d1f24",
	border2: "#22242a",
	border3: "#2a2d33",
	ink: "#f5f5f5",
	ink2: "#b6bcc6",
	muted: "#8a8f98",
	faint: "#4c5058",
	signal: "#c6f24e",
	signalHover: "#d4f76f",
	signalFg: "#0a0a0b",
	positive: "#7ec95e",
	negative: "#e0655f",
	warning: "#e0b44e",
	info: "#5aa8e0",
	violet: "#b48ef0",
} as const;

export type ThemeId = "default" | "high-contrast-dark";
export type ThemeGroup = "signal" | "accessibility";

export const THEMES: readonly ThemeDefinition[] = [
	{
		id: "default",
		label: "Signal",
		group: "signal",
		accent: SIGNAL_THEME_COLORS.signal,
		accent2: SIGNAL_THEME_COLORS.violet,
		surface: SIGNAL_THEME_COLORS.canvas,
		accentFg: SIGNAL_THEME_COLORS.signalFg,
		accent2Fg: SIGNAL_THEME_COLORS.signalFg,
	},
	{
		id: "high-contrast-dark",
		label: "High Contrast",
		group: "accessibility",
		accent: "#ffffff",
		accent2: SIGNAL_THEME_COLORS.violet,
		surface: "#000000",
		accentFg: "#000000",
		accent2Fg: "#000000",
	},
] as const;

export const THEME_GROUPS: readonly { key: ThemeGroup; label: string }[] = [
	{ key: "signal", label: "Design System" },
	{ key: "accessibility", label: "Accessibility" },
];
```

Replace `TERMINAL_COLORS_BY_THEME` with:

```ts
const TERMINAL_COLORS_BY_THEME: Record<ThemeId, ThemeTerminalColors> = {
	default: {
		textPrimary: SIGNAL_THEME_COLORS.ink,
		surfacePrimary: SIGNAL_THEME_COLORS.canvas,
		surfaceRaised: SIGNAL_THEME_COLORS.panel,
		selectionBackground: `${SIGNAL_THEME_COLORS.signal}33`,
		selectionForeground: SIGNAL_THEME_COLORS.signalFg,
		selectionInactiveBackground: `${SIGNAL_THEME_COLORS.panel2}99`,
		isLightBackground: false,
	},
	"high-contrast-dark": {
		textPrimary: "#ffffff",
		surfacePrimary: "#000000",
		surfaceRaised: "#0a0a0a",
		selectionBackground: "#ffffff4d",
		selectionForeground: "#000000",
		selectionInactiveBackground: "#1a1a1a99",
		isLightBackground: false,
	},
};
```

Keep `readStoredThemeId()` as:

```ts
export function readStoredThemeId(): ThemeId {
	const stored = readLocalStorageItem(LocalStorageKey.Theme);
	return isThemeId(stored) ? stored : "default";
}
```

In `flow/web/src/terminal/theme-colors.ts`, update `TERMINAL_THEME_COLORS` to:

```ts
export const TERMINAL_THEME_COLORS = {
	textPrimary: "#f5f5f5",
	surfacePrimary: "#0a0a0b",
	surfaceRaised: "#0f1013",
	selectionBackground: "#c6f24e33",
	selectionForeground: "#0a0a0b",
	selectionInactiveBackground: "#13141899",
} as const;
```

In `flow/web/src/main.tsx`, keep the current persisted-theme guard. It will now ignore retired stored ids through `isThemeId`.

- [ ] **Step 4: Run theme tests and typecheck**

Run:

```bash
npm --prefix flow/web run test -- src/hooks/use-theme.test.ts
npm run web:typecheck
```

Expected: both PASS.

- [ ] **Step 5: Commit Task 1**

Run:

```bash
git add flow/web/src/hooks/use-theme.ts flow/web/src/hooks/use-theme.test.ts flow/web/src/terminal/theme-colors.ts flow/web/src/main.tsx
git commit -m "feat(web): adopt Signal theme model"
```

Expected: commit succeeds on `codex/signal-redesign`.

---

### Task 2: Web Signal CSS Tokens, PWA Chrome, and Primitives

**Files:**
- Create: `flow/web/src/components/ui/dialog.test.tsx`
- Modify: `flow/web/src/styles/globals.css`
- Modify: `flow/web/src/components/ui/button.tsx`
- Modify: `flow/web/src/components/ui/dialog.tsx`
- Modify: `flow/web/public/manifest.json`
- Modify: `flow/web/src/pwa/pwa-assets.test.ts`

- [ ] **Step 1: Write failing primitive and manifest tests**

Create `flow/web/src/components/ui/dialog.test.tsx`:

```tsx
import { act } from "react";
import { createRoot, type Root } from "react-dom/client";
import { afterEach, beforeEach, describe, expect, it } from "vitest";
import { Dialog, DialogBody, DialogHeader } from "@/components/ui/dialog";

describe("Dialog Signal accessibility", () => {
	let container: HTMLDivElement;
	let root: Root;

	beforeEach(() => {
		container = document.createElement("div");
		document.body.append(container);
		root = createRoot(container);
	});

	afterEach(() => {
		act(() => root.unmount());
		container.remove();
		document.body.innerHTML = "";
	});

	it("labels the icon-only close control", () => {
		act(() => {
			root.render(
				<Dialog open={true} onOpenChange={() => {}}>
					<DialogHeader title="Signal dialog" />
					<DialogBody>Body</DialogBody>
				</Dialog>,
			);
		});

		const closeButton = document.querySelector('[aria-label="Close dialog"]');
		expect(closeButton).toBeInstanceOf(HTMLButtonElement);
	});
});
```

Update `flow/web/src/pwa/pwa-assets.test.ts` manifest assertions:

```ts
expect(manifest.background_color).toBe("#0a0a0b");
expect(manifest.theme_color).toBe("#0f1013");
```

- [ ] **Step 2: Run tests and verify failure**

Run:

```bash
npm --prefix flow/web run test -- src/components/ui/dialog.test.tsx src/pwa/pwa-assets.test.ts
```

Expected: FAIL because the dialog close button lacks `aria-label="Close dialog"` and the manifest still uses old gray-blue colors.

- [ ] **Step 3: Replace web CSS theme values with Signal tokens**

In `flow/web/src/styles/globals.css`, set the base `@theme` token values to:

```css
@theme {
	--color-surface-0: #0a0a0b;
	--color-surface-1: #0f1013;
	--color-surface-2: #131418;
	--color-surface-3: #191b20;
	--color-surface-4: #20232a;
	--color-border: #1d1f24;
	--color-border-bright: #2a2d33;
	--color-border-focus: #c6f24e;
	--color-divider: #1d1f24;
	--color-text-primary: #f5f5f5;
	--color-text-secondary: #b6bcc6;
	--color-text-tertiary: #8a8f98;
	--color-accent: #c6f24e;
	--color-accent-hover: #d4f76f;
	--color-accent-fg: #0a0a0b;
	--color-accent-2: #b48ef0;
	--color-accent-2-hover: #c4a5f5;
	--color-accent-2-fg: #0a0a0b;
	--color-status-blue: #5aa8e0;
	--color-status-green: #7ec95e;
	--color-status-orange: #e0b44e;
	--color-status-red: #e0655f;
	--color-status-purple: #b48ef0;
	--color-status-gold: #e0b44e;
	--color-status-violet: #b48ef0;
	--color-status-rose: #e0655f;
	--color-status-cyan: #5aa8e0;
	--color-status-lime: #7ec95e;
	--font-sans: "Inter", "Geist", -apple-system, BlinkMacSystemFont, system-ui, sans-serif;
	--font-mono: "JetBrains Mono", ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
	--radius-sm: 6px;
	--radius-md: 7px;
	--radius-lg: 10px;
	--radius-xl: 12px;
}
```

Remove old `[data-theme="graphite"]`, `[data-theme="midnight"]`, `[data-theme="pitch"]`, `[data-theme="solarized-dark"]`, `[data-theme="light"]`, `[data-theme="overcast"]`, `[data-theme="solarized-light"]`, `[data-theme="latte"]`, and `[data-theme="high-contrast-light"]` blocks. Keep only a `[data-theme="high-contrast-dark"]` block.

Use this high contrast block:

```css
[data-theme="high-contrast-dark"] {
	--color-surface-0: #000000;
	--color-surface-1: #0a0a0a;
	--color-surface-2: #141414;
	--color-surface-3: #1f1f1f;
	--color-surface-4: #2a2a2a;
	--color-border: #6e6e6e;
	--color-border-bright: #8e8e8e;
	--color-border-focus: #ffffff;
	--color-divider: #3a3a3a;
	--color-text-primary: #ffffff;
	--color-text-secondary: #d6d6d6;
	--color-text-tertiary: #b8b8b8;
	--color-accent: #ffffff;
	--color-accent-hover: #e0e0e0;
	--color-accent-fg: #000000;
	--color-accent-2: #b48ef0;
	--color-accent-2-hover: #c4a5f5;
	--color-accent-2-fg: #000000;
}
```

Replace hardcoded dialog border color `#5A6572` with `border-border-bright`.

- [ ] **Step 4: Tokenize the shared button and dialog primitives**

In `flow/web/src/components/ui/button.tsx`, keep the same API and use these variant strings:

```ts
const variantStyles: Record<ButtonVariant, string> = {
	default:
		"bg-surface-2 border border-border-bright text-text-primary hover:bg-surface-3 hover:border-border-bright active:bg-surface-4",
	primary: "bg-accent text-accent-fg border border-transparent hover:bg-accent-hover active:brightness-95",
	danger:
		"bg-status-red/10 text-status-red border border-status-red/30 hover:bg-status-red/20 active:bg-status-red/30",
	ghost: "bg-transparent text-text-secondary border border-transparent hover:text-text-primary hover:bg-surface-3 active:bg-surface-4",
};
```

In `flow/web/src/components/ui/dialog.tsx`, update `DialogHeader`, `DialogFooter`, `AlertDialogHeader`, and `AlertDialogFooter` class names to use `border-border-bright` and add the close label:

```tsx
<RadixDialog.Close
	aria-label="Close dialog"
	className="p-1 rounded-md text-text-tertiary hover:text-text-primary hover:bg-surface-3 cursor-pointer max-md:min-h-11 max-md:min-w-11 max-md:flex max-md:items-center max-md:justify-center"
>
```

- [ ] **Step 5: Update PWA manifest colors**

In `flow/web/public/manifest.json`, set:

```json
"background_color": "#0a0a0b",
"theme_color": "#0f1013"
```

- [ ] **Step 6: Run primitive, manifest, and build checks**

Run:

```bash
npm --prefix flow/web run test -- src/components/ui/dialog.test.tsx src/pwa/pwa-assets.test.ts
npm run web:build
```

Expected: PASS.

- [ ] **Step 7: Commit Task 2**

Run:

```bash
git add flow/web/src/styles/globals.css flow/web/src/components/ui/button.tsx flow/web/src/components/ui/dialog.tsx flow/web/src/components/ui/dialog.test.tsx flow/web/public/manifest.json flow/web/src/pwa/pwa-assets.test.ts
git commit -m "feat(web): apply Signal visual tokens"
```

Expected: commit succeeds.

---

### Task 3: Runtime Settings Appearance and Web Surface Polish

**Files:**
- Modify: `flow/web/src/components/runtime-settings-dialog.tsx`
- Modify: `flow/web/src/components/runtime-settings-dialog.test.tsx`
- Modify: `flow/web/src/data/column-colors.ts`
- Modify: `flow/web/src/components/startup-onboarding-dialog.tsx`
- Modify: `flow/web/src/components/board-card.tsx`
- Modify: `flow/web/src/components/board-column.tsx`
- Modify: `flow/web/src/components/card-detail-view.tsx`
- Modify: `flow/web/src/components/project-navigation-panel.tsx`
- Modify: `flow/web/src/components/top-bar.tsx`

- [ ] **Step 1: Rewrite runtime settings theme tests**

In `flow/web/src/components/runtime-settings-dialog.test.tsx`, replace the two Graphite theme tests with:

```tsx
it("renders the fixed Signal system appearance block", async () => {
	await act(async () => {
		root.render(
			<RuntimeSettingsDialog
				open={true}
				workspaceId={"workspace-1"}
				initialConfig={savedClineOauthConfig}
				onOpenChange={() => {}}
			/>,
		);
	});

	expect(document.body.textContent).toContain("Signal system");
	expect(document.body.textContent).toContain("Dark canvas, hairline panels, Signal green primary actions");
	expect(findButtonByAriaLabel(document.body, "Theme")).toBeNull();
});

it("does not enable save for the fixed Signal appearance block", async () => {
	await act(async () => {
		root.render(
			<RuntimeSettingsDialog
				open={true}
				workspaceId={"workspace-1"}
				initialConfig={savedClineOauthConfig}
				onOpenChange={() => {}}
			/>,
		);
	});

	const saveButton = findButtonByText(document.body, "Save");
	expect(saveButton).toBeInstanceOf(HTMLButtonElement);
	expect(saveButton?.disabled).toBe(true);
	expect(window.localStorage.getItem("kanban.theme")).toBeNull();
});
```

- [ ] **Step 2: Run runtime settings tests and verify failure**

Run:

```bash
npm --prefix flow/web run test -- src/components/runtime-settings-dialog.test.tsx
```

Expected: FAIL because the old theme select is still rendered and tests still find old Graphite behavior.

- [ ] **Step 3: Replace the theme picker with a fixed Signal block**

In `flow/web/src/components/runtime-settings-dialog.tsx`, remove the `RadixSelect` theme picker from the Appearance section and replace it with this block:

```tsx
<div className="rounded-lg border border-border bg-surface-0 px-4 py-3 mb-4">
	<h6 className="text-[12px] font-semibold uppercase tracking-wider text-text-secondary m-0 mb-2">
		Theme
	</h6>
	<div className="rounded-md border border-border-bright bg-surface-2 p-3">
		<div className="flex items-center gap-3">
			<span className="flex h-6 w-12 shrink-0 overflow-hidden rounded border border-border">
				<span className="flex-1 bg-surface-0" />
				<span className="flex-1 bg-surface-1" />
				<span className="flex-1 bg-accent" />
			</span>
			<div className="min-w-0">
				<p className="m-0 text-[13px] font-medium text-text-primary">Signal system</p>
				<p className="m-0 mt-1 text-[12px] text-text-secondary">
					Dark canvas, hairline panels, Signal green primary actions.
				</p>
			</div>
		</div>
	</div>

	<h6 className="text-[12px] font-semibold uppercase tracking-wider text-text-secondary mt-5 mb-2">
		Layout
	</h6>
	<Button size="sm" onClick={resetLayoutCustomizations}>
		Reset layout
	</Button>
</div>
```

Remove theme-specific state, imports, and save/cancel handling that only exist for selecting themes:

```ts
previewThemeId
readStoredThemeId
saveThemeId
THEME_GROUPS
THEMES
type ThemeId
draftThemeId
initialThemeId
currentThemeDef
```

Keep all non-theme settings save behavior intact.

- [ ] **Step 4: Remap board lane colors to Signal semantics**

In `flow/web/src/data/column-colors.ts`, update the exported maps to:

```ts
export const COLUMN_COLORS: Record<BoardColumnId, string> = {
	plan_later: "var(--color-status-gold)",
	plan_review: "var(--color-status-purple)",
	backlog: "var(--color-text-primary)",
	next_up: "var(--color-status-blue)",
	in_progress: "var(--color-accent)",
	review: "var(--color-status-purple)",
	trash: "var(--color-status-red)",
	archive: "var(--color-text-tertiary)",
};

export const COLUMN_BACKGROUND_COLORS: Record<BoardColumnId, string> = {
	plan_later: "var(--color-surface-0)",
	plan_review: "var(--color-surface-0)",
	backlog: "var(--color-surface-0)",
	next_up: "var(--color-surface-0)",
	in_progress: "var(--color-surface-0)",
	review: "var(--color-surface-0)",
	trash: "var(--color-surface-0)",
	archive: "var(--color-surface-0)",
};
```

This keeps the in-progress lane as the only lane using the primary Signal accent. The review lane uses AI/recommendation violet because it represents an AI-assisted review checkpoint, not a primary action.

- [ ] **Step 5: Polish high-traffic web surfaces after token swap**

Inspect the following files for raw blue values, hardcoded hover colors, or non-token hardcoded borders introduced before Signal:

```bash
rg -n "#0084FF|#339DFF|#86BEFF|#5A6572|bg-status-blue/20|text-status-blue|bg-accent-2/20" flow/web/src/components flow/web/src/data
```

For each hit in the listed owner files, make the smallest Signal-consistent change:

```tsx
// Link-like informational text
className="text-status-blue hover:text-status-blue"

// AI/recommendation selected treatment
className="bg-accent-2/15 text-accent-2"

// Primary active/current treatment
className="bg-accent text-accent-fg"

// Borders
className="border-border-bright"
```

Do not replace semantic info, warning, negative, or AI colors with Signal green.

- [ ] **Step 6: Run focused web tests**

Run:

```bash
npm --prefix flow/web run test -- src/components/runtime-settings-dialog.test.tsx src/components/board-card.test.tsx src/components/board-column.test.tsx src/components/card-detail-view.test.tsx
npm run web:typecheck
```

Expected: PASS.

- [ ] **Step 7: Commit Task 3**

Run:

```bash
git add flow/web/src/components/runtime-settings-dialog.tsx flow/web/src/components/runtime-settings-dialog.test.tsx flow/web/src/data/column-colors.ts flow/web/src/components/startup-onboarding-dialog.tsx flow/web/src/components/board-card.tsx flow/web/src/components/board-column.tsx flow/web/src/components/card-detail-view.tsx flow/web/src/components/project-navigation-panel.tsx flow/web/src/components/top-bar.tsx
git commit -m "feat(web): align core surfaces to Signal"
```

Expected: commit succeeds. If one of the listed component files has no changes after inspection, omit it from `git add`.

---

### Task 4: Mac Electron Launchpad Signal Shell

**Files:**
- Modify: `platforms/desktop/src/desktop-shell.css`
- Modify: `platforms/desktop/src/dashboard.css`
- Modify: `platforms/desktop/src/dashboard.html`
- Modify: `platforms/desktop/test/desktop-shell-assets.test.ts`

- [ ] **Step 1: Add failing static Signal shell assertions**

In `platforms/desktop/test/desktop-shell-assets.test.ts`, add this test:

```ts
it("uses Signal tokens instead of old blue launchpad accents", () => {
	const shellCss = readDesktopFile("src/desktop-shell.css");
	const dashboardCss = readDesktopFile("src/dashboard.css");
	const dashboard = readDesktopFile("src/dashboard.html");

	expect(shellCss).toContain("--accent: #c6f24e");
	expect(shellCss).toContain("--accent-hover: #d4f76f");
	expect(shellCss).toContain("--accent-fg: #0a0a0b");
	expect(shellCss).not.toContain("#0084ff");
	expect(shellCss).not.toContain("#339dff");
	expect(shellCss).not.toContain("#0a5cb8");
	expect(dashboardCss).not.toContain("0, 132, 255");
	expect(dashboard).toContain('aria-label="Refresh runtime status"');
});
```

- [ ] **Step 2: Run desktop static tests and verify failure**

Run:

```bash
npm --prefix platforms/desktop run test -- test/desktop-shell-assets.test.ts
```

Expected: FAIL because old blue tokens and gradients still exist and the refresh label is not present.

- [ ] **Step 3: Update static shell CSS tokens**

In `platforms/desktop/src/desktop-shell.css`, replace the `:root` variables with:

```css
:root {
	color-scheme: dark;
	--black: #0a0a0b;
	--surface-0: #0a0a0b;
	--surface-1: #0f1013;
	--surface-2: #131418;
	--surface-3: #191b20;
	--surface-4: #20232a;
	--border: #1d1f24;
	--border-bright: #2a2d33;
	--border-focus: #c6f24e;
	--text-primary: #f5f5f5;
	--text-secondary: #b6bcc6;
	--text-tertiary: #8a8f98;
	--accent: #c6f24e;
	--accent-hover: #d4f76f;
	--accent-fg: #0a0a0b;
	--accent-soft: rgba(198, 242, 78, 0.12);
	--accent-border: rgba(198, 242, 78, 0.42);
	--green: #7ec95e;
	--orange: #e0b44e;
	--red: #e0655f;
	--muted-dot: #8a8f98;
}
```

Update `.button-primary` to a flat Signal button:

```css
.button-primary {
	min-width: 288px;
	background: var(--accent);
	border-color: var(--accent);
	box-shadow: none;
	color: var(--accent-fg);
	font-weight: 700;
}

.button-primary:hover:not(:disabled) {
	background: var(--accent-hover);
	border-color: var(--accent-hover);
	box-shadow: none;
}
```

- [ ] **Step 4: Flatten dashboard CSS**

In `platforms/desktop/src/dashboard.css`, replace blue radial/linear gradient accents with flat Signal panel treatment:

```css
.launchpad-shell {
	display: grid;
	grid-template-columns: 264px minmax(0, 1fr);
	min-height: 100vh;
	background: var(--black);
	-webkit-app-region: drag;
}

.sidebar {
	display: flex;
	flex-direction: column;
	min-height: 100vh;
	padding: 34px 20px 28px;
	border-right: 1px solid var(--border);
	background: var(--surface-0);
}

.nav-item:hover,
.nav-item.is-active {
	background: var(--surface-2);
	border-color: var(--accent-border);
}

.nav-item.is-active::before {
	position: absolute;
	top: 0;
	bottom: 0;
	left: 0;
	width: 3px;
	border-radius: 4px;
	background: var(--accent);
	content: "";
}

.status-panel,
.section-card {
	border: 1px solid var(--border-bright);
	border-radius: 10px;
	background: var(--surface-1);
	box-shadow: none;
}

.section-heading {
	border-bottom: 1px solid var(--border);
	background: var(--surface-2);
}
```

Remove `rgba(0, 132, 255, ...)` from `dashboard.css`. Keep status dots and badge colors semantic.

- [ ] **Step 5: Add launchpad accessibility labels**

In `platforms/desktop/src/dashboard.html`, update the refresh button:

```html
<button id="refresh" class="button" aria-label="Refresh runtime status">
```

Keep the visible `Refresh` text.

- [ ] **Step 6: Run desktop checks and build static assets**

Run:

```bash
npm --prefix platforms/desktop run test -- test/desktop-shell-assets.test.ts
npm run desktop:typecheck
npm --prefix platforms/desktop run build:ts
```

Expected: PASS. `build:ts` should copy the updated CSS/HTML to `platforms/desktop/dist`.

- [ ] **Step 7: Commit Task 4**

Run:

```bash
git add platforms/desktop/src/desktop-shell.css platforms/desktop/src/dashboard.css platforms/desktop/src/dashboard.html platforms/desktop/test/desktop-shell-assets.test.ts platforms/desktop/dist/desktop-shell.css platforms/desktop/dist/dashboard.css platforms/desktop/dist/dashboard.html
git commit -m "feat(desktop): align launchpad to Signal"
```

Expected: commit succeeds. If the project policy treats `platforms/desktop/dist` as build output for this branch and it remains unchanged or ignored, omit unchanged dist paths.

---

### Task 5: Android Native Shell Signal Tokens

**Files:**
- Modify: `platforms/android/app/src/main/java/com/zonegazeflow/app/ZoneUi.kt`
- Modify: `platforms/android/app/src/main/java/com/zonegazeflow/app/DashboardBottomNavigationView.kt`
- Modify: `platforms/android/app/src/main/java/com/zonegazeflow/app/ServerDashboardView.kt`
- Modify: `platforms/android/app/src/main/java/com/zonegazeflow/app/RuntimeStatusPanel.kt`
- Modify: `platforms/android/app/src/main/java/com/zonegazeflow/app/DashboardOverviewSection.kt`
- Modify: `platforms/android/app/src/main/java/com/zonegazeflow/app/ServerDashboardListRenderer.kt`
- Modify: `platforms/android/app/src/main/java/com/zonegazeflow/app/TermuxDashboardSection.kt`
- Modify: `platforms/android/app/src/main/java/com/zonegazeflow/app/WebActivity.kt`
- Modify: `platforms/android/app/src/test/java/com/zonegazeflow/app/AndroidShellDesignSourceTest.kt`

- [ ] **Step 1: Add failing Android Signal token assertions**

In `AndroidShellDesignSourceTest.kt`, add:

```kotlin
	@Test
	fun androidShellMirrorsSignalDesignTokens() {
		val zoneUi = File("src/main/java/com/zonegazeflow/app/ZoneUi.kt").readText()
		val bottomNavigation = File("src/main/java/com/zonegazeflow/app/DashboardBottomNavigationView.kt").readText()

		assertTrue(zoneUi.contains("val canvas: Int = Color.rgb(10, 10, 11)"))
		assertTrue(zoneUi.contains("val panel: Int = Color.rgb(15, 16, 19)"))
		assertTrue(zoneUi.contains("val signal: Int = Color.rgb(198, 242, 78)"))
		assertTrue(zoneUi.contains("val positive: Int = Color.rgb(126, 201, 94)"))
		assertTrue(zoneUi.contains("val warning: Int = Color.rgb(224, 180, 78)"))
		assertTrue(zoneUi.contains("val negative: Int = Color.rgb(224, 101, 95)"))
		assertFalse(zoneUi.contains("Color.rgb(0, 132, 255)"))
		assertFalse(zoneUi.contains("Accent matches the web board"))
		assertTrue(bottomNavigation.contains("ZoneUi.Colors.signal"))
	}
```

Update the existing `dashboardBottomNavigationUsesFloatingIconTabs` test expectation from:

```kotlin
assertTrue(bottomNavigation.contains("ZoneUi.Colors.accent"))
assertTrue(bottomNavigation.contains("elevation = dp(10).toFloat()"))
```

to:

```kotlin
assertTrue(bottomNavigation.contains("ZoneUi.Colors.signal"))
assertFalse(bottomNavigation.contains("elevation = dp(10).toFloat()"))
```

- [ ] **Step 2: Run Android unit tests and verify failure**

Run:

```bash
npm run android:test
```

Expected: FAIL because `ZoneUi` still uses old blue accent values and the bottom navigation still references `accent`.

- [ ] **Step 3: Mirror Signal tokens in `ZoneUi`**

In `ZoneUi.kt`, replace `object Colors` with a Signal-first object that keeps compatibility aliases for existing call sites:

```kotlin
	object Colors {
		val canvas: Int = Color.rgb(10, 10, 11)
		val canvasAlt: Int = Color.rgb(12, 12, 14)
		val panel: Int = Color.rgb(15, 16, 19)
		val panelNested: Int = Color.rgb(19, 20, 24)
		val border: Int = Color.rgb(29, 31, 36)
		val borderStrong: Int = Color.rgb(42, 45, 51)
		val textPrimary: Int = Color.rgb(245, 245, 245)
		val textSecondary: Int = Color.rgb(182, 188, 198)
		val textTertiary: Int = Color.rgb(138, 143, 152)
		val faint: Int = Color.rgb(76, 80, 88)
		val signal: Int = Color.rgb(198, 242, 78)
		val signalHover: Int = Color.rgb(212, 247, 111)
		val signalText: Int = canvas
		val positive: Int = Color.rgb(126, 201, 94)
		val warning: Int = Color.rgb(224, 180, 78)
		val negative: Int = Color.rgb(224, 101, 95)
		val info: Int = Color.rgb(90, 168, 224)
		val violet: Int = Color.rgb(180, 142, 240)

		val surface0: Int = canvas
		val surface1: Int = panel
		val surface2: Int = panelNested
		val surface3: Int = Color.rgb(25, 27, 32)
		val surface4: Int = Color.rgb(32, 35, 42)
		val surfaceRaised: Int = panel
		val borderBright: Int = borderStrong
		val borderFocus: Int = signal
		val accent: Int = signal
		val accentHover: Int = signalHover
		val green: Int = positive
		val orange: Int = warning
		val red: Int = negative
		val accentCard: Int = panelNested
		val accentButton: Int = signal
		val primaryButtonFill: Int = signal
		val primaryButtonText: Int = signalText
	}
```

Update `applySurfaceDepth` default to reduced depth:

```kotlin
fun applySurfaceDepth(view: View, elevation: Int = 1, lift: Int = 0) {
	view.elevation = dp(view.context, elevation).toFloat()
	view.translationZ = dp(view.context, lift).toFloat()
}
```

Update `statusColor` to use `positive`, `warning`, `negative`, and `textSecondary`.

- [ ] **Step 4: Replace shell references that should use Signal explicitly**

In `DashboardBottomNavigationView.kt`, replace selected active color references:

```kotlin
selected -> ZoneUi.Colors.signal
```

In `RuntimeStatusPanel.kt`, ensure launch button text uses `primaryButtonText` only when enabled and warning/error state colors still use `statusColor`.

In `DashboardOverviewSection.kt`, keep status rows semantic:

```kotlin
return if (ok) DashboardStatus("Ready", ZoneUi.Colors.positive, R.drawable.ic_status_check_circle)
else DashboardStatus("Missing", ZoneUi.Colors.warning, R.drawable.ic_status_warning_triangle)
```

Use `ZoneUi.Colors.signal` only for primary next-step icon/CTA treatment.

In `ServerDashboardView.kt`, reduce top-bar depth:

```kotlin
ZoneUi.applySurfaceDepth(topBar, elevation = 1, lift = 0)
```

In `ServerDashboardListRenderer.kt`, reduce row depth:

```kotlin
ZoneUi.applySurfaceDepth(row, elevation = 1)
```

- [ ] **Step 5: Run Android tests and assemble debug APK**

Run:

```bash
npm run android:test
platforms/android/scripts/gradle-with-android-env.sh :app:assembleDebug
```

Expected: PASS.

- [ ] **Step 6: Commit Task 5**

Run:

```bash
git add platforms/android/app/src/main/java/com/zonegazeflow/app/ZoneUi.kt platforms/android/app/src/main/java/com/zonegazeflow/app/DashboardBottomNavigationView.kt platforms/android/app/src/main/java/com/zonegazeflow/app/ServerDashboardView.kt platforms/android/app/src/main/java/com/zonegazeflow/app/RuntimeStatusPanel.kt platforms/android/app/src/main/java/com/zonegazeflow/app/DashboardOverviewSection.kt platforms/android/app/src/main/java/com/zonegazeflow/app/ServerDashboardListRenderer.kt platforms/android/app/src/main/java/com/zonegazeflow/app/TermuxDashboardSection.kt platforms/android/app/src/main/java/com/zonegazeflow/app/WebActivity.kt platforms/android/app/src/test/java/com/zonegazeflow/app/AndroidShellDesignSourceTest.kt
git commit -m "feat(android): mirror Signal shell tokens"
```

Expected: commit succeeds. Omit Android view files that remain unchanged after the token update.

---

### Task 6: Full Visual QA and Verification

**Files:**
- Create: `project-testing/project-test-zonegaze-flow-043/screenshots/*`
- Create: `project-testing/project-test-zonegaze-flow-043/verification.md`
- Modify: source files only if visual QA reveals a concrete Signal mismatch or responsive/accessibility bug

- [ ] **Step 1: Run full local validation**

Run:

```bash
npm run web:typecheck
npm run web:build
npm --prefix flow/web run test -- src/hooks/use-theme.test.ts src/components/ui/dialog.test.tsx src/components/runtime-settings-dialog.test.tsx src/pwa/pwa-assets.test.ts src/components/board-card.test.tsx src/components/board-column.test.tsx src/components/card-detail-view.test.tsx
npm run desktop:typecheck
npm run desktop:test
npm --prefix platforms/desktop run build:ts
npm run android:test
platforms/android/scripts/gradle-with-android-env.sh :app:assembleDebug
npm run check:boundaries
```

Expected: all commands PASS. If a command fails, fix the specific failing source or test and rerun the failing command before continuing.

- [ ] **Step 2: Verify protected upstream surfaces are untouched**

Run:

```bash
git diff --name-only development...HEAD | rg '^flow/(kanban-core|runtime/src/cline-sdk)/' || true
```

Expected: no output.

- [ ] **Step 3: Relaunch canonical local app from the task branch for screenshot QA**

If port `4217` is already serving the main development checkout, do not silently switch ports. Stop only the stale ZoneGaze Flow server that is serving this repo, then start the app from this worktree on the canonical port:

```bash
lsof -nP -iTCP:4217 -sTCP:LISTEN
npm run dev -- --port 4217 --host 127.0.0.1 --no-open
```

Expected: the dev server listens on `127.0.0.1:4217`. If the port is occupied by an unrelated process, record the blocker in `project-testing/project-test-zonegaze-flow-043/verification.md` and use source/build evidence until the port conflict is resolved.

- [ ] **Step 4: Capture web screenshots**

Use the local Chrome executable with Playwright and save screenshots inside the project-testing run:

```bash
mkdir -p project-testing/project-test-zonegaze-flow-043/screenshots
node <<'NODE'
const { chromium } = require('playwright');
const path = require('path');

(async () => {
	const out = path.resolve('project-testing/project-test-zonegaze-flow-043/screenshots');
	const browser = await chromium.launchPersistentContext(
		path.resolve('project-testing/project-test-zonegaze-flow-043/chrome-profile'),
		{
			headless: true,
			executablePath: '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
		},
	);
	for (const view of [
		{ name: 'signal-web-desktop', width: 1440, height: 960 },
		{ name: 'signal-web-mobile', width: 390, height: 844 },
	]) {
		const page = await browser.newPage();
		await page.setViewportSize({ width: view.width, height: view.height });
		const messages = [];
		page.on('console', (msg) => {
			if (['error', 'warning'].includes(msg.type())) messages.push(`${msg.type()}: ${msg.text()}`);
		});
		page.on('pageerror', (error) => messages.push(`pageerror: ${error.message}`));
		await page.goto('http://127.0.0.1:4217/demo-website', { waitUntil: 'networkidle', timeout: 15000 });
		const closeButton = page.locator('[aria-label="Close dialog"]').first();
		if (await closeButton.isVisible({ timeout: 1500 }).catch(() => false)) {
			await closeButton.click();
			await page.waitForTimeout(300);
		}
		await page.screenshot({ path: path.join(out, `${view.name}.png`), fullPage: true });
		const layout = await page.evaluate(() => ({
			innerWidth,
			innerHeight,
			scrollWidth: document.documentElement.scrollWidth,
			scrollHeight: document.documentElement.scrollHeight,
		}));
		console.log(JSON.stringify({ view: view.name, layout, messages }, null, 2));
		await page.close();
	}
	await browser.close();
})();
NODE
```

Expected: screenshots show Signal green primary/current/live treatment, no page-level horizontal overflow, and no console warnings/errors.

- [ ] **Step 5: Capture Mac launchpad screenshot from built static dashboard**

Run:

```bash
node <<'NODE'
const { chromium } = require('playwright');
const path = require('path');
const { pathToFileURL } = require('url');

(async () => {
	const out = path.resolve('project-testing/project-test-zonegaze-flow-043/screenshots');
	const browser = await chromium.launchPersistentContext(
		path.resolve('project-testing/project-test-zonegaze-flow-043/chrome-profile-desktop-shell'),
		{
			headless: true,
			executablePath: '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
			viewport: { width: 1180, height: 760 },
		},
	);
	const page = await browser.newPage();
	await page.addInitScript(() => {
		window.desktop = {
			getServerSnapshot: async () => ({
				updatedAt: new Date().toISOString(),
				sections: {
					zone: [
						{
							id: 'zone',
							name: 'ZoneGaze Flow',
							description: 'Main runtime dashboard',
							status: 'running',
							url: 'http://127.0.0.1:4217/demo-website',
							capabilities: { canStart: false, canStop: true, canOpenEmbedded: true, canCopyUrl: true },
						},
					],
					other: [],
				},
			}),
			startZoneServer: async () => {},
			stopZoneServer: async () => {},
			openServerUrl: async () => {},
			copyText: async () => {},
		};
	});
	await page.goto(pathToFileURL(path.resolve('platforms/desktop/dist/dashboard.html')).href, {
		waitUntil: 'networkidle',
		timeout: 15000,
	});
	await page.screenshot({ path: path.join(out, 'signal-mac-launchpad.png'), fullPage: true });
	await browser.close();
})();
NODE
```

Expected: launchpad uses Signal green for primary action and live state, no blue gradients, and no broken brand asset.

- [ ] **Step 6: Capture or document Android visual evidence**

If an emulator is available:

```bash
adb devices
platforms/android/scripts/gradle-with-android-env.sh :app:installDebug
adb shell monkey -p com.zonegazeflow.app 1
adb exec-out screencap -p > project-testing/project-test-zonegaze-flow-043/screenshots/signal-android-dashboard.png
```

Expected: Android dashboard uses Signal green active navigation/primary action, flat dark panels, and semantic status hues.

If no emulator is available, write `project-testing/project-test-zonegaze-flow-043/verification.md` with this entry:

```md
## Android Visual Evidence

Android emulator screenshot was not captured because no emulator/device was available in this environment.
Substitute evidence:
- `npm run android:test` passed.
- `platforms/android/scripts/gradle-with-android-env.sh :app:assembleDebug` passed.
- `AndroidShellDesignSourceTest.androidShellMirrorsSignalDesignTokens` proves the native shell uses Signal token values and centralized `ZoneUi` references.
```

- [ ] **Step 7: Write verification report**

Create `project-testing/project-test-zonegaze-flow-043/verification.md`:

```md
# Signal System Redesign Verification

## Summary

Signal redesign implemented across web, Mac launchpad, and Android native shell.

## Commands

- npm run web:typecheck
- npm run web:build
- npm --prefix flow/web run test -- src/hooks/use-theme.test.ts src/components/ui/dialog.test.tsx src/components/runtime-settings-dialog.test.tsx src/pwa/pwa-assets.test.ts src/components/board-card.test.tsx src/components/board-column.test.tsx src/components/card-detail-view.test.tsx
- npm run desktop:typecheck
- npm run desktop:test
- npm --prefix platforms/desktop run build:ts
- npm run android:test
- platforms/android/scripts/gradle-with-android-env.sh :app:assembleDebug
- npm run check:boundaries

## Screenshots

- screenshots/signal-web-desktop.png
- screenshots/signal-web-mobile.png
- screenshots/signal-mac-launchpad.png
- screenshots/signal-android-dashboard.png, or Android substitute evidence if no emulator was available

## Protected Surfaces

`git diff --name-only development...HEAD | rg '^flow/(kanban-core|runtime/src/cline-sdk)/' || true` produced no output.

## Open Blockers

None.
```

If any blocker remains, replace `None.` with the exact blocker and the verification already completed.

- [ ] **Step 8: Commit Task 6**

Run:

```bash
git add project-testing/project-test-zonegaze-flow-043
git commit -m "test: verify Signal redesign"
```

Expected: commit succeeds. If project-testing artifacts are ignored by git, keep the report in the working tree for handoff and do not force-add ignored artifacts unless the user asks.

---

### Task 7: Merge Back to Development, Push, and Relaunch

**Files:**
- No source changes unless merge conflict resolution is required.

- [ ] **Step 1: Reconcile task branch before merge**

Run:

```bash
git status --short --branch
git fetch origin
git diff --name-only development...HEAD | rg '^flow/(kanban-core|runtime/src/cline-sdk)/' || true
```

Expected: clean or only intended verification artifacts; protected-surface check produces no output.

- [ ] **Step 2: Update development and merge task branch**

From the main checkout:

```bash
git checkout development
git pull --ff-only origin development
git merge --no-ff codex/signal-redesign
```

Expected: merge succeeds without conflicts. If conflicts appear, stop and resolve only conflicts in files owned by this plan.

- [ ] **Step 3: Run joined-branch verification**

From the main checkout after merge:

```bash
npm run web:typecheck
npm run web:build
npm run desktop:typecheck
npm run desktop:test
npm run android:test
npm run check:boundaries
```

Expected: all PASS.

- [ ] **Step 4: Push development**

Run:

```bash
git push origin development
```

Expected: push succeeds. Do not force-push.

- [ ] **Step 5: Relaunch canonical local app from development**

Stop any stale ZoneGaze server that is serving the task worktree, then start from the merged development checkout:

```bash
lsof -nP -iTCP:4217 -sTCP:LISTEN
npm run dev -- --port 4217 --host 127.0.0.1 --no-open
```

Expected: `http://127.0.0.1:4217/demo-website` serves the merged development checkout.

- [ ] **Step 6: Final browser smoke check**

Run the web screenshot script from Task 6 against the development checkout and save outputs under a new project-testing run directory.

Expected: Signal design visible on desktop and mobile, no console warnings/errors, and no page-level horizontal overflow.

- [ ] **Step 7: Final handoff**

Report:

```md
Summary:
- Signal redesign implemented across web, Mac launchpad, and Android shell.

Verification:
- List passing commands.
- Link project-testing screenshots/report paths.

Git:
- Merged to development.
- Pushed origin/development.
- Relaunched http://127.0.0.1:4217/demo-website from development.

Open Blockers:
- None, or exact remaining blocker.
```

Expected: goal can be marked complete only after all requirements above have current evidence.

---

## Plan Self-Review

Spec coverage:
- Shared Signal tokens: Tasks 1, 2, 4, and 5.
- Web app theme, primitives, board/sidebar/topbar/dialog/onboarding polish: Tasks 1, 2, and 3.
- Mac launchpad: Task 4.
- Android native shell: Task 5.
- Assets and brand: Tasks 4 and 6 verify no broken wordmark and no URL/identity change.
- Accessibility and responsive states: Tasks 2, 3, and 6.
- Protected upstream Cline surfaces: Tasks 6 and 7.
- Validation, merge, push, and relaunch: Tasks 6 and 7.

Completeness scan:
- No incomplete requirements remain in this plan.
- Every task has files, failing-test step, implementation step, verification command, and commit step.

Type consistency:
- `ThemeId` is consistently `default | high-contrast-dark`.
- `SIGNAL_THEME_COLORS` is defined in Task 1 before tests import it.
- Android `ZoneUi.Colors.signal` is defined before view files reference it.
