0.32.22026-10-23
Download page: publish all that is built
- `tools/publish-downloads.sh --list` finds what is built (Android launcher and store APKs, release before debug; CoolDash OS `sdcard.img` / `disk.img` of `os/output/<board>/images/`, the `.img.xz` when it is newer); `<destination> --all` publishes all of it, compressing `.img` to `.img.xz` first (WEB-17, SITE-13).
- `menu.sh`: *Put what is built on the Download page* lists the files found (size, debug builds marked) with All that is built on top, one file, or another file.
- Tested: `--list` and `--all` with the real debug APKs and a fake OS image (compressed, published, then found as `.img.xz`); the menu's list in a simulated terminal.
0.32.12026-10-23
menu.sh
- `menu.sh` at the root (also in the public copy): arrow keys, Enter, `r`, `q`; sub-choices with arrows (app, board, extension, service, version part). The top shows each service, its address and whether it answers (host, dev servers; with `deploy/`: the online website and cloud here and online, the database's health, the home host), refreshed every 15 s.
- Actions: dev servers in the background (logs and PIDs in `.menu/`, gitignored), install, unit tests, type-check, browser tests, build everything, the host image, Android APKs, CoolDash OS, pack or create an extension, the host with Docker Compose (up, down, logs), publish the host image on Docker Hub (multi-platform builder, ARM emulation asked first), publish an extension, raise the version; with `deploy/`: deploy all or one service, logs, stop, Download page files.
- Tested in a simulated terminal (status, moving, a sub-choice and Esc back).
0.32.02026-10-23
Drafts, project pages and reviewed pages
- Drafts: a project's first upload is `draft`; the answer has `submitUrl` (`/dev/<id>`), printed by `cooldash publish`. `POST /v1/dev/items/:id/submit` sends it to review with its page.
- Every version is reviewed (reasons say what to look at; *new version* otherwise). No link for an existing project.
- Pages are reviewed (migration 13: `page_pending`, `pending_*`, `page_note`): approving a version makes its page live; a page change alone is a queue entry (`/page/approve`, `/page/reject`). On accept, images the live page no longer shows are deleted; on refuse, the refused page's own images are deleted and the creator sees the note.
- Panel: `/dev` lists all projects; `/dev/<id>` manages one (page, Submit for review, versions with the reviewer's notes, listed, stats, ratings). The review page shows the page with a preview. The character maker submits new characters with a page. Project pages show the package icon as the default logo.
- `tools/e2e.sh`: `CD_DEBUG=pw:api` passes Playwright's debug log through.
- Tests: cloud 32, CLI 5, panel e2e 4 (publish) + 6. Deployed.
0.31.02026-10-23
Item pages filled on submit
- MKT-12 (Listing): migration 12 (`details`, `github_url`, `logo_media`, `market_media`); `POST /v1/dev/media`, `GET /v1/market/media/:id`, `PUT /v1/dev/items/:id/listing`; the summary's icon is the chosen logo, with `github` and `details`.
- `packages/schemas/src/listing.ts`: the rules and a safe small-Markdown renderer (escaped first; images only from the cloud's media; https links).
- Panel: the developer page's submit form (package read in the browser; logo; description with pictures placed at the cursor and a preview; GitHub; what's new) and Edit the page; the item page shows it. Website: the catalogue panel shows it.
- Tests: schemas (rules, renderer, XSS attempts), cloud (media, listing, ownership), panel e2e (submit, review, public page). Deployed.
0.30.02026-10-23
Character maker finds extension keys and publishes
- CHR-08 the Sprite Pack Builder collects the `characterActions` of every extension in the home library and on the public marketplace (`detectKeys`) and adds the missing ones to the pack by themselves (`addDetected`), marked with the extensions that ask. The cloud's catalogue now includes `characterActions`.
- Save… asks where: the library (and TVs) and/or the marketplace (changelog, review). Publishing works in the cloud panel, also with a home opened through the cloud (`/v1/dev` and `/v1/review` stay on the cloud in `apiUrl`).
- The builder is in the panel's top bar (Characters), on the host and on the cloud.
- Tests: `pack.test.ts` (detection), `webapp.spec.ts` (save dialog on a host: publishing off), `publish.spec.ts` (cloud: a published extension asks for *cheer*, the builder adds it, the character goes to review).
0.29.12026-10-23
The home panel from anywhere with live updates
- That is the cloud link (WEB-14), already working: the cloud panel opens a linked home and every call goes through the tunnel to the host. Checked again after the split.
- Live events through the tunnel: the panel opened through the cloud follows each TV by long polling (`/v1/devices/<id>/events/poll`, host side; Cloud link): the TV preview, online status and install progress now update remotely too. The host keeps the last 50 events per TV.
- The host panel suggests the cloud it is set up with (`COOLDASH_MARKET_CLOUD`) instead of a placeholder address.
- Test: `apps/cloud/src/cloud-link.test.ts` sends a command to a TV and waits for its event, from the cloud panel through the tunnel.
- Deployed both images.
0.29.02026-10-23
Host and Cloud as two programs
- ADR-010 Host and Cloud as two programs: `apps/cloud` renamed `apps/host` (history kept); a new `apps/cloud` built on it.
- Host (`@cooldash/host`): panel, usernames, passkeys, TVs, relay, library + installs, uploads, analysis, Wake-on-LAN, background tasks, mDNS, the cloud link (host's end), `routes/market-host.ts` (the cloud's catalogue, items through the linked account, the TV store, killed versions), `routes/session.ts`. `createApp(d, parts)` and `startServer({ plugin })` take extension points. Its config lost the cloud's settings and the mailer.
- Cloud (`@cooldash/cloud`): `server.ts` starts the host's server as "cloud" with a plugin: accounts, public marketplace + review + developer panel, `host-tunnel.ts`, `account-link.ts`, public config, `/os`, `/downloads`; `context.ts` adds its settings and the mailer.
- Images: `Dockerfile` = the host only; `deploy/cloud/Dockerfile` = `cooldash-cloud`. The online stack runs `CLOUD_IMAGE` and `HOST_IMAGE`; the release workflow publishes both. Deployed on this server.
- Tests: host 19, cloud 32 (same as before the split), panel e2e 7 against the two servers.
0.28.12026-10-23
Docs on the app and the website as home
- `app.cooldash.duckdns.org/`: signed out → the website; signed in → My TVs (SITE-01, WEB-17).
- The website's Use / Create (top bar, mobile menu, footer, home, catalogue) open the panel's docs; the site image forwards `/docs/use/*` and `/docs/create/*` there; the zips stay on the site (SITE-02, SITE-15).
- Deployed. `app.cooldash.duckdns.org` answers over HTTPS through NPM.
0.28.02026-10-23
Accounts, the TV store, extension pages
- WEB-18 accounts on the cloud (Accounts): sign up, sign in (5 tries then 1 minute), confirmation email, forgot password (30-minute link; other sessions end), Google sign-in (code + PKCE, state cookie; same email = same account), set or change the password. Migration 11 (`google_sub`, `email_verified`, `name`, link `purpose`).
- Downloads need an account: `/v1/market/items/:id/package` takes a session, a `Device` token or a `Host` token; a home host gets items only once linked to an account.
- The store on the TV: Settings › Marketplace (browse by kind, newest or top rated, item page, Get it); `POST /v1/market/items/:id/tv-install` (Device token) adds to the home's library and installs on that TV. Linking the TV is the pairing QR code.
- Panel: sign-in page with Sign in / Create an account / Continue with Google / Forgot password / Email link; `/signin/reset`; Account shows the confirmed email and sets a password; item pages send signed-out visitors to sign up and back.
- WEB-19: the panel's Extensions tab is a list; each extension has its own page (on/off, settings, permissions, remove). TV command `ext.remove` for the TV as its own host.
- SITE-11 demo videos recorded again (the first encode failed: xfade needs a constant frame rate after `trim`); raw frames now kept in `apps/site/.demos-raw` (`tools/site-demos.sh --encode`).
- `tools/publish-downloads.sh`: APKs and OS images to the Download page, `downloads.json` with SHA-256.
- Email: the cloud sends from `no-reply@cooldash.duckdns.org` through the owner's Stalwart (`mail.coolroom.app:465`). DuckDNS gives a name one TXT record, returned for every sub-name too: it holds SPF (`v=spf1 ip4:82.65.115.85 -all`); DKIM is off for that domain (its record can't exist), no DMARC. Test email delivered to Gmail.
- Deployed: cloud (accounts, Google keys set by the owner) and website (videos) on this server.
- Site: the mobile menu's class renamed `menu-sheet` (it shared `.sheet` with the catalogue panel).
0.27.02026-10-23
Online stack behind NPM and the Astro website
- WEB-17 online stack (`deploy/`): website, cloud (Postgres), db, home host. No Caddy in front: the owner's Nginx Proxy Manager does the domains and HTTPS. The site and the cloud publish `BIND_ADDRESS:SITE_PORT` / `CLOUD_PORT` (here `192.168.1.101:4410` and `:4411`, like the server's other stacks); optional `deploy/npm-network.yml` joins NPM's Docker network instead. The routes: Behind Nginx Proxy Manager.
- Deployed on this server as compose project `cooldash`: `https://cooldash.duckdns.org` (website) and `https://app.cooldash.duckdns.org` (panel, API); home host on the LAN at `:4412` (8787 is the dev preview container). Images built locally (`ghcr.io/gandalf63/cooldash`, `cooldash-site`), not pushed yet.
- `/v1/public-config` moved before the sign-in middleware (it answered "sign in first"); test added in `host.test.ts`.
- Session cookie `Secure` also when the proxy says `X-Forwarded-Proto: https`.
- The live demo (SITE-14) is served by the website's image at `/dash/` (same origin), because NPM sends `X-Frame-Options`.
- Root `.env.example`: `VITE_CLOUD_URL` / `VITE_WEB_URL` removed (built apps read URLs at runtime).
- SITE-10…SITE-15 the Astro website (`apps/site`): pages, motion system, catalogue island, downloads page, docs and zips, recorded demos (`tools/site-demos.sh`). `astro check`: 0 errors (needs TypeScript 6, like the Svelte apps).
- Third-party docs registered: Astro, GSAP, Lenis, React Bits, Nginx Proxy Manager (new).
- Feature IDs renamed: the deployment and accounts features were first written as PLT-14 / PLT-15 (commit 0.26.1+42 and its build log); they belong to the cloud section and are now WEB-17 and WEB-18. The old IDs were never used for anything else.
0.26.12026-10-22
Phase 12 started
- Plan: Phase 12 rewritten with the owner's milestones; Play Store struck out (Phase 12).
- ADR-009 Public website in Astro.
- Third-party docs downloaded: Docker Compose (variables, services, buildx), GitHub Container registry and releases, Caddy (reverse proxy, automatic HTTPS), Google sign-in (OAuth 2.0, OpenID Connect), Nodemailer (SMTP).
- `CURRENT.md`: Phase 12 status and what is written but not committed.
0.26.02026-10-22
Logo everywhere and credits
- DS-09: `@cooldash/ui` gets `<cd-logo kind="symbol|lockup">` and `logoSvg()` (current colour), with the geometry generated from the master (`brand-paths.ts`).
- TV: logo at the top of the sidebar; logo with name on the setup's first and last steps; Settings › About: logo and a Credits row ("CoolDash, made by Gandalf63"); favicon.
- Panel and website: logo in the top bar; footer "CoolDash, made by Gandalf63" on every page; docs sites' header; favicon, PNG icons (32, 180, 192, 512), web manifest; the landing plays the animated logo above its title.
- Android TV: launcher icon and TV banner are now the logo (vector drawables; they were placeholder blocks).
- `README.md`: logo and Credits; root `package.json`: `"author": "Gandalf63"`. The README no longer calls CoolDash OS "planned".
- The exporter (`marketing/logo/tools/export.sh`) writes all of these, so a logo change updates every app.
0.25.02026-10-22
Boot screen
- PLT-13 (new): psplash from early boot (the C on `#121212`), the loop inlined in the Dash's `index.html` from the first paint, and at "ready" the end: letters fade, the logo centres the C's screen, the dash lights up inside it, zoom until it is the TV. Quiet boot (no rainbow, no kernel logo, no cursor). The agent quits psplash on `dash.ready`.
- Generator: `build_exports.py` also writes `apps/dash/src/boot/boot-splash.html`, `boot-timing.json` and `os/board/cooldash/common/psplash.png`.
- Preview: `marketing/logo/animation/boot-preview.webm` (1280 × 720) and `filmstrips/boot.png`.
0.24.02026-10-22
Logo loop
- DS-09: loop versions next to the current files: `cooldash-<symbol|lockup>-<dark|light>-loop.svg` (C drawn once, then the cycle forever) and `-loop.gif` (the cycle, seamless). The dash fades out; the letters lean out forward, drifting right and up, c first, faster than they came in. Preview `marketing/logo/animation/loop.html`; filmstrips `loop-lockup.png`, `loop-symbol.png`.
- GIF timing fix for every GIF: 25 fps instead of 30 (GIF stores frame times in 1/100 s; 33 ms became 30 ms, 10 % fast).
0.23.02026-10-22
Logo files
- DS-09: `marketing/logo/exports/{dark,light}/` with, for the symbol and the lockup: static SVG, animated SVG, PNGs (symbol 16, 32, 48, 64, 128, 180, 192, 256, 512, 1024; lockup 64–1024 high), animated GIF. 42 files, 1.2 MB. README inside.
- Exporter: `marketing/logo/tools/export.sh` → `build_exports.py` (writes the SVGs, reuses the motion's geometry and timings from `build_animation.py`) → `render.mjs` in the Playwright image (PNG screenshots, GIF frames seeked at 30 fps) → Pillow assembles the GIFs.
0.22.52026-10-22
Logo motion letters after the dash
- DS-09: each letter now waits until the dash's tail is 18 px past where the letter starts (it starts 70 px right of its place), and until the last afterimage (40 ms behind the dash) has passed too. The dash keeps speeding up, so the gap only grows: nothing overlaps. Filmstrip regenerated.
0.22.42026-10-22
Logo motion dash from the right
- DS-09: in the chosen motion the dash now enters from beyond the word's right end and slides left into the C, accelerating, with afterimages; its impact knocks the C back. The letters appear in its wake (each one as the dash passes it, h first) and lean in. Filmstrip regenerated.
0.22.32026-10-22
Logo motion chosen
- DS-09: the chosen motion as the first card of `marketing/logo/animation/index.html`: C drawn top to bottom; the dash pulls back, bursts out with a stretch and two afterimages; the C is knocked back 16 px and settles; the letters lean in behind the dash. Filmstrip `animation/filmstrips/final.png`. The other proposals stay below.
0.22.22026-10-22
Logo motion proposals
- DS-09: `marketing/logo/animation/index.html`, five proposals (Breeze, Lean in, The dash is the wind, Traced, Ripple) with Replay buttons; filmstrips of each in `animation/filmstrips/`; generator `marketing/logo/tools/build_animation.py` (reuses the concept geometry).
- The C draws along its own path from the top end to the bottom end; the dash then draws out of the screen. In "The dash is the wind", each letter's reveal time is computed from the dash's easing curve, so letters appear exactly as the dash passes.
0.22.12026-10-22
Logo concepts
- DS-09 (new, in progress): three black concepts, an overview sheet and a test sheet in `marketing/logo/`. A · C-Dash (the C is a TV screen and the dash runs out of it, recommended), B · cool— (the dash is a little TV with antennas), C · The Dash (a TV showing the dash's sidebar and one long dash). Monoline lowercase wordmark "cooldash" built from strokes.
- `marketing/README.md`; `CLAUDE.md` layout lists `marketing/` (and `os/` is no longer "planned").
0.22.02026-10-22
Settings System and Dash
- PLT-12 (new): Settings › System on CoolDash OS with six pages: Wi-Fi (on/off, join with the on-screen keyboard, disconnect, forget), Bluetooth (on/off, paired devices: connect, disconnect, forget; pair a new one), Sound (output, volume, mute through WirePlumber), Date & time (automatic time, time zone), Updates (moved from General), About like Android (System update first, then model, OS version and build, slot, Dash, kernel, firmware, processor, graphics, memory, storage, IP and MAC, uptime). On Android and in the browser: About only.
- DASH-14: the menu has two groups, System and Dash, and scrolls with the focus.
- Agent: `crates/cooldash-agent/src/settings.rs` (11 new `system.call` methods); the `Runner` reads system files too, so the pretend system answers About.
- The on-screen keyboard is one component (`KeyboardKeys.svelte`) for the setup and for dialogs (`Dialog.keyboard`, focus group `keyboard`).
- New `bluetooth` icon in the UI kit.
- "The firmware for updating" is built as the CoolDash OS update (Android's About › System update). Updating the board's own firmware is Q-25.
0.21.02026-10-22
CoolDash OS updates after the setup
- PLT-07 release side: `tools/os-release.sh` builds each board, collects the signed RAUC bundles, writes one `channel.json` for all boards (notes from the newest build log, or `--notes`) and publishes (rsync to a server or a folder): bundles first, the channel last.
- PLT-07 public side: CoolDash Cloud serves `/os/<channel>/channel.json` (no cache) and the bundles (immutable) from `COOLDASH_OS_DIR`.
- PLT-07 agent: picks this board's bundle (board from RAUC's `compatible`), newer versions only, relative URLs; downloads with `os.progress` messages and a sha256 check before `rauc install`. `--fake` has a pretend release channel (`/__fake/os/…`).
- PLT-07 Dash: checks 2 minutes after start and every 6 hours; asks "CoolDash OS X is ready" with Update now (progress: Downloading %, Installing, Restarting), Later (a day), Never show this again (toast says where updates are). Settings › General › CoolDash OS (version, check now / install) and Ask when an update is ready. Never prompts over the setup, a dialog, editing or the music scene.
- On CoolDash OS the automatic host search (`findHost`) is off: the setup asks, and after a restart it had picked the host found on the network although the setup chose "this TV is the host".
- A dialog with no buttons no longer leaves an empty button row.
0.20.02026-10-22
CoolDash OS full onboarding
- PLT-06 extended: 12 steps (language, remote, network, time zone, name, how the TV is run, account, people, look, screen, updates, welcome with a summary). Four paths: a CoolDash Host found on the network or typed, CoolDash Cloud, this TV as its own host (admin account typed on the TV), on its own (no account step, people kept on the TV). One on-screen keyboard for every text.
- WEB-11 on Linux: the agent finds CoolDash Hosts with `avahi-browse` (Avahi added to the image) and answers `host.discover` like Android.
- WEB-15: trusted device-core calls `host.setup`, `host.members`, `host.members.add` (only the TV itself; the LAN API refuses them). The HTTP setup and member routes share the same code.
- DASH-08: the household's people are kept on the TV (`settings.members`), from the host or from the setup.
- The setup now covers the sidebar (it was drawn under it), and the automatic host search (with its toast, and setting the address by itself) waits until the setup is over.
- Shared `styling-choices.ts` for the theme / voice / characters selects (Settings › Styling and the setup).
- Fixed a type error in `apps/dash/src/ext/redirects.test.ts` (from 0.19.0) found by svelte-check.
0.19.02026-10-22
Phase 11 CoolDash OS
- PLT-05 (in progress): `crates/cooldash-agent` (embeds the device core, serves the Dash, its packages and the bridge WebSocket); Buildroot external tree `os/` for the Raspberry Pi 5 and x86_64 UEFI; `tools/os-build.sh` (with `--check`). Both configs pass Buildroot's Kconfig check against Buildroot master of 2026-10-22.
- PLT-06 (done): first-run setup in the Dash (language, remote, Wi-Fi with on-screen keyboard, time zone, account, updates, welcome), stored by the agent. Browser test with the real agent.
- PLT-07 (in progress): updates with RAUC (agent: check, install, mark-good after 90 s). Pi 5 A/B with the firmware's `tryboot` and a RAUC custom backend (tested by `tools/os-test.sh`); x86_64 with GRUB.
- PLT-08 (in progress): CEC (claim a playback address, wake the TV), Bluetooth remote pairing, and the agent forwards remote keys the browser can't name (OK, colors, digits) from `/dev/input`.
- MUS-01, MUS-02 on Linux: PipeWire tap (`pw-record` on the sink monitor) and now playing from MPRIS players (`playerctl`).
- Dash: `LinuxHost` (`?bridge=ws://…`), chosen from the URL.
- Security: the bridge only accepts the Dash's own origin (`http://127.0.0.1` / `localhost`), because browsers let any page open a WebSocket anywhere.
0.18.02026-10-22
Phase 10 website docs and marketplace
- SITE-01…SITE-08 (As built (Phase 10, 2026-10-22)): landing, `/docs/use`, `/docs/create` (own sidebar and search each), live examples in a real Dash (`?sim`), `/market`, `/dev`, `/review`, `/changelog`.
- MKT-05, MKT-06, MKT-07, MKT-10 (As built (Phase 10, 2026-10-22)): publishing, automatic checks, review, ratings, reports, takedown, kill switch (TV side `package.kill`); `cooldash publish`.
- New docs: Create › Try it in your browser, Create › Publish to the marketplace, Use › The store.
0.17.02026-10-22
Phase 9 CoolCam
- CAM-01…CAM-10 (As built (Phase 9, 2026-10-22)): official `extensions/coolcam/`, the CoolCam test double, the exit test.
- SDK: `cd.members.open/close/quietMs/onChange`, `cd.net.fetch(…, { as: 'base64' })`, socket `auth.inUrl`; Android `NetFetch` binary answers.
- Q-14 answered (B); new Q-23 (CoolCam has no way to read privacy mode).
0.16.02026-10-22
Phase 8 music engine scenes and Spotify
- MUS-01…MUS-12 done (see As built (Phase 8, 2026-10-22)): `crates/cooldash-audio` (live + offline + fingerprint + demo songs, native and WASM), the Dash engine, six scenes, takeover, settings, the analysis DB (WEB-08).
- EXT-11, EXT-18 `cd.scenes`, `cd.media`; MUS-10 `cd.audio`; `Scene` in `@cooldash/sdk`; `auth.inBody` for refresh tokens.
- CHR-12 characters jump with their peak frame on the beat; no seats while a scene shows.
- SPT-01…SPT-10: Spotify extension, panel steps, the TV as a Spotify Connect device (Web Playback SDK, Widevine granted in the WebView), logo from Brandfetch.
- Android: Visualizer and playback-capture taps, media sessions, JNI audio, new permissions.
- ADR-008 Spotify on the TV and music analysis; Q-01 answered; Q-04 built with all three options (still open for the owner).
0.15.02026-10-22
Phase 7 CoolRoom and Home Assistant
- Platform: secrets in the device core (`secret.<ext>`, LAN access only in developer mode, removed with the package); `cd.secrets`, `cd.net.fetch(url, { auth })`, `cd.net.socket(url, { auth: { secret, firstMessage } })` (UI and worker); `secret` settings fields go to the secret store; `connect` fields are buttons. Android: secrets through the bridge, mixed content allowed for `ws://`/`http://` on the LAN.
- WEB-09: account-link sessions: TV → host → cloud; the phone opens `/link/<id>`, the provider returns to `/link/callback`; the code goes cloud → tunnel → host → relay → TV. Without a cloud link: a clear 409 and the pasted-key path.
- CoolRoom extension: 14 widgets, the CoolRoom page, connect with the phone (PKCE) or a pasted key, polling plan under half the hourly limit (stretches when the limit gets close, handles 401/403/429), change detection → `ext.coolroom.*` actions and toasts, character reactions, private widgets on the owner's dash, disconnect and wipe.
- Home Assistant extension: token in the secret store, live WebSocket (auth through the TV, states, events, registry, ping, backoff), entity widget per domain with optimistic OK, status widget, Rooms page, doorbell → action, toast and characters, `fire_event cooldash_*`.
- Test doubles: `tools/mock-coolroom/` and `tools/mock-home-assistant/`, shaped after the docs; started by the TV e2e config.
- Bug found: the Dash's version was never read from `VERSION` (stuck at 0.4.0), so extensions asking for a newer Dash were refused. Vite now injects it.
- Tests: core secret guard (Rust), relay of account links (cloud + host + fake TV), extension logic (CoolRoom 7, Home Assistant 4), TV e2e: widgets with data, private widget locked, light toggled from the Dash and confirmed by HA, a new grade → action + toast, doorbell → toast, disconnect revokes the key. The TV e2e suite now runs one test at a time (shared device core).
0.14.02026-10-22
Phase 6 characters
- New package `@cooldash/characters`: pure logic (effects, action player, character state machine, Wander behavior, world with avoid zones and integer scale, seats, shelf atlas packer, 8×8 emotes) and a PixiJS renderer in `./pixi`.
- Dash: `characters/stage.ts` (cast per dash, seats from the cards on screen, avoid zones from the focused card and the detail panel, reactions to actions, sleep on idle), TV › Styling › Characters, panel command `characters.react`, `cd.characters` for extensions (spawn, react, seats).
- Panel: a TV › Characters (cast, try reactions) and the Sprite Pack Builder (`/app/characters`): frames, actions, needed actions, transitions with parameters, live preview with the TV's code, copy animations from a pack, save to the library and send to TVs, fork rule for packs not made by the builder.
- Host: library package files for the panel (`/v1/marketplace/items/:id/files/*`), `characterActions` and `builder` in summaries.
- CLI: `cooldash validate` for character packs; `pngRgba`.
- Example: `extensions/character-fox` (16 frames drawn by a script).
- Tests: package unit tests (11), builder logic (5), library files route, TV e2e (moves in, walks, reacts in the same call, seats, three foxes, sleep, none), panel e2e (the exit test).
0.13.02026-10-22
Phase 5 themes and voices
- Themes: `theme/build.ts` (theme.json + member overrides → `cd-theme` / `cd-user` layers, slot variables, fonts, icons, background; unsafe values dropped), `theme/engine.ts` (fonts first, crossfade with motion tokens, icons refresh, motion overrides, transition aliases, theme script on the stage layer), `theme/contrast.ts`. Per-dash theme and overrides in the TV settings. TV › Styling › Theme. Panel › Styling: theme, my colors (contrast check), my card frame.
- Motion package: live motion values with `setMotionOverrides` (named presets follow the theme). UI kit: `refreshIcons`, `clearIconOverrides`; frames use `border-width` (the 4-value width in the `border` shorthand was invalid: frames never drew).
- Voices: registry, engine (priority, cooldowns, global gap, variants without repeats, `when` conditions, quiet hours, volume, ducking), Web Audio output with a decoded cache, preset store with the fork rule, per-dash presets, first pack auto-selected. TV › Styling › Voices and volume. Panel › Voices: preset, per-action clips, play on TV, save / fork dialog, quiet hours.
- CLI: `cooldash validate` checks `theme.json` and `voice.json` (schemas, files, clip size, community presets); packs `icons/`, `sprites/`, `backgrounds/`, `theme.js`.
- Examples / fixtures: `extensions/theme-arcade` (pixel frames drawn by a script, icon, CSS, scanline script), `extensions/voice-chime` (chimes generated with ffmpeg, preset).
- Tests: theme build and contrast (unit), motion overrides (unit), voice engine and fork rule (unit), TV e2e (theme applied: tokens, frame image and 12 px width, icon override, script decoration, motion; page changes speak; settings show the choices), panel e2e (library install of both, theme picked in the panel reaches the TV, community preset edit → "Save as a new preset?" → "Chimes (my version)" active, original unchanged).
0.12.02026-10-22
Phase 4 done passkeys QR mDNS
- Passkeys in host and cloud mode (SimpleWebAuthn 14, docs downloaded): discoverable credentials, sign in with one button, Account › Passkeys to add/remove. Secure context only (HTTPS or localhost).
- QR code in the TV's pairing dialog (`uqr`); with a host address, it links to `<host>/pair/<code>`.
- mDNS: the host announces `_cooldash-host._tcp` (bonjour-service); the Android TV looks for it on first start (NsdManager, bridge `host.discover`) and fills in the host address. The Docker image passes its version to the announcement.
- Third-party docs: SimpleWebAuthn, Android NSD, bonjour-service, uqr (`tools/docs-fetch/fetch-all.sh`, `SOURCES.md`).
- Tests: passkey options and refusals (unit), full passkey ceremony with Chromium's virtual authenticator (panel e2e), mDNS announce + browse (unit, `CD_MDNS_TEST=1`), QR in the pairing dialog and discovery through the simulated Android bridge (TV e2e).
0.11.02026-10-22
The TV as its own host
- `cooldash-core` embedded host (`embedded_host.rs`): the panel's API subset with the same JSON shapes as the Docker host (`mode: "tv"`), local accounts (scrypt), users and members, one device `tv`, commands and events bridged to the Dash through core events (`host.command` → `host.result`, `host.event`), the panel at `/` and the Dash at `/dash/`. Off by default; turned on from the TV.
- LAN `POST /api/v1/host/<method>` (dev core), JNI `hostCall` (Android), `WebRoot.kt` copies the panel and Dash assets; the APK now bundles the panel. Dev binary flags `--web DIR --own-host`.
- Dash: answers panel commands from the core, Settings › General › This TV is the host.
- Panel: `mode: "tv"` signs in like a host and hides the marketplace, Installed tab, APK upload, cloud link, tasks and Remove.
- Tests: Rust unit (passwords, usernames) and integration (off by default, enable only from the TV, setup, users, login, commands answered through events, `host.*` refused, 501 for host-only routes, kv `host` namespace read-only, panel/Dash files with SPA fallback); panel e2e against a real core serving the panel.
0.10.02026-10-22
Host background tasks and cloud link
- WEB-16: manifest field `hostBackground` (spec first, Extensions SDK - 5 Manifest Format); the host keeps a snapshot of each TV's host-capable extensions (`ext.snapshot`), runs the worker in a sandboxed Node worker thread when every TV that has it is off, and hands storage, channels and toasts back with `ext.hostState` when a TV wakes. Panel: Account › Background tasks. The example extension opts in.
- WEB-14: link code from the host panel claimed in the cloud panel, outbound WebSocket from the host, API tunnel `/v1/hosts/<id>/api/...` acting as the admin who linked, unlink from both sides. Cloud panel: My homes; an open home shows a banner.
- `checkNetwork` and friends moved to `@cooldash/schemas` (used by the Dash and the host).
- Tests: host tasks (handover and sandbox: `node:fs` blocked, a non-allowed domain blocked, no global `fetch`), cloud link (code, tunnel, other household refused, `/v1/auth` refused, unlink).
0.9.02026-10-22
APK upload from the panel
- Q-22 answered (owner: "the admin can do extension non signed even if the dev mode is off"): option A. ADR-007 Household library signed by the host is accepted.
- APP-05 remote part: panel › TV › Apps › Install an app. The file is streamed to the host (≤ 2 GB, sha256), the host sends `apps.installUrl` over the relay, the TV downloads it with its device token, checks the sha256 and installs it with the system installer. The panel shows the upload progress, then the TV's download and install progress (`install.progress` events).
- Android: `AppDownload.kt`, bridge request `apps.download`, progress `phase`, and `Installer` now checks "install unknown apps" (answers `needs_permission` and opens the setting; the host keeps the file so the user can press Install again). Files from the panel are deleted on the TV once installed.
- Host: uploads in `<COOLDASH_DATA>/files/uploads` (the Docker volume), deleted after the install answer or after 24 h. Migration 4 (`uploads.device_id`, `sha256`, `size` BIGINT).
- Spec first: bridge messages and the flow in Install from the panel (as built, 2026-10-22).
- Tests: cloud 3 new (names, upload → install → cleanup with a fake TV that checks the sha256, permission retry), schemas 1 new (file ids), panel e2e extended: an APK is sent from the panel, the browser TV downloads it from the host and the app appears on the TV.
0.8.02026-10-22
Household library and signed packages
- Signature check in `cooldash-core` (MKT-06): `SIGNATURE` = ed25519 over sorted `path:sha256` lines. Trusted keys: built in (none yet) + the paired host's key (one at a time). A wrong signature is refused even in developer mode; unsigned still needs developer mode. Rollback to the previous version (MKT-03). LAN API: `POST /api/v1/packages/<id>/rollback`, `PUT /api/v1/trust/<keyId>` (developer mode only); the `core` and `trust` kv namespaces are no longer writable over the LAN.
- Household library on the host (MKT-01…MKT-04, ADR-007 Household library signed by the host): admins add `.cdpkg` files; the host checks the manifest, signs with its own key, stores the file in the database. Browse, search, item page, install on TVs over the relay, auto-update for minor/patch, major asks, rollback, packs (parts first, removal asks what to keep). The host pushes its key to every TV on connect (`host.trust`); the panel can't send `host.*` commands.
- TV side: relay commands `packages.list`, `package.install`, `package.remove`, `package.rollback`, `host.trust`. Android downloads with `PackageDownload.kt` (device token, sha256) and installs through new JNI calls; the browser Dash on a dev core does the same in `core-dev.ts`.
- Panel: Marketplace page (kind chips, search, cover cards, detail panel, "Install on" TVs, Add a package), TV › Installed tab (update, back, auto-update, remove), a `Dialog` component that also replaces `confirm()` when removing a TV.
- Shared code moved to `@cooldash/schemas`: `semver` (now accepts `^1`, `1.2`, `1.x`) and `describePermissions`.
- Spec first: `SIGNATURE` format in SIGNATURE.
- Tests: Rust 4 new (signing, signed install, rollback); cloud 5 new (library, download auth, install/update/rollback with a fake TV that verifies signatures, packs, reserved commands); schemas 1 new (partial ranges); panel e2e 1 new: the example extension is added, installed on a TV with a real `cooldash-core`, the core reports `signed: true, signer: host-…` (Node signs, Rust verifies) and the Dash runs it.
0.7.12026-10-22
Docs catch-up and CURRENT.md
- `CURRENT.md`: one-page status, run commands, not done yet, things to acknowledge (untested hardware, my decisions, known issues, changes outside the repo).
- Plan: header (Phase 4 about half done), architecture diagram with the Host, Phase 4 milestones ticked or marked partial, repo layout.
- Home, Cloud and Web App Home, Glossary (Host, Cloud, Panel, Live preview, Idle screen, Pairing code, Device token, Admin, Member).
- Code Maps: missing files added (Tooling, Platforms, Dash, Cloud and Web App). Every source file is now listed.
- Open Questions: a Details section with one heading per question, so links like `Open Questions#Q-01` resolve.
- User docs (website content): General settings, Idle screen page (replaces the old ambient mode page), new Panel page "Edit from a computer".
- `CLAUDE.md` (reads `CURRENT.md` first, gotchas, checklist) and `README.md` rewritten.
- Housekeeping: generated `apps/dash/.svelte-check/` and `test-results/` removed from git and ignored. The vault lint skips templates.
0.7.02026-10-22
CoolDash Host in Docker
- ADR-006 Local host first, cloud optional; Q-12 answered.
- `apps/cloud` runs as host (Docker default) or cloud. Host: first-run admin setup, username/password login with lockout, users (admin/member, each with a dash), password change, panel served at `/` with the Dash at `/dash`, Wake-on-LAN with MAC learned from the ARP table.
- Panel: mode-aware sign-in, Users and Account pages, Wake button and MAC field, static build.
- TV: Settings › General › Host address; pairing uses it.
- `Dockerfile` + `docker-compose.yml` (host networking); image built and probed.
- User doc: *Install the CoolDash Host (Docker)*.
0.6.02026-10-22
Idle screen and web app
- Idle screen (DASH-17, respecified): a 6×4 grid stored in the layout file (`idle`), one per dash. No focus, nothing hides; any key returns to the dash with focus where it was. Text scales with the cell. Settings: Idle screen / Ambient clock / Nothing, and the delay.
- TV ↔ cloud (WEB-02, WEB-03): Settings › General › Web app shows a 6-letter code; the TV keeps a relay connection and answers `state.get`, `state.set` (settings, layouts of any dash incl. idle grid, members), apps, extensions, `remote.key`, `toast`; it emits `state.changed`.
- Preview mode: the Dash with `?preview=1` mirrors `postMessage` patches.
- Web app (`apps/web`, SvelteKit 3): sign in by email link, add a TV with its code, my TVs and household members, TV editor with tabs Dashboard, Idle screen (drag/resize/add/remove), Styling (background presets, link, upload), General, Extensions (web settings renderer), Apps, Remote; live preview on the right; autosave.
0.4.02026-10-22
Phase 3 Extension SDK
- `@cooldash/sdk`: `Widget`, `defineExtension`, `defineBackground` (self-starting in a worker), typed `cd` API, page↔worker RPC.
- Extension runtime in the Dash (`apps/dash/src/ext/runtime.ts`): loads packages from the device core, validates manifests and `dash` ranges, permission-checked `cd` object, widgets/pages/transitions/services/remote hooks/actions, background workers, channels, storage, settings, watchdog, hot reload with versioned element names, enable/disable/remove.
- Settings › Extensions with the TV renderer of the settings framework.
- `@cooldash/cli`: create, validate, pack (esbuild + fflate), dev (hot reload), publish (stub).
- `cooldash-core` binary: the developer's TV for the browser Dash (`?core=…&token=…`), also used by the e2e tests.
- Android: `net.fetch` (no CORS) and `packages.remove` (JNI).
- `extensions/_example`: uses every API; it is the end-to-end fixture.
- Website content: 18 creator pages, 11 user pages (Markdown, waiting for the SvelteKit site in Phase 4).
0.3.02026-10-22
Phases 1 and 2 Dash and Android host
- Dash (Phase 1): widget menu and picker, empty rows, skeletons, PixiJS layers (lazy), ambient particles, quality tiers from a startup benchmark, developer mode switch. See Dash - 2 Features.
- Android host (Phase 2): `apps/android-tv`, Gradle 9.8.2 + AGP 9.4.1, flavors `launcher` (Home screen, QUERY_ALL_PACKAGES) and `store`. WebView on the https asset origin, bridge over WebMessagePort, all remote keys forwarded with down/up, Home via `onNewIntent`, app list with banners and icons, launch, uninstall, APK/XAPK install with PackageInstaller sessions.
- Rust device core (PLT-04, PLT-10): `crates/cooldash-core` (SQLite storage with quotas, package store with safe unzip and atomic switch, LAN API on 8421 with token auth, SSE events), `crates/cooldash-jni` (8 JNI functions), cross-compiled for 3 ABIs and bundled in the APK.
0.1.02026-10-22
Phase 0 foundations
- Monorepo: pnpm 12 workspaces + Turborepo, strict TypeScript 7, Vitest 5, CI workflow.
- `tools/version` (MAJOR.MINOR.PATCH+BUILD), `tools/docs` (feature status + Code Map sync), `tools/e2e.sh` (Playwright in Docker).
- `@cooldash/tokens` (DS-01, DS-06), `@cooldash/motion` (DS-02, DS-05), `@cooldash/ui` (DS-03, DS-04, DS-07), gallery + visual baseline (DS-08).
- `@cooldash/schemas`: manifest, layout, voice, character, theme, analysis, bridge, action IDs. The tests parse the examples written in the docs.
- `apps/dash`: most of Phase 1 already (DASH-01…DASH-13), see Dash - 2 Features.
0.0.12026-10-22
Vault, plan and downloaded docs
- `CLAUDE.md` with reading order, docs maintenance rules, downloaded-docs structure, versioning `MAJOR.MINOR.PATCH+BUILD`, commit rules, stack, design rules.
- Vault: Home, Plan, Open Questions (Q-01…Q-21), Glossary, References, Decisions (ADR-001…005), Templates, 16 sections each with Home, Vision, Features, Tech, Code Map (+ format specs).
- Downloaded docs (57 files): Spotify (OpenAPI → Markdown, guides, policy, SDK), Home Assistant, Android, Linux kiosk pieces, AcoustID, MusicBrainz. Script `tools/docs-fetch/fetch-all.sh`.
- git repo with local identity Gandalf63 <gandasdev@coolroom.app>.