# Harness compatibility contract The wallpaper is a fixed layer *behind* the Harness shell, so it only works while the surfaces that carry the app paint nothing opaque over it. Two regressions came from exactly that boundary — one colour report ("the whale is barely visible") each time a Harness release moved a hook this plugin matched. Everything below is verified against the shipped `lib/client.js` by `npm run check`, and re-checked in the running page by the shell self-check. ## Hooks this plugin relies on | Hook | Selector / API | Holds for | If it moves | | --- | --- | --- | --- | | Shell frame stays clear | `#root > [data-slot="root"] > *` | 0.1.2-rc.1 … 0.1.6-alpha.1 | The frame paints `--dsw-alias-bg-base` across the viewport again and dims the whole wallpaper. Harness ≤ 0.1.2 exposed the same element as `[data-details-collapsed]`; 0.1.5 dropped the attribute, so the frame is matched structurally. | | Conversation column | `[data-slot="main.conversation"] > [data-phase]` (≥ 0.1.5) and `[data-slot="conversation"] > [data-phase]` (≤ 0.1.2) | 0.1.2-rc.1 … 0.1.6-alpha.1 | The column falls back to its own `--dsw-alias-bg-base` background: welcome screen stays fine, running text sits on the dot matrix. | | Conversation phase | `data-phase` on that element: `hero` (welcome / blank session), `active`, `settling` | 0.1.5 … 0.1.6-alpha.1 | The scrim/clear split breaks: either the welcome screen gets scrimmed or reading loses protection. Never match bare `[data-phase]` — the composer input carries its own `plain` phase. | | Dark palette | `body[data-ds-dark-theme]` | 0.1.2-rc.1 … 0.1.6-alpha.1 | Theme sync breaks: the canvas keeps the wrong ink curve. The plugin also reads `ctx.theme.getTheme().active.colorScheme` and `theme/change`, which are the primary signals. | | Composer card | `[data-composer-card="true"]` | 0.1.2-rc.1 … 0.1.6-alpha.1 | The composer loses its wallpaper-matched surface and reads as a raw app default. | | Token override layer | `ctx.theme.overrideTokens(source, tokens)` folded into the snapshot, applied by ui-layout as inline styles on `body` | 0.1.2-rc.1 … 0.1.6-alpha.1 | Surfaces stop being translucent, which is what makes the wallpaper visible through the app at all. A bare string token value throws a teaching error by design; pass `{ light, dark }`. | | Client bundle loading | `dsh.client` manifest + `exports["./client"]` → `lib/client.js`, read at plugin activation; the HMR row stat-polls the bundle and re-issues it under a content-sha1 `rev` | 0.1.2-rc.1 … 0.1.6-alpha.1 | Rebuilding `lib/` has no effect until the plugin is re-activated or the poll notices the change; the page then needs a reload to fetch the new revision. | ## Self-check `src/self-check.ts` re-validates the shell contract 1.5 s after mount and on every conversation phase change. If a surface starts painting where it should not, the console gets one warning per build/scheme/problem, for example: ``` [harness-whale 0.3.5] the Harness shell is covering the wallpaper again: - #root > [data-slot="root"] > * paints rgba(244, 248, 255, 0.72); the wallpaper behind it is covered ``` Run the same report on demand from the page console: ```js window.__harnessWhale.check() // { version, colorScheme, renderer, canvas, layerFound, surfaces: [...], problems: [...], ok } ``` What it asserts: - the shell frame and `#root` are transparent (alpha ≤ 0.02); - the conversation column is transparent on `hero` and carries a scrim of 0.30–0.85 alpha in every content phase; - the wallpaper layer and its canvas exist and have a drawing buffer; - both conversation slot spellings are still present, so a rename is reported instead of silently ignored. `npm run check` asserts the same contract on the built bundle, plus a palette floor: the modelled dot contrast at idle / session / active must stay above 200 / 160 / 120 luma in both themes, or the build fails. That floor is the "never pale again" tripwire. ## When a Harness update lands 1. **Before restarting the GUI** — `npm run compat` (or `node scripts/dsh-compat.mjs --dsh