From a40038e18544976b3a06c15264e9cf66919aa76a Mon Sep 17 00:00:00 2001 From: Sahil Ahuja Date: Wed, 22 Jul 2026 16:03:28 +0530 Subject: [PATCH 1/2] fix: instance-accent PWA titlebar wash (mock parity) --- .../contexts/instance-accent-context.test.tsx | 11 +- .../src/contexts/instance-accent-context.tsx | 13 +- app/frontend/src/instance-accent.test.ts | 8 +- app/frontend/src/instance-accent.ts | 18 ++- docs/memory/run-kit/ui-patterns.md | 14 +- .../.history.jsonl | 10 ++ .../.status.yaml | 54 ++++++++ .../intake.md | 96 ++++++++++++++ .../plan.md | 121 ++++++++++++++++++ 9 files changed, 327 insertions(+), 18 deletions(-) create mode 100644 fab/changes/260722-y5c3-instance-accent-titlebar-wash/.history.jsonl create mode 100644 fab/changes/260722-y5c3-instance-accent-titlebar-wash/.status.yaml create mode 100644 fab/changes/260722-y5c3-instance-accent-titlebar-wash/intake.md create mode 100644 fab/changes/260722-y5c3-instance-accent-titlebar-wash/plan.md diff --git a/app/frontend/src/contexts/instance-accent-context.test.tsx b/app/frontend/src/contexts/instance-accent-context.test.tsx index 79f75274..4ffb27dc 100644 --- a/app/frontend/src/contexts/instance-accent-context.test.tsx +++ b/app/frontend/src/contexts/instance-accent-context.test.tsx @@ -3,7 +3,8 @@ import { render, screen, cleanup, waitFor, fireEvent } from "@testing-library/re import { ThemeProvider } from "@/contexts/theme-context"; import { ToastProvider } from "@/components/toast"; import { InstanceAccentProvider, useInstanceAccent } from "./instance-accent-context"; -import { readInstanceColorEcho, writeInstanceColorEcho } from "@/instance-accent"; +import { readInstanceColorEcho, writeInstanceColorEcho, deriveAccentHexes } from "@/instance-accent"; +import { DEFAULT_DARK_THEME } from "@/themes"; // Mock the API client module so no real HTTP calls happen in tests. vi.mock("@/api/client", () => ({ @@ -83,9 +84,13 @@ describe("InstanceAccentProvider resolution chain", () => { expect(screen.getByTestId("stripe").textContent).toMatch(/^#[0-9a-f]{6}$/i); // Echo rewritten with the authoritative value. await waitFor(() => expect(readInstanceColorEcho()?.value).toBe("5")); - // Meta carries the accent hex, not the bare background. + // Meta carries the subtle titlebar blend (mock parity) — NOT the full-hue + // stripe hex — and the echo's hex matches it for the pre-paint script. const meta = document.querySelector('meta[name="theme-color"]'); - expect(meta?.getAttribute("content")).toBe(screen.getByTestId("stripe").textContent); + const titlebarHex = deriveAccentHexes("5", DEFAULT_DARK_THEME)?.titlebarHex; + expect(meta?.getAttribute("content")).toBe(titlebarHex); + expect(meta?.getAttribute("content")).not.toBe(screen.getByTestId("stripe").textContent); + expect(readInstanceColorEcho()?.hex).toBe(meta?.getAttribute("content")); }); it("defaults to no accent when no explicit color is set (no derived default)", async () => { diff --git a/app/frontend/src/contexts/instance-accent-context.tsx b/app/frontend/src/contexts/instance-accent-context.tsx index f065bd34..a3446d78 100644 --- a/app/frontend/src/contexts/instance-accent-context.tsx +++ b/app/frontend/src/contexts/instance-accent-context.tsx @@ -24,8 +24,9 @@ export type InstanceAccent = { color: string | null; /** True when an explicit instance color is set. */ isExplicit: boolean; - /** Contrast-guarded accent hex — top-bar stripe, HOST hostname tint, and - * the theme-color meta content. Null when no accent is resolved. */ + /** Contrast-guarded accent hex — top-bar stripe and HOST hostname tint + * (the theme-color meta takes the subtler `titlebarHex` blend instead). + * Null when no accent is resolved. */ stripeHex: string | null; /** Subtle accent-into-background blend for the top-bar wash. */ washHex: string | null; @@ -70,11 +71,13 @@ export function InstanceAccentProvider({ children }: { children: React.ReactNode ); // Bridge: keep the theme-color meta and the localStorage echo in sync with - // the resolved accent under the active theme. + // the resolved accent under the active theme. The meta (installed-PWA + // titlebar) carries the subtle titlebar blend — NOT the full-hue stripeHex — + // so the 2px stripe below the titlebar stays visible (mock parity). useEffect(() => { if (resolved != null && hexes != null) { - setAccentThemeColor(hexes.stripeHex); - writeInstanceColorEcho({ value: resolved, hex: hexes.stripeHex }); + setAccentThemeColor(hexes.titlebarHex); + writeInstanceColorEcho({ value: resolved, hex: hexes.titlebarHex }); } else if (authoritative) { setAccentThemeColor(null); writeInstanceColorEcho(null); diff --git a/app/frontend/src/instance-accent.test.ts b/app/frontend/src/instance-accent.test.ts index 57fb7e02..f463cd60 100644 --- a/app/frontend/src/instance-accent.test.ts +++ b/app/frontend/src/instance-accent.test.ts @@ -51,15 +51,20 @@ describe("instance color echo", () => { }); describe("deriveAccentHexes", () => { - it("derives stripe and wash hexes for single and blend descriptors, per theme", () => { + it("derives stripe, wash, and titlebar hexes for single and blend descriptors, per theme", () => { for (const value of ["4", "1+3"]) { for (const theme of [DEFAULT_DARK_THEME, DEFAULT_LIGHT_THEME]) { const hexes = deriveAccentHexes(value, theme); expect(hexes).not.toBeNull(); expect(hexes?.stripeHex).toMatch(/^#[0-9a-f]{6}$/i); expect(hexes?.washHex).toMatch(/^#[0-9a-f]{6}$/i); + expect(hexes?.titlebarHex).toMatch(/^#[0-9a-f]{6}$/i); // The wash is a near-background blend, never the raw accent. expect(hexes?.washHex).not.toBe(hexes?.stripeHex); + // The titlebar tint is its own blend — stronger than the wash, + // far dimmer than the full-hue stripe. + expect(hexes?.titlebarHex).not.toBe(hexes?.stripeHex); + expect(hexes?.titlebarHex).not.toBe(hexes?.washHex); } } }); @@ -72,6 +77,7 @@ describe("deriveAccentHexes", () => { const dark = deriveAccentHexes("4", DEFAULT_DARK_THEME); const light = deriveAccentHexes("4", DEFAULT_LIGHT_THEME); expect(dark?.washHex).not.toBe(light?.washHex); + expect(dark?.titlebarHex).not.toBe(light?.titlebarHex); }); }); diff --git a/app/frontend/src/instance-accent.ts b/app/frontend/src/instance-accent.ts index 80395031..74556411 100644 --- a/app/frontend/src/instance-accent.ts +++ b/app/frontend/src/instance-accent.ts @@ -30,6 +30,11 @@ export const INSTANCE_COLOR_STORAGE_KEY = "runkit-instance-color"; * (intake latitude: ~6-7%; one trivially-tunable constant). */ export const INSTANCE_WASH_RATIO = 0.065; +/** Ratio of the accent blended into the theme background for the PWA titlebar + * (theme-color meta) tint — mock parity ≈ 12% within the granted ~0.12–0.15 + * band; a taste constant, trivially tunable. */ +export const INSTANCE_TITLEBAR_RATIO = 0.12; + export type InstanceColorEcho = { value: string; hex: string }; function isEcho(v: unknown): v is InstanceColorEcho { @@ -65,20 +70,23 @@ export function writeInstanceColorEcho(echo: InstanceColorEcho | null): void { } /** Theme-derived hexes for the accent surfaces: `stripeHex` is the - * contrast-guarded family hex (top-bar stripe, HOST hostname tint, and the - * theme-color meta content), `washHex` the subtle top-bar background blend. - * Accepts family names and legacy descriptors incl. blends ("1+3") via the - * owned-family mapping. Null when the value maps to no owned family. */ + * contrast-guarded family hex (top-bar stripe and HOST hostname tint), + * `washHex` the subtle top-bar background blend, and `titlebarHex` the + * slightly stronger blend that becomes the theme-color meta content (the + * installed-PWA titlebar tint — subtle wash above, full-brightness stripe + * below). Accepts family names and legacy descriptors incl. blends ("1+3") + * via the owned-family mapping. Null when the value maps to no owned family. */ export function deriveAccentHexes( value: string, theme: Theme, -): { stripeHex: string; washHex: string } | null { +): { stripeHex: string; washHex: string; titlebarHex: string } | null { const src = colorValueToHex(value, theme.palette); if (src == null) return null; const bg = theme.palette.background; return { stripeHex: adjustBorderForContrast(src, bg, theme.category === "dark", BORDER_MIN_CONTRAST), washHex: blendHex(src, bg, INSTANCE_WASH_RATIO), + titlebarHex: blendHex(src, bg, INSTANCE_TITLEBAR_RATIO), }; } diff --git a/docs/memory/run-kit/ui-patterns.md b/docs/memory/run-kit/ui-patterns.md index 2f593b78..edfce5d0 100644 --- a/docs/memory/run-kit/ui-patterns.md +++ b/docs/memory/run-kit/ui-patterns.md @@ -1444,15 +1444,15 @@ A per-instance accent color makes multiple run-kit instances (laptop, Mac mini, **Resolution chain** (frontend, in the `InstanceAccentProvider`): (1) the explicit `instance_color` setting from `getInstanceColor()` — authoritative; (2) the localStorage echo (`runkit-instance-color`) — a **paint cache only, never authoritative**, used as the first-frame seed before the fetch lands; (3) **none** — there is **no derived default**: a fresh instance renders no accent until the user picks one (an earlier hostname-hash default was removed — its color could coincide with a just-cleared explicit pick, making "Clear color" look like a no-op). An `authoritative` flag (the explicit fetch resolved) gates clearing the echo/meta on a null accent, so the paint seed is never wiped before the real resolution replaces it. -**Provider + consumers** — `InstanceAccentProvider` / `useInstanceAccent()` (`contexts/instance-accent-context.tsx`) mounts once in `RootWrapper` inside `ThemeProvider` > `ToastProvider` and above `ChromeProvider`, so it can read `useTheme()` for the theme-derived hexes and `useToast()` for the write-failure toast. It fetches `getInstanceColor()` + `getHealth()` once on mount (StrictMode-guarded via a `didFetchRef`), seeds the first paint from the echo, resolves per the chain, derives `{stripeHex, washHex}` from the active theme, keeps the echo + theme-color meta in sync on every accent/theme change, and exposes `{color, isExplicit, stripeHex, washHex, setColor}`. `setColor(descriptor | null)` updates state optimistically then POSTs via `setInstanceColor`, toasting on failure. Both rendering surfaces read from this ONE instance — one fetch, one state, so a pick in the HOST panel repaints the top-bar stripe without a reload. A test seam `InstanceAccentValueProvider` injects a fixed value (mirroring the session-context value-injection pattern). +**Provider + consumers** — `InstanceAccentProvider` / `useInstanceAccent()` (`contexts/instance-accent-context.tsx`) mounts once in `RootWrapper` inside `ThemeProvider` > `ToastProvider` and above `ChromeProvider`, so it can read `useTheme()` for the theme-derived hexes and `useToast()` for the write-failure toast. It fetches `getInstanceColor()` + `getHealth()` once on mount (StrictMode-guarded via a `didFetchRef`), seeds the first paint from the echo, resolves per the chain, derives `{stripeHex, washHex, titlebarHex}` from the active theme, keeps the echo + theme-color meta in sync on every accent/theme change, and exposes `{color, isExplicit, stripeHex, washHex, setColor}` (`titlebarHex` is consumed only by the bridge effect, not exposed — no rendering surface reads it). `setColor(descriptor | null)` updates state optimistically then POSTs via `setInstanceColor`, toasting on failure. Both rendering surfaces read from this ONE instance — one fetch, one state, so a pick in the HOST panel repaints the top-bar stripe without a reload. A test seam `InstanceAccentValueProvider` injects a fixed value (mirroring the session-context value-injection pattern). -**Theme-derived hexes** (`deriveAccentHexes(value, theme)` in `src/instance-accent.ts`, never hardcoded): `stripeHex` is the **contrast-guarded** family hex — `colorValueToHex(value, palette)` then `adjustBorderForContrast(src, bg, isDark, BORDER_MIN_CONTRAST)` (the same guarded-hex derivation server tiles/stripes use, § Color Tinting) — used for the top-bar stripe, the HOST hostname tint, AND the theme-color meta content. `washHex` is a subtle `blendHex(src, bg, INSTANCE_WASH_RATIO = 0.065)` (~6.5%) accent-into-background blend for the top-bar wash. Blend descriptors (`"1+3"`) render through the same `resolveFamily`-based owned-family mapping as single indices — no new blend scheme. Both recompute on a theme switch; nothing hardcoded survives. +**Theme-derived hexes** (`deriveAccentHexes(value, theme)` in `src/instance-accent.ts`, never hardcoded) — a three-hex split, each a distinct accent surface: `stripeHex` is the **contrast-guarded** family hex — `colorValueToHex(value, palette)` then `adjustBorderForContrast(src, bg, isDark, BORDER_MIN_CONTRAST)` (the same guarded-hex derivation server tiles/stripes use, § Color Tinting) — used for the 2px top-bar stripe and the HOST hostname tint. `washHex` is a subtle `blendHex(src, bg, INSTANCE_WASH_RATIO = 0.065)` (~6.5%) accent-into-background blend for the top-bar background wash. `titlebarHex` is a slightly stronger `blendHex(src, bg, INSTANCE_TITLEBAR_RATIO = 0.12)` (~12%) accent-into-background blend that is the theme-color meta content (the installed-PWA titlebar tint) — a subtle tinted titlebar with the full-brightness `stripeHex` 2px stripe visible directly below it. `INSTANCE_TITLEBAR_RATIO` sits beside `INSTANCE_WASH_RATIO` (a taste constant, granted ~0.12–0.15 band). Blend descriptors (`"1+3"`) render through the same `resolveFamily`-based owned-family mapping as single indices — no new blend scheme. All three recompute on a theme switch (light themes get a light-background blend by construction); nothing hardcoded survives. An unrecognized descriptor yields `null` (no partial object). **Top-bar stripe + wash** (`AppLayout`, `app.tsx`) — the persistent top bar's `shrink-0` wrapper (above `RootTopBar`) carries a 2px accent stripe (an `aria-hidden` `
`) plus the `washHex` as its `backgroundColor` (the `TopBar` header has no background of its own, so the wash on the wrapper shows through). Both render only when their hex is non-null (no accent resolved ⇒ neither renders). This is the "which instance" channel living where the sidebar colors do not. **HOST panel — accent hostname tint + swatch picker** (`components/sidebar/host-panel.tsx`) — the hostname in the panel's `headerRight` slot renders in `stripeHex` (the contrast-guarded accent) when resolved, else the default `text-text-primary`. A palette swatch button rides the `CollapsiblePanel` **`titleAction` slot** (immediately right of the HOST title — a sibling of the toggle button, since a nested button would be invalid markup; with `titleAction` set the header splits into `[toggle: chevron+title][titleAction][toggle click-region: headerRight]`, the trailing region keeping mouse click-to-toggle while keyboard toggling stays on the title button), hover-revealed with the touch/keyboard fallbacks `opacity-0 group-hover/panel:opacity-100 coarse:opacity-100 focus-visible:opacity-100` (`CollapsiblePanel`'s header gained a `group/panel` class to drive it) and an `aria-label="Set instance color"`. Clicking it opens a **color-only** `SwatchPopover` (no `onSelectMarker`) portalled to `document.body` at fixed coordinates anchored at the button — left-aligned (the button sits beside the title at the sidebar's left edge), with a **flip-above** `useLayoutEffect` heuristic (the HOST panel sits at the sidebar bottom, so flip-above is the norm) — escaping the panel's overflow clip. This is the x4sf ServerGroup-header picker precedent (§ Server-group header action cluster). A pick calls `useInstanceAccent().setColor` (which POSTs and repaints every surface); `Clear color` sends `null`, removing the accent entirely (the no-color default). The HOST header carries **no connection dot**: the top-bar dot already reflects the same current-server subscription health (`setChromeConnected(slice.isConnected)` in session-context), and the removed dot's "SSE" title predated the `/ws/state` socket. -**PWA titlebar bridge — single theme-color meta writer** (`src/instance-accent.ts`) — the `` tag is accent-aware through ONE shared writer holding module-level state (`currentAccentHex` + `lastBackground`, content = accent hex when set, else the theme background): (a) the blocking pre-paint script in `index.html` (§ PWA Meta Tags & Theme Color) reads the echoed `hex` from `runkit-instance-color`, validates the `#rrggbb` shape, and applies it as the initial theme-color (falling back to the per-mode defaults) so an installed PWA window opens already tinted with no flash; (b) at runtime the provider calls `setAccentThemeColor(stripeHex)` on accent/theme change; (c) `theme-context.tsx`'s `applyThemeToDOM` calls `applyThemeColorMeta(theme.palette.background)` instead of writing the meta directly, so a theme switch cannot clobber the accent tint. Desktop Chrome retints installed-PWA titlebars live on meta changes. **Out of scope** (follow-up): dynamically-served `manifest.json`, tinted manifest/dock icons, the Badging API — dock icons snapshot at PWA install time and refresh only on Chrome's lazy manifest-update cycle (an accepted, documented limitation); this change lays only the settings.yaml groundwork they need. +**PWA titlebar bridge — single theme-color meta writer** (`src/instance-accent.ts`) — the `` tag is accent-aware through ONE shared writer holding module-level state (`currentAccentHex` + `lastBackground`, content = accent hex when set, else the theme background): (a) the blocking pre-paint script in `index.html` (§ PWA Meta Tags & Theme Color) reads the echoed `hex` from `runkit-instance-color`, validates the `#rrggbb` shape, and applies it **verbatim** as the initial theme-color (falling back to the per-mode defaults) so an installed PWA window opens already tinted with no flash; (b) at runtime the provider calls `setAccentThemeColor(titlebarHex)` (the ~12% titlebar blend, NOT `stripeHex`) on accent/theme change, and writes the same `titlebarHex` into the echo's `hex` field — so cold start and runtime both paint the subtle tinted titlebar, and the full-brightness `stripeHex` 2px stripe stays visible directly below it; (c) `theme-context.tsx`'s `applyThemeToDOM` calls `applyThemeColorMeta(theme.palette.background)` instead of writing the meta directly, so a theme switch cannot clobber the accent tint. Desktop Chrome retints installed-PWA titlebars live on meta changes. `index.html` is unchanged — the pre-paint script applies the echoed `hex` verbatim, so swapping the echoed value retints cold start for free (an echo written by a pre-titlebar-blend build carries the full-hue hex and self-corrects when the runtime resolution rewrites both meta and echo — the same accepted self-correcting transient class as cross-mode loads). **Out of scope** (follow-up): dynamically-served `manifest.json`, tinted manifest/dock icons, the Badging API — dock icons snapshot at PWA install time and refresh only on Chrome's lazy manifest-update cycle (an accepted, documented limitation); this change lays only the settings.yaml groundwork they need. ### Row Anatomy — Selection & Board-Pin Cues (`window-row.tsx`) @@ -1834,7 +1834,7 @@ The `` tag is injected automatically by `vite-plugin-pwa` d 1. **Initial load** — the blocking inline script in `index.html` sets `theme-color` alongside `data-theme` before first paint; it first tries the echoed instance-accent hex from `runkit-instance-color` (validated `#rrggbb`), falling back to the per-mode default (`#0f1117` dark / `#f8f9fb` light) so an installed PWA window opens already tinted with no flash 2. **Runtime switch** — `applyThemeToDOM` in `ThemeProvider` calls `applyThemeColorMeta(theme.palette.background)` (NOT a direct `setAttribute`), and the `InstanceAccentProvider` calls `setAccentThemeColor(hex)`; both funnel through the one writer's module state so a theme switch cannot clobber the accent tint (§ Instance Accent → PWA titlebar bridge) -Theme color is per-theme (derived from `palette.background`) when no accent is set, else the contrast-guarded accent hex — not a fixed dark/light pair. +Theme color is per-theme (derived from `palette.background`) when no accent is set, else the accent's `titlebarHex` (the ~12% accent-into-background titlebar blend, § Instance Accent → Theme-derived hexes) — not a fixed dark/light pair. **Icon set**: Canonical mark at `app/frontend/public/icon.svg` (hexagonal cube, transparent). Generated variants in `app/frontend/public/generated-icons/`: - `favicon.svg` — copy of `icon.svg` (transparent, used as browser favicon) @@ -2400,3 +2400,9 @@ The regression test in `app/frontend/src/hooks/use-dialog-state.test.tsx` flips **Why**: deriving a hex from a descriptor needs the OKLCH/palette machinery (`colorValueToHex` + contrast guard), far too heavy to inline in a blocking script; precomputing at echo time keeps the script a validate-and-apply read. A cross-theme-mode load shows a transient mismatch that self-corrects post-fetch (accepted). **Rejected**: inlining the derivation in `index.html` (duplicates themes.ts logic in unlintable inline JS); echoing only the descriptor (leaves the script unable to tint). *Introduced by*: 260721-1etw-instance-accent-host-color + +### PWA titlebar carries a subtle blend, not the full accent hue +**Decision**: The theme-color meta content (and the echo's `hex`) is `titlebarHex` — a `blendHex(src, background, INSTANCE_TITLEBAR_RATIO = 0.12)` accent-into-background blend — not the contrast-guarded `stripeHex`. `stripeHex` stays the 2px top-bar stripe and the HOST hostname tint; `washHex` (~6.5%) stays the top-bar background wash. Three distinct derived hexes, one per surface. +**Why**: mock parity. The design mock tinted the installed-PWA titlebar with a subtle dark blend of the accent into the theme background and relied on the full-brightness 2px stripe sitting directly below it as the vivid accent line. Painting the meta with the full-hue `stripeHex` made the whole titlebar a loud color band and rendered the stripe invisible — it abutted a titlebar painted the identical hex. The blend restores the tinted-titlebar + bright-line pairing the mock designed. Reuses the existing `blendHex` machinery (theme-aware by construction, no hardcoded hexes); `index.html` needs no change because the pre-paint script applies the echoed `hex` verbatim, so swapping the echoed value retints cold start for free. +**Rejected**: keeping the saturated titlebar and instead removing/relocating the stripe (the mock's whole point is the tinted-titlebar + bright-line pairing); a dynamic manifest / tinted dock icons / Badging API (separate follow-up `260722-eo8e-accent-dock-icon`). +*Introduced by*: 260722-y5c3-instance-accent-titlebar-wash diff --git a/fab/changes/260722-y5c3-instance-accent-titlebar-wash/.history.jsonl b/fab/changes/260722-y5c3-instance-accent-titlebar-wash/.history.jsonl new file mode 100644 index 00000000..4ddb817d --- /dev/null +++ b/fab/changes/260722-y5c3-instance-accent-titlebar-wash/.history.jsonl @@ -0,0 +1,10 @@ +{"action":"enter","driver":"fab-new","event":"stage-transition","stage":"intake","ts":"2026-07-22T10:14:27Z"} +{"args":"Instance-accent PWA titlebar: dim the theme-color meta to a dark wash (mock parity), revealing the 2px stripe","cmd":"fab-new","event":"command","ts":"2026-07-22T10:14:27Z"} +{"delta":"+4.9","event":"confidence","score":4.9,"trigger":"calc-score","ts":"2026-07-22T10:16:05Z"} +{"cmd":"fab-switch","event":"command","ts":"2026-07-22T10:17:05Z"} +{"cmd":"fab-fff","event":"command","ts":"2026-07-22T10:18:52Z"} +{"action":"enter","driver":"fab-fff","event":"stage-transition","stage":"apply","ts":"2026-07-22T10:19:11Z"} +{"action":"enter","driver":"fab-fff","event":"stage-transition","stage":"review","ts":"2026-07-22T10:25:18Z"} +{"action":"enter","driver":"fab-fff","event":"stage-transition","stage":"hydrate","ts":"2026-07-22T10:29:38Z"} +{"event":"review","result":"passed","ts":"2026-07-22T10:29:38Z"} +{"action":"enter","driver":"fab-fff","event":"stage-transition","stage":"ship","ts":"2026-07-22T10:32:41Z"} diff --git a/fab/changes/260722-y5c3-instance-accent-titlebar-wash/.status.yaml b/fab/changes/260722-y5c3-instance-accent-titlebar-wash/.status.yaml new file mode 100644 index 00000000..994e91de --- /dev/null +++ b/fab/changes/260722-y5c3-instance-accent-titlebar-wash/.status.yaml @@ -0,0 +1,54 @@ +id: y5c3 +name: 260722-y5c3-instance-accent-titlebar-wash +created: 2026-07-22T10:14:27Z +created_by: sahil-noon +change_type: fix +issues: [] +progress: + intake: done + apply: done + review: done + hydrate: done + ship: active + review-pr: pending +plan: + generated: true + task_count: 5 + acceptance_count: 11 + acceptance_completed: 11 +confidence: + certain: 7 + confident: 2 + tentative: 0 + unresolved: 0 + score: 4.9 + fuzzy: true + dimensions: + signal: 82.8 + reversibility: 90.6 + competence: 87.2 + disambiguation: 85.0 +stage_metrics: + intake: {started_at: "2026-07-22T10:14:27Z", driver: fab-new, iterations: 1, completed_at: "2026-07-22T10:19:11Z"} + apply: {started_at: "2026-07-22T10:19:11Z", driver: fab-fff, iterations: 1, completed_at: "2026-07-22T10:25:18Z"} + review: {started_at: "2026-07-22T10:25:18Z", driver: fab-fff, iterations: 1, completed_at: "2026-07-22T10:29:38Z"} + hydrate: {started_at: "2026-07-22T10:29:38Z", driver: fab-fff, iterations: 1, completed_at: "2026-07-22T10:32:41Z"} + ship: {started_at: "2026-07-22T10:32:41Z", driver: fab-fff, iterations: 1} +prs: [] +true_impact: + added: 0 + deleted: 0 + net: 0 + excluding: + added: 0 + deleted: 0 + net: 0 + tests: + added: 0 + deleted: 0 + net: 0 + computed_at: "2026-07-22T10:32:41Z" + computed_at_stage: hydrate +summary: instance-accent PWA titlebar now uses a subtle titlebarHex blend (INSTANCE_TITLEBAR_RATIO=0.12) as theme-color meta + echo, keeping the full-hue stripeHex 2px stripe visible below it +# true_impact: lazily created on first stage-finish that computes it (no placeholder here). +last_updated: 2026-07-22T10:32:41Z diff --git a/fab/changes/260722-y5c3-instance-accent-titlebar-wash/intake.md b/fab/changes/260722-y5c3-instance-accent-titlebar-wash/intake.md new file mode 100644 index 00000000..5f878f5f --- /dev/null +++ b/fab/changes/260722-y5c3-instance-accent-titlebar-wash/intake.md @@ -0,0 +1,96 @@ +# Intake: Instance-Accent PWA Titlebar Wash (Mock Parity) + +**Change**: 260722-y5c3-instance-accent-titlebar-wash +**Created**: 2026-07-22 + +## Origin + +Promptless dispatch (create-intake subagent, `{questioning-mode} = promptless-defer`) from a feature description synthesized from a design conversation held today, in which the user reviewed the original design mock ("Per-Instance Accent — Option 6") on screen against the shipped behavior of PR #435 (`260721-1etw-instance-accent-host-color`) and explicitly confirmed the mock's composition. Decisions below are captured verbatim from that conversation — not re-derived. + +> Instance-accent PWA titlebar: dim the theme-color meta to a dark wash (mock parity), revealing the 2px stripe. The merged change 260721-1etw writes the FULL contrast-guarded accent hex (`stripeHex`) into ``, so Chrome paints an installed PWA window's entire titlebar in the fully saturated hue — a loud color band. The mock instead tinted the titlebar with a subtle DARK BLEND of the accent into the theme background and relied on the already-implemented 2px full-brightness accent stripe at the top of the web content to provide the vivid accent line sitting directly below the titlebar. Today that stripe is invisible because it abuts a titlebar painted the identical hex. Wanted: subtle tinted titlebar + visible bright 2px line below it. + +## Why + +1. **Pain point**: The shipped change writes the contrast-guarded full-hue accent (`stripeHex`) into the theme-color meta — via `setAccentThemeColor(hexes.stripeHex)` in the `InstanceAccentProvider` effect (`app/frontend/src/contexts/instance-accent-context.tsx:74-82`) and the `hex` field of the `runkit-instance-color` localStorage echo consumed verbatim by the `index.html` pre-paint script. Chrome therefore paints the entire installed-PWA titlebar in the fully saturated hue: a loud color band that dominates the window chrome. Worse, the 2px accent stripe rendered in `AppLayout` (`app/frontend/src/app.tsx:209-212`) is **invisible** — it abuts a titlebar painted the identical hex, so the deliberate accent line reads as part of the titlebar. +2. **Consequence of not fixing**: Every accent-colored instance has an aggressively colored titlebar (the opposite of the subtle instance-identity channel the mock designed), and the stripe surface shipped in 1etw does no visible work. The composition diverges from the reviewed and approved design. +3. **Why this approach**: The design mock's composition — a titlebar tinted with a subtle dark blend of the accent into the theme background (the mock's cyan instance used titlebar background `#142329` ≈ a 10-15% blend of the accent into `#0f1117`; **reference values only, not to be hardcoded**), with the full-brightness 2px stripe sitting directly below it as the vivid accent line. The user explicitly confirmed this composition today. It reuses the exact `blendHex` machinery the 6.5% top-bar wash already uses — one new named ratio constant, one new derived hex, no new color scheme. **Alternative rejected (implicitly, by confirming the mock)**: keeping the saturated titlebar and instead removing/relocating the stripe — the mock's whole point is the tinted-titlebar + bright-line pairing. + +## What Changes + +### 1. `deriveAccentHexes` gains a third derived hex — the meta/titlebar hex + +`deriveAccentHexes(value, theme)` in `app/frontend/src/instance-accent.ts` (currently returns `{ stripeHex, washHex } | null`, lines 72-83) gains a third field computed as a blend of the accent into the active theme's background: + +```ts +/** Ratio of the accent blended into the theme background for the PWA titlebar + * (theme-color meta) tint — mock parity ≈ 12%; a taste constant, trivially tunable. */ +export const INSTANCE_TITLEBAR_RATIO = 0.12; // ~0.12–0.15 band granted + +// inside deriveAccentHexes: +return { + stripeHex: adjustBorderForContrast(src, bg, theme.category === "dark", BORDER_MIN_CONTRAST), + washHex: blendHex(src, bg, INSTANCE_WASH_RATIO), + titlebarHex: blendHex(src, bg, INSTANCE_TITLEBAR_RATIO), +}; +``` + +- The constant is defined next to the existing `INSTANCE_WASH_RATIO = 0.065` (`instance-accent.ts:31`), same doc-comment style. +- The exact ratio is an acknowledged taste constant within the granted ~0.12–0.15 band (mock ≈ 12%), trivially tunable. +- Theme-aware by construction: the blend derives from `theme.palette.background` via the existing `blendHex` (`themes.ts:101`), so light themes get a light-background blend. No hardcoded hexes (constitution/code-quality conformance — same rule 1etw followed). + +### 2. The blended titlebar hex — NOT `stripeHex` — becomes the meta content and the echo `hex` + +In `InstanceAccentProvider` (`app/frontend/src/contexts/instance-accent-context.tsx`), the bridge effect (lines 74-82) switches both writes from `hexes.stripeHex` to the new titlebar hex: + +- (a) `setAccentThemeColor(hexes.titlebarHex)` — the single theme-color meta writer in `instance-accent.ts` now receives the wash-blend hex, so Chrome retints the installed-PWA titlebar to the subtle dark blend. +- (b) `writeInstanceColorEcho({ value: resolved, hex: hexes.titlebarHex })` — the echo's `hex` field carries the titlebar blend, so the `index.html` blocking pre-paint script (which applies the echoed hex **verbatim**, per the existing "Echo carries the precomputed meta hex" design decision in `fab/changes/260721-1etw-instance-accent-host-color/plan.md`) tints the titlebar with the wash on cold start **with no change to `index.html` itself**. + +Doc comments referencing "stripeHex … and the theme-color meta content" (e.g., `instance-accent.ts:67-71`, `instance-accent-context.tsx:27-29`) update to reflect the split: stripe/hostname surfaces keep `stripeHex`; the meta surface takes `titlebarHex`. + +Transient note: an echo written by the pre-change build still carries the full-hue hex; a cold start on the new build paints the old hex for the pre-fetch frame and self-corrects when the runtime resolution rewrites both meta and echo — the same accepted-transient class as the existing cross-mode-load note (1etw plan assumption #2). No migration needed. + +### 3. Unchanged — the other surfaces and the resolution chain + +- `stripeHex` (contrast-guarded full hue) stays as-is for the 2px top-bar stripe (`app.tsx:209-212`) and the HOST-panel hostname tint (`components/sidebar/host-panel.tsx`). +- `washHex` (6.5% blend, `INSTANCE_WASH_RATIO`) stays as-is for the top-bar background wash. Only the theme-color meta surface changes. +- With no accent resolved, the meta content remains the theme background (current single-writer behavior: `setAccentThemeColor(null)` → `lastBackground`) — unchanged. +- The no-default resolution chain (explicit setting → echo seed → none; hostname-hash fallback deliberately removed in commit 9864b2c) is NOT touched. +- `index.html` is NOT touched (see §2b). + +### 4. Tests to update + +- `app/frontend/src/instance-accent.test.ts` — `deriveAccentHexes` now returns a distinct meta/titlebar hex (extend the shape/derivation assertions at lines 53-76: titlebar hex is a valid `#rrggbb`, differs from both `stripeHex` and `washHex`, recomputes per palette); echo round-trip semantics carry the wash hex. +- `app/frontend/src/contexts/instance-accent-context.test.tsx` — the meta-content assertion currently expecting `stripeHex` (line 87-88: `expect(meta?.getAttribute("content")).toBe(screen.getByTestId("stripe").textContent)`) flips to assert the meta carries the titlebar hex and explicitly does NOT equal the stripe hex. +- No Playwright e2e touches the theme-color meta or instance-accent surfaces (verified: `grep -rn "theme-color|instance-color|instance-accent|stripeHex" app/frontend/tests/` → no matches), so unit-test coverage suffices; the existing e2e suite runs as the regression gate. + +## Affected Memory + +- `run-kit/ui-patterns`: (modify) instance-accent surfaces — the theme-color bridge's meta-hex derivation description (§ Theme-derived hexes, § PWA titlebar bridge, and the theme-color synchronization notes) changes from "stripeHex is … the theme-color meta content" to the three-hex split (stripe/wash/titlebar) with the new `INSTANCE_TITLEBAR_RATIO` +- `run-kit/architecture`: likely unaffected — no API change, no new endpoint, no backend impact (verify at hydrate; the architecture entry covers the settings field and endpoint pair, both untouched) + +## Impact + +- **Frontend-only (TS/React)**: `app/frontend/src/instance-accent.ts` (new constant + third derived hex + comment updates), `app/frontend/src/contexts/instance-accent-context.tsx` (two-line write switch + comment updates), `app/frontend/src/instance-accent.test.ts`, `app/frontend/src/contexts/instance-accent-context.test.tsx`. +- **No backend/Go impact, no API change, no new routes** (constitution Principles II/IV/IX untouched). No `index.html` change. No settings/storage change — the stored descriptor and endpoint pair are untouched; only the derived presentation hex on one surface changes. +- **Risk**: very low — a presentation-hex swap on one surface, fully covered by existing unit-test files; trivially reversible (one constant, two write sites). +- **Out of scope (explicit)**: dynamic `manifest.json` / tinted dock icons / Badging API (separate follow-up change `260722-eo8e-accent-dock-icon` already in flight in another worktree); reintroducing any default accent color; changing the stripe or wash surfaces. + +## Open Questions + +- None — the design conversation resolved all blocking decisions; remaining latitude is recorded as graded assumptions below. + +## Assumptions + +| # | Grade | Decision | Rationale | Scores | +|---|-------|----------|-----------|--------| +| 1 | Certain | Titlebar/meta hex = `blendHex(src, theme.palette.background, INSTANCE_TITLEBAR_RATIO)` as a third `deriveAccentHexes` field, replacing `stripeHex` in exactly two writes: the `setAccentThemeColor` meta content and the echo's `hex` field | Discussed — decisions 1-2 captured verbatim; mechanism verified against instance-accent.ts:72-83 and instance-accent-context.tsx:74-82 | S:95 R:85 A:90 D:95 | +| 2 | Confident | Exact ratio value 0.12 (mock ≈ 12%) within the granted ~0.12–0.15 band, as named constant `INSTANCE_TITLEBAR_RATIO` beside `INSTANCE_WASH_RATIO` | Taste constant with explicit user latitude ("acknowledged taste constant, trivially tunable"); one-line reversal; mirrors 1etw's wash-ratio latitude precedent | S:70 R:95 A:60 D:65 | +| 3 | Certain | `stripeHex` (stripe + HOST hostname tint) and `washHex` (top-bar wash) unchanged; no-accent meta stays theme background; resolution chain (explicit → echo → none) untouched | Decisions 3 and 5, explicit in conversation; verified current behavior in code | S:95 R:90 A:95 D:95 | +| 4 | Certain | `index.html` unchanged — the pre-paint script applies the echoed `hex` verbatim, so swapping the echoed value retints cold start for free | Decision 2b, explicit; rests on 1etw plan's "Echo carries the precomputed meta hex" design decision, verified at index.html:29-34 | S:90 R:90 A:90 D:90 | +| 5 | Confident | Third derived-hex field named `titlebarHex` (conversation fixed the constant name pattern via "e.g. `INSTANCE_TITLEBAR_RATIO`" but not the field name) | Naming-only latitude; consistent with the constant and the existing stripe/wash naming; trivially renameable | S:55 R:95 A:85 D:70 | +| 6 | Certain | Tests: update `instance-accent.test.ts` (three-hex shape, titlebar ≠ stripe/wash, per-palette recompute) and `instance-accent-context.test.tsx` (meta = titlebar hex, ≠ stripe hex); no new e2e | Decision 6, explicit; verified no e2e asserts theme-color/accent chrome (memory: e2e-assertions-on-ui-chrome grep done); code-quality test mandate | S:85 R:90 A:90 D:85 | +| 7 | Certain | Old-build echo carrying the full-hue hex is an accepted self-correcting cold-start transient — no echo migration/versioning | Same accepted-transient class 1etw already documents for cross-mode loads (plan assumption #2); runtime rewrites echo+meta on every load by design | S:75 R:90 A:90 D:85 | +| 8 | Certain | Out of scope: dynamic manifest / dock icons / Badging API (follow-up 260722-eo8e in flight elsewhere), no default accent reintroduction, no stripe/wash surface changes | Explicit in conversation | S:95 R:90 A:95 D:95 | +| 9 | Certain | Memory: `run-kit/ui-patterns` (modify) for the meta-hex derivation; `run-kit/architecture` untouched (no API change) | Affected-memory guidance explicit in the dispatch; ui-patterns §§ verified to describe the stripeHex-as-meta behavior being changed | S:85 R:90 A:90 D:85 | + +9 assumptions (7 certain, 2 confident, 0 tentative, 0 unresolved). diff --git a/fab/changes/260722-y5c3-instance-accent-titlebar-wash/plan.md b/fab/changes/260722-y5c3-instance-accent-titlebar-wash/plan.md new file mode 100644 index 00000000..c71f65c4 --- /dev/null +++ b/fab/changes/260722-y5c3-instance-accent-titlebar-wash/plan.md @@ -0,0 +1,121 @@ +# Plan: Instance-Accent PWA Titlebar Wash (Mock Parity) + +**Change**: 260722-y5c3-instance-accent-titlebar-wash +**Intake**: `intake.md` + +## Requirements + +### Frontend: Accent hex derivation + +#### R1: `deriveAccentHexes` gains a third derived hex — `titlebarHex` +`deriveAccentHexes(value, theme)` in `app/frontend/src/instance-accent.ts` SHALL return a third field `titlebarHex` computed as `blendHex(src, theme.palette.background, INSTANCE_TITLEBAR_RATIO)`, where `INSTANCE_TITLEBAR_RATIO = 0.12` is a new exported named constant defined beside the existing `INSTANCE_WASH_RATIO = 0.065` with the same doc-comment style (noting mock parity ≈ 12% and the granted ~0.12–0.15 tunable band). The blend MUST derive from the active theme's `palette.background` via the existing `blendHex` (`themes.ts`) — no hardcoded hexes; light themes get a light-background blend by construction. The function's return type annotation MUST include the new field. + +- **GIVEN** a resolvable accent descriptor and the active theme +- **WHEN** `deriveAccentHexes(value, theme)` runs +- **THEN** the result carries `{stripeHex, washHex, titlebarHex}` where `titlebarHex` is a valid `#rrggbb`, differs from both `stripeHex` (full contrast-guarded hue) and `washHex` (6.5% blend), and recomputes per palette (dark vs light differ) + +- **GIVEN** an unrecognized descriptor (e.g. `"99"`) +- **WHEN** `deriveAccentHexes` runs +- **THEN** the result is `null` (unchanged) + +#### R2: The titlebar hex — NOT `stripeHex` — becomes the meta content and the echo `hex` +The bridge effect in `InstanceAccentProvider` (`app/frontend/src/contexts/instance-accent-context.tsx`) SHALL switch both of its writes from `hexes.stripeHex` to `hexes.titlebarHex`: (a) `setAccentThemeColor(hexes.titlebarHex)` so the single theme-color meta writer receives the subtle dark blend, and (b) `writeInstanceColorEcho({ value: resolved, hex: hexes.titlebarHex })` so the `index.html` blocking pre-paint script — which applies the echoed `hex` verbatim — tints the titlebar with the wash on cold start with **no change to `index.html`**. + +- **GIVEN** a resolved accent under the active theme +- **WHEN** the bridge effect runs +- **THEN** `meta[name="theme-color"]` content equals the derived `titlebarHex` (and does NOT equal `stripeHex`), and the `runkit-instance-color` echo's `hex` field carries the same `titlebarHex` + +- **GIVEN** no accent resolved (authoritative null) +- **WHEN** the bridge effect runs +- **THEN** the meta content is the theme background and the echo is cleared (current behavior, unchanged) + +#### R3: All other surfaces and the resolution chain are untouched +`stripeHex` (contrast-guarded full hue) SHALL remain the hex for the 2px top-bar stripe (`app.tsx` AppLayout) and the HOST-panel hostname tint; `washHex` (`INSTANCE_WASH_RATIO` = 6.5%) SHALL remain the top-bar background wash. The no-default resolution chain (explicit setting → echo seed → none), the `useInstanceAccent()` exposed shape, and `index.html` SHALL NOT change. Doc comments that currently state "stripeHex … and the theme-color meta content" (`instance-accent.ts` `deriveAccentHexes` doc, `instance-accent-context.tsx` `stripeHex` field doc) MUST update to reflect the split: stripe/hostname surfaces keep `stripeHex`; the meta surface takes `titlebarHex`. + +- **GIVEN** the change is applied +- **WHEN** `git diff` is inspected +- **THEN** only `instance-accent.ts`, `instance-accent-context.tsx`, and their two test files are modified — no `index.html`, `app.tsx`, or `host-panel.tsx` edits + +### Frontend: Tests + +#### R4: Unit tests cover the three-hex split and the meta/echo switch +`app/frontend/src/instance-accent.test.ts` SHALL extend the `deriveAccentHexes` assertions: `titlebarHex` is a valid `#rrggbb`, differs from both `stripeHex` and `washHex`, and recomputes per palette. `app/frontend/src/contexts/instance-accent-context.test.tsx` SHALL flip the meta-content assertion from `stripeHex` to the titlebar hex: the meta carries the derived `titlebarHex`, explicitly does NOT equal the stripe hex, and the rewritten echo's `hex` matches the meta content. No new Playwright e2e (verified: no e2e touches theme-color/instance-accent surfaces); the existing suites run as the regression gate. + +- **GIVEN** the provider resolves an explicit accent in the context test +- **WHEN** the bridge effect settles +- **THEN** the test asserts `meta.content === deriveAccentHexes(value, theme).titlebarHex`, `meta.content !== stripeHex`, and `readInstanceColorEcho().hex === meta.content` + +### Non-Goals + +- Dynamic `manifest.json` / tinted dock icons / Badging API — separate follow-up change `260722-eo8e-accent-dock-icon` already in flight +- Reintroducing any default accent color — the no-default resolution chain stays +- Changing the stripe or wash surfaces, or exposing `titlebarHex` through `useInstanceAccent()` (no rendering surface consumes it) +- Echo migration/versioning — an old-build echo carrying the full-hue hex is an accepted self-correcting cold-start transient (same class as the existing cross-mode-load note) + +## Tasks + +### Phase 2: Core Implementation + +- [x] T001 In `app/frontend/src/instance-accent.ts`: add exported `INSTANCE_TITLEBAR_RATIO = 0.12` beside `INSTANCE_WASH_RATIO` (same doc-comment style, noting mock parity ≈ 12% and the ~0.12–0.15 band); add `titlebarHex: blendHex(src, bg, INSTANCE_TITLEBAR_RATIO)` to `deriveAccentHexes`'s return (and its return type annotation); update the `deriveAccentHexes` doc comment to the stripe/wash/titlebar split (meta content = `titlebarHex`) +- [x] T002 In `app/frontend/src/contexts/instance-accent-context.tsx`: switch the bridge effect's two writes from `hexes.stripeHex` to `hexes.titlebarHex` (`setAccentThemeColor` + `writeInstanceColorEcho` `hex`); update the `InstanceAccent.stripeHex` field doc comment to drop the "theme-color meta content" clause (stripe + HOST hostname only) +- [x] T003 [P] Extend `app/frontend/src/instance-accent.test.ts` `deriveAccentHexes` suite: `titlebarHex` matches `/^#[0-9a-f]{6}$/i`, differs from `stripeHex` and `washHex`, and recomputes per palette (dark vs light `titlebarHex` differ) +- [x] T004 [P] Update `app/frontend/src/contexts/instance-accent-context.test.tsx`: the meta-content assertion in "explicit setting wins over the localStorage echo" asserts meta = derived `titlebarHex`, meta ≠ stripe hex, and the rewritten echo `hex` = meta content + +### Phase 4: Verification + +- [x] T005 Run the frontend gates: `cd app/frontend && npx tsc --noEmit`, then the two scoped Vitest files (`pnpm vitest run src/instance-accent.test.ts src/contexts/instance-accent-context.test.tsx`), then the full unit suite via `just test-frontend`; fix any failures + +## Execution Order + +- T001 blocks T002 (the context reads the new field) and T003 +- T002 blocks T004 +- T003 and T004 are [P] against each other +- T005 last + +## Acceptance + +### Functional Completeness + +- [x] A-001 R1: `deriveAccentHexes` returns `{stripeHex, washHex, titlebarHex}` with `titlebarHex = blendHex(src, theme.palette.background, INSTANCE_TITLEBAR_RATIO)` and `INSTANCE_TITLEBAR_RATIO = 0.12` defined as a named exported constant beside `INSTANCE_WASH_RATIO`, same doc-comment style +- [x] A-002 R2: the provider's bridge effect writes `titlebarHex` (not `stripeHex`) to both `setAccentThemeColor` and the echo's `hex` field + +### Behavioral Correctness + +- [x] A-003 R2: with a resolved accent, `meta[name="theme-color"]` content equals the titlebar blend and does not equal the full-hue stripe hex; the echoed `hex` matches the meta content (cold-start tint via the unchanged pre-paint script) +- [x] A-004 R3: with no accent resolved, the meta content remains the theme background and the echo is cleared — behavior unchanged + +### Scenario Coverage + +- [x] A-005 R1: tests prove `titlebarHex` is a valid `#rrggbb`, distinct from both `stripeHex` and `washHex`, and theme-aware (dark vs light palettes yield different titlebar hexes) +- [x] A-006 R4: the context test asserts the meta = titlebar hex ≠ stripe hex and the echo round-trips the titlebar hex + +### Edge Cases & Error Handling + +- [x] A-007 R1: an unrecognized descriptor still yields `null` from `deriveAccentHexes` (no partial object) + +### Code Quality + +- [x] A-008 Pattern consistency: the new constant and field mirror the existing `INSTANCE_WASH_RATIO`/`washHex` naming, doc-comment, and derivation style +- [x] A-009 No unnecessary duplication: reuses `blendHex` from `themes.ts` — no new blend machinery, no hardcoded hexes (theme-aware by construction) +- [x] A-010 Tests included: both touched behaviors are covered by the two updated Vitest files (code-quality.md mandate; no e2e needed — verified none asserts theme-color/accent chrome) +- [x] A-011 Scope discipline: only the four named files change — `index.html`, `app.tsx`, `host-panel.tsx`, the resolution chain, and the `useInstanceAccent()` shape are untouched + +## Notes + +- Check items as you review: `- [x]` +- All acceptance items must pass before `/fab-continue` (hydrate) +- If an item is not applicable, mark checked and prefix with **N/A**: `- [x] A-NNN **N/A**: {reason}` + +## Deletion Candidates + +- None — this change replaces the meta/echo `hex` value in place (`stripeHex` → `titlebarHex` at the two bridge writes) and adds one constant + one derived field. `stripeHex` remains actively consumed by the 2px stripe (`app.tsx`) and HOST hostname tint (`host-panel.tsx`), so nothing is made redundant. No dead symbols, files, or branches result. + +## Assumptions + +| # | Grade | Decision | Rationale | Scores | +|---|-------|----------|-----------|--------| +| 1 | Certain | `titlebarHex` is NOT exposed through `useInstanceAccent()` / the `InstanceAccent` type — only the bridge effect consumes it | Intake specifies "two-line write switch" only; no rendering surface consumes the titlebar hex; smallest diff, trivially reversible | S:85 R:95 A:90 D:85 | +| 2 | Confident | Ratio value 0.12 (mock ≈ 12%) within the granted ~0.12–0.15 band | Carried verbatim from intake assumption #2 — acknowledged taste constant, one-line reversal | S:70 R:95 A:60 D:65 | +| 3 | Certain | Context-test equality assertion computes the expected hex via `deriveAccentHexes(value, DEFAULT_DARK_THEME).titlebarHex` (test env resolves system → dark via the mocked `matchMedia`) | Matches the existing test file's theme setup (matchMedia mocked dark, `#0f1117` meta seed); strongest available assertion for "meta carries the titlebar hex" | S:75 R:90 A:85 D:80 | + +3 assumptions (2 certain, 1 confident, 0 tentative). From 207102be4baf8ba8d432edba9e3f14fdc3ffd3c6 Mon Sep 17 00:00:00 2001 From: Sahil Ahuja Date: Wed, 22 Jul 2026 16:04:16 +0530 Subject: [PATCH 2/2] Update ship status and record PR URL --- .../.history.jsonl | 1 + .../.status.yaml | 34 ++++++++++--------- 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/fab/changes/260722-y5c3-instance-accent-titlebar-wash/.history.jsonl b/fab/changes/260722-y5c3-instance-accent-titlebar-wash/.history.jsonl index 4ddb817d..9d8019b2 100644 --- a/fab/changes/260722-y5c3-instance-accent-titlebar-wash/.history.jsonl +++ b/fab/changes/260722-y5c3-instance-accent-titlebar-wash/.history.jsonl @@ -8,3 +8,4 @@ {"action":"enter","driver":"fab-fff","event":"stage-transition","stage":"hydrate","ts":"2026-07-22T10:29:38Z"} {"event":"review","result":"passed","ts":"2026-07-22T10:29:38Z"} {"action":"enter","driver":"fab-fff","event":"stage-transition","stage":"ship","ts":"2026-07-22T10:32:41Z"} +{"action":"enter","driver":"git-pr","event":"stage-transition","stage":"review-pr","ts":"2026-07-22T10:34:10Z"} diff --git a/fab/changes/260722-y5c3-instance-accent-titlebar-wash/.status.yaml b/fab/changes/260722-y5c3-instance-accent-titlebar-wash/.status.yaml index 994e91de..4f8144b2 100644 --- a/fab/changes/260722-y5c3-instance-accent-titlebar-wash/.status.yaml +++ b/fab/changes/260722-y5c3-instance-accent-titlebar-wash/.status.yaml @@ -9,8 +9,8 @@ progress: apply: done review: done hydrate: done - ship: active - review-pr: pending + ship: done + review-pr: active plan: generated: true task_count: 5 @@ -33,22 +33,24 @@ stage_metrics: apply: {started_at: "2026-07-22T10:19:11Z", driver: fab-fff, iterations: 1, completed_at: "2026-07-22T10:25:18Z"} review: {started_at: "2026-07-22T10:25:18Z", driver: fab-fff, iterations: 1, completed_at: "2026-07-22T10:29:38Z"} hydrate: {started_at: "2026-07-22T10:29:38Z", driver: fab-fff, iterations: 1, completed_at: "2026-07-22T10:32:41Z"} - ship: {started_at: "2026-07-22T10:32:41Z", driver: fab-fff, iterations: 1} -prs: [] + ship: {started_at: "2026-07-22T10:32:41Z", driver: fab-fff, iterations: 1, completed_at: "2026-07-22T10:34:10Z"} + review-pr: {started_at: "2026-07-22T10:34:10Z", driver: git-pr, iterations: 1} +prs: + - https://github.com/sahil87/run-kit/pull/436 true_impact: - added: 0 - deleted: 0 - net: 0 + added: 327 + deleted: 18 + net: 309 excluding: - added: 0 - deleted: 0 - net: 0 + added: 36 + deleted: 14 + net: 22 tests: - added: 0 - deleted: 0 - net: 0 - computed_at: "2026-07-22T10:32:41Z" - computed_at_stage: hydrate + added: 7 + deleted: 1 + net: 6 + computed_at: "2026-07-22T10:34:10Z" + computed_at_stage: ship summary: instance-accent PWA titlebar now uses a subtle titlebarHex blend (INSTANCE_TITLEBAR_RATIO=0.12) as theme-color meta + echo, keeping the full-hue stripeHex 2px stripe visible below it # true_impact: lazily created on first stage-finish that computes it (no placeholder here). -last_updated: 2026-07-22T10:32:41Z +last_updated: 2026-07-22T10:34:10Z