diff --git a/docs/MAP.md b/docs/MAP.md
index 1c071ca7..5ade98c7 100644
--- a/docs/MAP.md
+++ b/docs/MAP.md
@@ -28,7 +28,7 @@ Rules live in `.claude/rules/`; depth docs are read-on-demand (`youcoded/docs/`,
| Perf lab / stress suite (dev-only) | `scripts/perf-lab/run.mjs` (one command, one JSON report)
`scripts/perf-lab/scenario-idle.mjs` and its siblings (per-surface scenarios)
`scripts/perf-lab/probe-ipc.mjs` (main-process stall detector)
`scripts/perf-lab/gpu.mjs` (which renderer the run actually got)
`scripts/perf-lab/layout-cost.mjs` (layouts per streamed token)
`scripts/perf-lab/late-content.mjs` (blank entries in view while scrolling)
`scripts/perf-lab/real-scale-startup.mjs` (launch + Resume against a copy-on-write COPY of the real history; `--profile` records a whole launch, `--real-look` adds the real theme/plugins)
`youcoded/desktop/src/main/perf-marks.ts` (the app-side marks it parses)
Ad-hoc CDP probes outside the suite: `scripts/measure-idle-cpu.mjs` (idle CPU burn, names the cause when it's high)
`scripts/resize-bench.mjs` (real viewport-resize cost — layout, frame gaps, long tasks)
`scripts/tool-arg-stream-probe.mjs` (whether a model provider streams tool-call argument deltas or buffers them) | (none — workspace tool) | `scripts/perf-lab/README.md` · `docs/active/handoffs/2026-08-27-perf-lab-session-status.md` | `node --test scripts/perf-lab/tests/*.test.mjs` (433 tests, 417 of them in CI, counted 2026-09-26 — `fixture.test.mjs` is excluded there, it downloads a 469 MB model; **`node --test
/` fails on Node 26**)
`youcoded/desktop/tests/perf-marks-placement.test.ts` pins the mark names the rig parses |
| Luna live cache-measurement rig (dev-only; spends ChatGPT-plan quota — ask first) | `scripts/luna/live-repeat-comparison.mjs` (plain turns)
`scripts/luna/live-tool-comparison.mjs` (tool loops)
`scripts/luna/live-lifecycle-comparison.mjs` (restart + resume + compaction)
`scripts/luna/request-gate.mjs` (per-request quota ceiling)
`scripts/luna/live-native-cdp.mjs` (isolated app over CDP, private HOME) | (none — workspace tool) | `scripts/luna/README.md` · `docs/active/investigations/2026-09-23-chatgpt-cache-affinity.md` | `node --test $(ls scripts/luna/*.test.mjs)` (offline, in CI) |
| Roadmap (the backlog itself) | `ROADMAP.md` (index + "Filing an item" — the token table)
`docs/roadmap/native-harness.md` and its 14 siblings (the backlogs; each opens with its `Filing test:`)
`docs/roadmap/shipped.md` (closed items, one line each, max 300 characters; older history in `docs/archive/roadmap/`)
`scripts/roadmap-check.mjs` (structure · claims · symptom pass · index; `--fix` before committing, `--close` to close an item in one step, `--vocab` prints every closed token list)
`.claude/hooks/roadmap-edit-check.mjs` (PostToolUse: hands structure errors back to the session that wrote them)
`scripts/fixtures/roadmap/` (the one fixture every test mutates) | (none — the vocabulary lives in `--vocab` and in ROADMAP.md, not in a rule) | `docs/archive/specs/2026-09-01-roadmap-restructure-design.md` §2–3 (grammar + vocabularies) | `node --test scripts/roadmap-check.test.mjs` (64 cases, incl. a guard that ROADMAP.md's token table and the validator's constants agree)
`node --test .claude/hooks/roadmap-edit-check.test.mjs` |
-| Desktop packaging + release (how a build becomes an installer) | `youcoded/desktop/electron-builder.yml` (targets, per-OS packaging, the `mac: identity` line)
`youcoded/.github/workflows/desktop-release.yml` (tag -> installers on the GitHub Release)
`youcoded/.github/workflows/desktop-test-build.yml` (manual beta; SAME build path, and since 2026-09-11 a master dispatch also runs the `sign` job — the release key over that beta's installers, refused unless it verifies against the key embedded in the app)
`youcoded/scripts/build-icons.mjs` (every app, installer and Android launcher icon, from `youcoded/desktop/assets/icon-mascot.svg` — never hand-edit the outputs) | (none) | `docs/build-and-release.md` (build order, version bumps, the dmg-inspection recipe, icons) | the `Verify the macOS bundle is signed` step in both workflows (fails the build if the packaged .app has no seal)
`node scripts/smoke-test.js` (launches the packaged build)
`youcoded/desktop/tests/app-icons.test.ts` (which icon file each platform reads — both fall back silently)
`youcoded/desktop/tests/installer-artifact-names.test.ts` (installer names vs the updater and website) |
+| Desktop packaging + release (how a build becomes an installer) | `youcoded/desktop/electron-builder.yml` (targets, per-OS packaging, the `mac: identity` line)
`youcoded/.github/workflows/desktop-release.yml` (tag -> installers on the GitHub Release)
`youcoded/.github/workflows/desktop-test-build.yml` (manual beta; SAME build path, and since 2026-09-11 a master dispatch also runs the `sign` job — the release key over that beta's installers, refused unless it verifies against the key embedded in the app)
`youcoded/scripts/build-icons.mjs` (every app, installer and Android launcher icon, from `youcoded/desktop/assets/icon-mascot.svg` — never hand-edit the outputs) | (none) | `docs/build-and-release.md` (build order, version bumps, the dmg-inspection recipe, icons) | the `Verify the macOS bundle is signed` step in both workflows (fails the build if the packaged .app has no seal)
`node scripts/smoke-test.js` (launches the packaged build)
`youcoded/desktop/tests/app-icons.test.ts` (which icon file each platform reads — both fall back silently)
`youcoded/desktop/tests/app-icon-runtime.test.ts` (the running app's taskbar/Dock icon matches it; `youcoded/desktop/src/main/app-icon.ts`)
`youcoded/desktop/tests/installer-artifact-names.test.ts` (installer names vs the updater and website) |
| In-app updates (the Update button, and what it refuses) | `youcoded/desktop/src/main/update-release-status.ts` (**is there an update, and which file for this computer** — one semver-aware compare for the check AND the install gate; the pill waits until the release carries this computer's installer, because one tag starts two workflows; `selectRelease` picks the highest VERSION out of a `/releases` listing, which is the only way a beta is ever offered another beta — `/releases/latest` omits pre-releases entirely)
`youcoded/desktop/src/main/update-settings.ts` (**the beta channel**, `~/.youcoded/config.json` → `updates.betaChannel`; unset is NOT off — an unasked install inherits whether its own build is a pre-release, else beta testers are stranded behind a toggle they never saw)
`youcoded/desktop/src/main/update-manifest-verify.ts` (`compareVersions` — a pre-release sorts BELOW its release, which is what lets a beta install 1.3.0 — plus the signature/hash/downgrade gate)
`youcoded/desktop/src/main/linux-install-kind.ts` (**how this Linux copy was installed** — `$APPIMAGE`, else the package manager owning `process.execPath`; the picker takes ONE suffix per kind with no fallback, because offering a pacman install the AppImage was a 180 MB download it could not apply)
`youcoded/desktop/src/main/update-installer.ts` (allowed hosts, safe filenames, download, per-platform launch — a system package installs through `pkexec`, then the app relaunches; no polkit agent means the exact command with Copy and Run in terminal)
`youcoded/desktop/src/main/update-signing-key.ts` (the embedded public key)
`youcoded/desktop/scripts/generate-release-manifest.mjs` (what CI signs; `--verify-with` writes nothing unless it verifies against that key)
`youcoded/desktop/src/renderer/components/UpdatePanel.tsx` | (none) | `docs/build-and-release.md` → How the app orders versions | `youcoded/desktop/tests/update-release-status.test.ts` (beta → newer beta → full release, end to end through the signed manifest)
`youcoded/desktop/tests/update-settings.test.ts` (the beta channel's persistence)
`youcoded/desktop/tests/update-manifest-verify.test.ts`
`youcoded/desktop/tests/release-manifest-roundtrip.test.ts` (the CLI CI runs, incl. the wrong-key refusal)
`youcoded/desktop/tests/update-installer.test.ts`
`youcoded/desktop/tests/linux-install-kind.test.ts` |
| App logging + bug reports (what the app leaves behind) | `youcoded/desktop/src/main/logger.ts` (**the log is `~/.claude/desktop.log`, NOT under the app's own folder** — last 500 lines only)
`youcoded/desktop/src/main/dev-tools.ts` (`readLogTail` redacts it, `buildIssueBody` attaches it to Report-a-bug)
`youcoded/desktop/src/main/crash-diagnostics.ts` (crashes, dead helper processes and hung windows write into that same log; crash files stay on the machine) | (none) | `docs/error-message-standards.md` | `youcoded/desktop/tests/dev-tools.test.ts` · `crash-diagnostics.test.ts` |
| Session workspace startup + reorientation (dev-only) | `scripts/workspace-start.mjs`
`scripts/workspace-sync.mjs` (shared sync/report policy)
`scripts/workspace-sync.sh` (maintenance compatibility entry point)
`.claude/hooks/context-inject.sh` (read-only startup reminder)
`scripts/workspace-repos.json` (shared with `setup.sh`)
`scripts/git-hooks/pre-commit` (**refuses commits in EVERY shared clone**; installed per repo by `setup.sh`) | (none) | `docs/workspace-start.md` | `node --test scripts/workspace-start.test.mjs scripts/workspace-sync.test.mjs scripts/workspace-drift-guards.test.mjs .claude/hooks/context-inject.test.mjs` |
diff --git a/docs/archive/design/2026-09-27-mac-dock-icon/mac-dock-icon.review.json b/docs/archive/design/2026-09-27-mac-dock-icon/mac-dock-icon.review.json
new file mode 100644
index 00000000..fd9ed86b
--- /dev/null
+++ b/docs/archive/design/2026-09-27-mac-dock-icon/mac-dock-icon.review.json
@@ -0,0 +1,25 @@
+{
+ "title": "Mac Dock icon — back to the right size",
+ "key": "mac-dock-icon-review",
+ "out": "mac-dock-icon.review.html",
+ "stage": "review",
+ "images": "images/mac-dock-icon.review",
+ "runs": { "before": "runs/before", "after": "runs/after" },
+ "labels": { "before": "Today (1.3.0)", "after": "With this fix" },
+ "crops": { "dock": ["dock", "dock", "820x200+0+0"] },
+ "steps": [
+ {
+ "id": "D-1",
+ "surface": "Mac Dock",
+ "path": "The Dock at the bottom of a Mac screen, a second after YouCoded opens",
+ "crop": "dock",
+ "themes": ["light"],
+ "headline": "On a Mac, YouCoded's Dock icon now matches the size of the apps beside it.",
+ "changed": "When the app opened, it swapped its Dock icon for the Windows version, which fills the whole square. It now uses the Mac version, the one the installer already shows while the app is closed. Gray squares are stand-ins for other apps.",
+ "measured": "Icon body 80% of its square instead of 100%, same as the installed Mac icon",
+ "notice": "Mac users no longer see the icon jump bigger right after launch. It stays the same size the whole time.",
+ "risk": "A theme that brings its own icon gets shrunk to the same size automatically. No theme has its own icon today, so nobody sees that part yet.",
+ "highlight": { "box": [40, 15, 20, 72] }
+ }
+ ]
+}
diff --git a/docs/archive/design/2026-09-27-mac-dock-icon/runs/after/shots-dock/light/dock.png b/docs/archive/design/2026-09-27-mac-dock-icon/runs/after/shots-dock/light/dock.png
new file mode 100644
index 00000000..bbccd28a
Binary files /dev/null and b/docs/archive/design/2026-09-27-mac-dock-icon/runs/after/shots-dock/light/dock.png differ
diff --git a/docs/archive/design/2026-09-27-mac-dock-icon/runs/before/shots-dock/light/dock.png b/docs/archive/design/2026-09-27-mac-dock-icon/runs/before/shots-dock/light/dock.png
new file mode 100644
index 00000000..5477d8df
Binary files /dev/null and b/docs/archive/design/2026-09-27-mac-dock-icon/runs/before/shots-dock/light/dock.png differ
diff --git a/docs/build-and-release.md b/docs/build-and-release.md
index a874c07d..0433c284 100644
--- a/docs/build-and-release.md
+++ b/docs/build-and-release.md
@@ -281,8 +281,14 @@ files and the Android `mipmap-*` layers from it (needs `rsvg-convert`, `magick`,
Pillow). The design rounds and the one-off generator that made the mascot drawing are in
`docs/archive/design/2026-09-10-app-icon/`. `desktop/tests/app-icons.test.ts` pins which file each
platform reads, because electron-builder and Android both fall back to a default icon silently.
+The RUNNING app resets its taskbar/Dock icon on every theme load, so `desktop/src/main/app-icon.ts`
+picks the same file per platform (`icon-mac.png` is the `.icns`'s PNG twin) and shrinks any
+edge-to-edge icon — a theme's included — onto Apple's grid before it reaches the Dock;
+`desktop/tests/app-icon-runtime.test.ts` pins that. Resetting to `icon.png` made the Mac Dock icon
+oversized until 2026-09-27.
+
**macOS ships two dmgs, and the x64 one is built on an arm64 runner.** `electron-builder.yml`
targets both `x64` and `arm64`, but both workflows run on `macos-latest` (Apple Silicon) and cut
diff --git a/docs/roadmap/themes.md b/docs/roadmap/themes.md
index 51cf58c7..43cc0ebf 100644
--- a/docs/roadmap/themes.md
+++ b/docs/roadmap/themes.md
@@ -45,7 +45,8 @@ here: installing or browsing themes (marketplace).
- [ ] Destin's ask (2026-09-10): the app's taskbar and Dock icon should change to match the
theme, drawn from the new robot icon. Until then, every theme shows the same lavender robot
- icon, because the old theme matching redrew the retired "YC" square
+ icon, because the old theme matching redrew the retired "YC" square. The Mac Dock
+ already shrinks edge-to-edge theme art onto Apple's grid (app-icon.ts, 2026-09-27)
`window-chrome` `desktop` `parked` `checked 2026-09-10`
- [ ] A theme's icon overrides are accepted, and the Library shows a "custom icons" badge for