Files
buzz/desktop
Matt Toohey 5bad6f3b1a feat(desktop): persist Project Canvas widget positions across reloads
Widget placement was session-only: the template seeded `state.positions`
from the package defaults on every `host.init` and nothing wrote back, so
an arrangement was lost on navigating away, restarting, reloading the
canvas, an agent presentation update, answering the consent banner, and
community switching. Pan reset the same way.

Persist the arrangement host-side, keyed by (community, project,
dashboard, widget) and deliberately *not* by revision — an agent editing
the package must not throw away where the user put things, while consent
stays per-revision. Only overrides are stored, so an untouched widget
keeps following the package and a later revision can still move it.

- `canvas.layout` is a new child-to-host port message, not a
  `canvas.command`: it is direct user manipulation of host-rendered
  chrome, so it needs no capability and no consent, but it is strictly
  validated — bounded pan and per-widget coordinates (±100 000), the
  existing widget-id charset as keys, at most 256 widgets, and the
  existing 64 KiB port cap. `projectCanvasRpc` explicitly excludes it, so
  layout can never reach the broker.
- `projectCanvasLayout.ts` mirrors `projectCanvasConsent.ts`: one
  localStorage record per community+project holding a per-dashboard map.
  A write replaces its dashboard wholesale (stale widget ids self-prune),
  an empty layout deletes the entry and the last one clears the record,
  and stored dashboards (32) and record bytes (128 KiB) are capped by
  dropping the least recently written. Corrupt or oversized records read
  as no layouts; write failures degrade to package defaults next load.
  This is refreshable preference state, not a durable record.
- The host reads layouts at frame mount into `host.init` and persists
  `canvas.layout` beside the `canvas.rendered` branch. Both callbacks
  capture the binding they were built for, so a message landing while the
  host switches community or project can never read or write under the
  next binding's key.
- `sdk.layout.save({dashboard, pan, widgets})` sanitizes and trailing-
  debounces (300ms, last write wins) through the queued-until-init send
  path. The debounce is load-bearing: a held arrow key auto-repeats far
  past the host's 60-per-10s port limit, which kills the frame rather
  than failing one message.
- The template seeds positions and pan from `layouts[dashboard.id]`,
  saves after a drag end, keyboard nudge, and pan end, and its reset
  button becomes a true layout reset — package pan *and* positions, then
  a save that clears the record. Widgets move in place rather than
  re-rendering, so live subscriptions survive it. Combined with
  coordinate clamping, a stranded widget is always recoverable.
- Extract `ProjectCanvasFrame` and the Tauri command wrappers out of
  `ProjectCanvasHost.tsx`, which the new wiring pushed to 1035 lines,
  past the 1000-line file-size gate.

Tests: protocol coverage for the widget cap, non-finite and out-of-range
coordinates, malformed widget-id keys, and unknown fields; new storage
tests for round-trip, wholesale replace, deletion, corrupt/oversized
records, both caps, and a widget named `__proto__`; three template
harness tests driving the real template and real SDK — a persisted
override reaching `data-world-x/y` and the transform, a nudge burst
producing exactly one debounced message carrying only the moved widget's
delta, and reset clearing to empty overrides and default pan; and a
Playwright spec binding the host seam end to end (arrange, reload into a
new load id and revision, reset, reload). Each was checked falsifiable:
dropping the init merge, the reset's position restore, or the host's
persisted `layouts` fails the matching test.

Verified: desktop unit suite 5923 passing, Tauri lib suite 3077 passing,
buzz-acp prompt tests passing, canvas e2e specs 9 passing, and clippy -D
warnings, rustfmt, tsc, biome, px-text, and the file-size gate clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Matt Toohey <contact@matttoohey.com>
2026-09-02 12:32:18 +10:00
..
2026-03-09 13:02:11 -07:00
2026-06-11 15:06:24 +00:00
2026-03-09 13:02:11 -07:00

Buzz

Desktop chat shell with:

  • Tauri + React + TypeScript + Vite
  • Tailwind CSS
  • shadcn/ui-ready shared components
  • Biome (lint/format/check)
  • Feature-driven frontend structure

Scripts

  • pnpm dev - run the web frontend
  • pnpm tauri dev - run the desktop app
  • pnpm build - typecheck and build frontend
  • pnpm typecheck - TypeScript checks
  • pnpm lint - Biome lint
  • pnpm format - Biome format (write)
  • pnpm check - Biome check

Structure

  • src/shared - reusable app-wide code (ui, lib, styles)
  • src/features - feature modules (vertical slices)
  • src/app - top-level app composition