Skip to content

feat(app): choose the picture atop a new chat, or use your own, from new painted templates - #110

Merged
sean-brydon merged 2 commits into
mainfrom
feat/header-pictures
Oct 14, 2026
Merged

sean-brydon merged 2 commits into
mainfrom
feat/header-pictures

Conversation

@sean-brydon

Copy link
Copy Markdown
Collaborator

The painting across the top of a new chat, an empty worktree and Home was always the harbour. Now it's a choice in Settings › Appearance › Header picture: the harbour (default, by time of day as before), six new painted plates, your own picture, or none. The plates are also chat background scenes.

The plates. Six watercolours in the harbour's style: Alpine lake, Meadow, Desert mesa, Snowy fjord, Lavender fields and Sea of clouds.

  • Each has a landmark on the far left, a clear centre and a paler bottom for the UI.
  • Each is a day/night pair, like the harbour: dark themes get the night painting. The day plates were generated with Codex from the harbour day plate. The nights were generated with Codex from each day plate, using harbour/night.webp as the style reference.
  • They're WebP at 1672×941: 51–128 KB by day, 96–173 KB at night.
  • All 12 are separate hashed assets that load only when shown; nothing is inlined into JS.

The six plates, day and night

How

  • Pref: headerPicture: { source: "builtin" | "image" | "none", builtin: "harbour" | PlateId, image?, position }.
    • normalizeHeaderPicture (lib/header-picture.ts) turns old or missing prefs, unknown plates and lost pictures into the harbour, so nothing changes for anyone who hasn't chosen.
    • headerArt() is a pure function that picks the painting, mute, dim and position.
  • One place to draw it: useHeaderPicture() and HeaderBand (components/art/header-band.tsx) draw all three headers: the launcher, the empty conversation's first prompt, and Home. They replace the direct HARBOUR[light] uses, so the three can't drift. With None, each screen drops the band and its large top padding.
  • Your own picture reuses the chat background's pipeline and IndexedDB store.
    • The two choices are independent, but the kept pictures are shared.
    • keepImage now takes an inUse list, so it never drops a picture either setting uses. Removing one resets whichever setting used it.
    • In dark themes, your own (daylight) picture is dimmed: a new dim on DitherBand, 0.5, plus mute 0.2, chosen after comparing settings in berth-dark and dracula.
  • The picker: tiles are drawn as the header draws them, so a dark theme shows the night thumbnails. You can drop a file on the section, and Top/Center/Bottom shows for your own picture.

Proof: the app on mock data.

Light Dark
The picker
Lavender fields
Alpine lake
Your own picture
None
A plate as the chat background

Every plate in Catppuccin Latte, Dracula and Tokyo Night, plus Home: all screenshots.

Tests

  • lib/header-picture.test.ts (8 tests): old or missing prefs, invalid values, the harbour following its light, plates switching to night, your own picture's dim and mute, None and a lost picture.
  • e2e/header-picture.spec.ts (5 tests):
    • a plate on a new chat, Home and the launcher, kept across a reload;
    • a dark theme loading the night file;
    • an uploaded PNG as the header, its position saved, and the chat background left alone;
    • None, kept across a reload;
    • a plate as the chat background, with the header choice left alone.
  • The mock fixtures gain ?scene=first-prompt (agents at rest open to their first prompt), so the spec can reach the empty conversation.

Checks

  • tsc, typecheck:plugins, check:titles, check:csp, check:themes, pnpm test (452 tests), pnpm build, npx tsc -p e2e and the docs build pass.
  • header-picture.spec.ts and look.spec.ts pass (12 tests, re-run by me on a fresh build; the author ran them 3× as well).
  • first-run.spec.ts:36 (Labs on and off) fails, on main as well: getByTestId('settings-labs').getByRole('switch') resolved to 2 elements. This PR doesn't touch it.

Docs: a Header picture section in guides/themes.mdx, the plates on its Scenes line, a link from guides/labs.mdx, and two changelog bullets.

Open questions

  • Header picture comes after Chat background in Appearance. It could go before.
  • With None, the launcher and Home show no art. They don't fall back to the small line drawings shown when Labs is off.
  • Both pickers list the same kept pictures.

🤖 Generated with Claude Code

…new painted templates

Settings › Appearance › Header picture picks the painting across the top of
a new chat, an empty worktree and Home: the harbour (the default, so nothing
changes for existing prefs), six new plates with a day and a night painting
each, a picture of your own (kept with the chat background's pictures but
chosen apart), or none. A dark theme shows a plate's night; a picture of
your own is dimmed there. The plates are chat background scenes too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@vercel

vercel Bot commented Oct 14, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
berth Ready Ready Preview Oct 14, 2026 5:05pm UTC
berth-docs Ready Ready Preview Oct 14, 2026 5:05pm UTC

Request Review

@sean-brydon
sean-brydon merged commit 3077ed8 into main Oct 14, 2026
2 of 3 checks passed
@linear-code

linear-code Bot commented Oct 14, 2026

Copy link
Copy Markdown

ENG-6097

This branch was successfully deployed

2 active deployments
Preview – berth-docs — 3dba1d4a Deployed Oct 14, 2026 by vercel[bot]
Preview – berth — 3dba1d4a Deployed Oct 14, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant