mirror of
https://github.com/block/buzz.git
synced 2026-11-13 12:51:34 +00:00
d220d5d3d88385b7cf331bb37998aba249a41e94
6 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
d77611dd08 |
feat(desktop): let users resize Project Canvas widgets
Widgets could be dragged and nudged but their footprint stayed fixed by
the package, so a dense reviews list or cramped tasks card could not be
adjusted without editing data/*.json. Resize is the same kind of direct
manipulation as drag, so it follows the same host-persisted,
revision-independent layout contract.
- `canvas.layout` gains an optional `sizes` map alongside `widgets`,
validated like positions: bounded dimensions (16 to 100 000), the
widget-id charset as keys, at most 256 entries, inside the existing
64 KiB port cap. Optional keeps packages that predate size persistence
valid — malformed layout messages count toward frame teardown.
- Size overrides persist and replay independently of positions:
resizing a widget must not pin its position and vice versa, so a later
package revision can still move a resized widget (and resize a moved
one). `projectCanvasLayout.ts` sanitizes stored sizes like points,
pruning malformed entries and folding them into the record caps.
- The template gets a per-widget resize handle: a focusable button
(aria-label "Resize {title} widget", the label's single owner) that
drags with the pointer and resizes with arrow keys (24px steps, 48px
with Shift), snapping to the 24px grid and clamping at 192x144 so a
widget cannot shrink out of reach. Saves go through the SDK's
debounced `layout.save`, which now clamps dimensions into the accepted
range so a save can never emit an invalid port message.
- Reset restores package sizes and clears stored overrides. The e2e mock
package grew a resize control and had to slim its shell comment: the
descriptor `url` data-URL cap is 8192 characters and the first version
exceeded it by 12, failing every canvas frame activation.
- Also updates the template-harness avatar assertion broken by
|
||
|
|
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>
|
||
|
|
380cc506e1 |
feat(desktop): let Project Canvas widgets send direct messages
Add a dm.send command to the canvas widget SDK so a widget can deliver a
message to a user's DM as the viewing user, behind a new consent-gated
app.dm.send capability.
- Protocol: app.dm.send joins the capability schema and the consequential
(consent-required) set in projectCanvasProtocol.ts, mirrored in the Rust
manifest validator's ALLOWED_CAPABILITIES. The consent banner names it
"send direct messages as you" and now joins three-plus capability
phrases as a serial list instead of chaining " and ".
- Broker: dm.send takes strict {pubkey, message} params — 64-hex
recipient, message capped at 2,000 chars and rejected when blank after
trimming — and dispatches to a new sendDirectMessage delegate with the
recipient lowercased. Like the existing `user` open target, the
recipient is any valid pubkey rather than a host-source row, because
legitimate recipients come from people.lookup/people.search results,
which are not host sources; containment is the per-revision consent
gate, the 10-per-minute command budget, and a host toast on every send.
- Delegate: useProjectCanvasBroker captures tenant scope (normalized
relay URL + signer pubkey) before the first await so the backend fails
closed if the community or identity changes mid-send, opens the DM via
useOpenDmMutation (which also upserts the conversation into the
sidebar), and sends over the HTTP path because a freshly opened DM has
no live subscription yet. ProjectChannelHome feeds the community relay
URL through a new relayUrl parameter.
- Host: success toasts "Canvas sent a direct message"; failures title as
"Canvas direct message failed" instead of borrowing the task-update
title.
- Template: the support dashboard's bug reporter now delivers the report
as a DM to the project owner (resolved via project.metadata) when
app.dm.send and project.metadata.read are granted, showing "Report
sent"; without the capability it stages the report exactly as before.
The bundled manifest requests app.dm.send.
- Docs: base_prompt.md and nest_skill.md document dm.send, its params,
and the third consent capability; the buzz-acp doc-binding test now
asserts both `dm.send` and `app.dm.send` so the prompt cannot drift.
Tests: new broker coverage for the capability gate, param bounds, blank
and oversized messages, unknown keys, and recipient normalization; the
external-template harness drives the real bug reporter through the SDK
(metadata query -> dm.send command -> success copy) and pins the staged
fallback when the capability is missing; protocol tests cover the
granted/consent/effective sets with the new capability; the Rust
manifest test accepts the full eight-capability set.
Verified: desktop unit suite 5898 passing, Tauri lib suite 3077 passing,
buzz-acp prompt tests passing, canvas e2e spec 7 passing, and clippy -D
warnings, rustfmt, biome, tsc, and the file-size gate all clean.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Matt Toohey <contact@matttoohey.com>
|
||
|
|
61226c68be |
feat(desktop): add a live data SDK to Project Canvas widgets
Implement the Project Canvas plan: live queries, task actions, navigation, and standard components for sandboxed widget packages. - RPC plumbing: child-to-host canvas.query/subscribe/unsubscribe/command/ open messages with strict schemas, tiered budgets answered as error results instead of frame kills (16 concurrent subscriptions, 10 commands per minute, 3 opens per 10s, 64 KiB port messages), and a disposal-fenced dispatcher (projectCanvasRpc) so late completions never post to a closed port. - Data broker (projectCanvasBroker): project.metadata, project.channels.list, project.reviews.list, project.tasks.list/get, people.lookup (max 32 pubkeys, avatars inlined as data URLs up to 48 KiB through a 32-entry LRU), and people.search, all scoped by page-fed sources; live subscriptions re-push on source changes with serialized dedupe, and commands/opens resolve against those sources before any delegate runs so widgets can never widen scope. - New capabilities project.tasks.read, project.people.read, project.tasks.write, and app.open in the Rust manifest validator and TS protocol, with per-(binding, revision) consent for the consequential pair: an approve/deny banner gates them, denial degrades the canvas to read-only, and widget-initiated writes surface toasts. - Task actions: NIP-34 status events (kinds 1630-1633) via the previously unused buildGitStatusTags, tasks.setStatus/assign/unassign mutations, and useLiveProjectWorkItems keeping issues and reviews fresh over a relay subscription with backoff, reconnect overlap, and debounced invalidation. - Navigation: canvas.open targets channel/user/task/review behind app.open; ProjectChannelHome routes them and remounts the workspace sheet with the selected item. - SDK runtime served at /__buzz/sdk.js and /__buzz/sdk.css before every package script: data.query/liveQuery/command, app.open, capabilities(), and ui.avatar/reviewRow/channelRow themed by --buzz-* variables. The bundled template's activeChannels and reviews widgets now render from liveQuery instead of fixture snapshots, a tasks widget adds Mark done/ Reopen/Assign to me, and the SDK surface is documented in base_prompt.md and nest_skill.md. Tested: desktop unit suite 5895 passing (new broker, rpc, protocol, template, and issue-template tests), Tauri lib 3076 passing, buzz-acp prompt tests, canvas e2e spec 7 passing with new capability-badge and consent assertions, and clippy, fmt, biome, tsc, px-text, and file-size gates all clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Matt Toohey <contact@matttoohey.com> |
||
|
|
3eb9a239bb |
feat(desktop): add channel project features and project canvas
Move projects onto the channel-first stack: a channel can opt into project features and surface them as tabs alongside chat, instead of projects living as a separate top-level surface. Desktop: - Channel project features are opt-in per channel, with a feature bar, settings pane, and tabs for the canvas, resources, repos, reviews, and meetings; project home redirects to its channel - Sidebar nests project channels under a project disclosure group, adds move destinations, and keeps section storage in sync with moves - Extract the join-channel banner and the channel pane main column, and pull sidebar scroll lock and unread DM previews into their own modules to stay under the file-size ratchet - Survive unmounted editor views and listener teardown races via mountedEditorView and safeUnlisten Project Canvas: - Sandboxed local canvas packages served over a Tauri custom protocol, with manifest validation, path security, and snapshot storage that supports pending/committed widget updates and revision pruning - React canvas host and surface plus a shared protocol module - Bundle a project-canvas-template resource with demo dashboards, widgets, and styles CLI and agent surface: - Add `buzz canvas notify` to tell the local desktop that a widget's presentation or data changed; it runs locally with no relay connection - Update the ACP base prompt and nest skill docs for canvas authoring Also promotes Projects out of preview-features. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: Matt Toohey <contact@matttoohey.com> |
||
|
|
61ba9dfaa0 |
refactor(voice): extract reusable Pocket primitives + Pocket voice settings (relands #2467 + #3208) (#3910)
Relands **#2467** (extract `buzz-voice` crate) and **#3208** (Pocket
voice settings) onto main, after #3266 and #3180 merged.
## Why a fresh PR
The repo is squash-only with delete-branch-on-merge. Squashing #3266
deleted `jtennant/pocket-tts-2026-04`, which was #2467's base — GitHub
auto-closed #2467 and it cannot be reopened. Squash merges also sever
ancestry, so GitHub's natural merge-base reports phantom conflicts for
the whole remaining stack.
## Content provenance
- Byte-identical to the blessed `jt/buzz-voice-refactor` branch
(`93029c577`, tree `6729e0eff` — reviewed by Dawn (#2467) and Max
(#3208) at exact heads) **except** the three files where #3180 and #3208
genuinely interact.
- Three-file resolution (union of both sides):
- `huddle/mod.rs` — #3180's pipeline re-exports + #3208's
`agent_tts_routing` imports.
- `huddle/state.rs` — `reset_preserving_generation` preserves both
`huddle_generation` (#3180) and `tts_enabled` (#3208); test sets merged
into one `tests` module.
- `desktop/src/testing/e2eBridge.ts` — both switch arms kept; no
duplicate case labels.
## Verification at
|