mirror of
https://github.com/block/buzz.git
synced 2026-11-03 16:01:35 +00:00
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>
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 frontendpnpm tauri dev- run the desktop apppnpm build- typecheck and build frontendpnpm typecheck- TypeScript checkspnpm lint- Biome lintpnpm 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