feat(canvas): fly an envelope between agents on each delegate/message #2443
Reference in New Issue
Block a user
Delete Branch "feat/a2a-message-flight-envelope"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
What
When one agent delegates to or messages another, an envelope ✉ now flies from the source agent to the target agent — for better "the org is alive" UX. Works on both surfaces:
ViewportPortal), so it pans and zooms with the canvas.How
useA2AFlights(new hook) — subscribes to the sameACTIVITY_LOGGEDWS bus theCommunicationOverlayalready uses; turns eacha2a_send/a2a_receive/task_updateinto a transient flight (source → target). Bounded (max 12 concurrent), auto-expiring, skips self-loops/missing targets, and honoursprefers-reduced-motion(emits nothing).FlightEnvelope(shared) — one envelope animatedfrom → tovia the Web Animations API (the delta is per-flight, so a static@keyframeswon't do). Coloured by kind (send = cyan, receive = violet, task = warm) to matchCommunicationOverlay.MessageFlightLayer(canvas) — renders flights viaViewportPortalinside<ReactFlow>; resolves node centres from the store. Also covers the concierge Org map (which embeds<Canvas/>).MessageFlightHome(home) — fixed overlay; resolves the source/target row rects (rows now carrydata-ws-id) and captures them once per flight so a mid-flight scroll doesn't restart the animation.No new event source, no new polling — it reuses the live A2A activity stream.
Tests
useA2AFlights7/7 (event→flight, kind mapping, ignore non-a2a / non-ACTIVITY_LOGGED, self-loop + no-target skip, reduced-motion, disabled, TTL expiry). Canvas + concierge suites unchanged (the layer renders nothing when idle). New files typecheck clean.Notes
aria-hidden(decorative); the textualCommunicationOverlayremains the SR-accessible record. Reduced-motion users get no animation.🤖 Generated with Claude Code
SOP Checklist
Comprehensive testing performed
useA2AFlightshas 7 offline unit tests (event→flight, kind mapping, ignore non-a2a / non-ACTIVITY_LOGGED, self-loop + no-target skip, prefers-reduced-motion, disabled, TTL expiry) — all pass on local vitest. New files typecheck clean (tsc --noEmit). Canvas + concierge suites re-run green (the flight layer renders nothing when idle, so it never touches the mocked ViewportPortal). The animation itself (Web Animations) is presentational and not unit-asserted beyond the flight lifecycle that drives it.Local-postgres E2E run
N/A — purely a canvas/frontend change (new hook + 3 components + 2 mount points). No Go/handler/DB/migration surface.
Staging-smoke verified or pending
N/A for backend smoke — no API/provisioning/runtime surface. Visually verifiable on the canvas + concierge home after deploy (envelope flies on a delegate/message event).
Root-cause not symptom
This is a UX feature, not a fix. It reuses the EXISTING live A2A event stream (the same
ACTIVITY_LOGGEDWS busCommunicationOverlayconsumes) rather than adding a new event source or polling — so it's derived from the SSOT for agent comms, not a parallel pipeline.Five-Axis review walked
Correctness: Web-Animations per-flight delta; canvas uses
ViewportPortal(flow coords → pans/zooms); home captures row rects once/flight to avoid scroll-restart. Security: none — envelopes arearia-hiddendecorative, no data/auth/secret path; the only payload read issource_id/target_id/activity_typealready on the bus. Performance: bounded concurrency (cap 12) + TTL expiry +willChange; honours prefers-reduced-motion (emits nothing). Maintainability: one shared hook + one sharedFlightEnvelopedrive both surfaces (no canvas/home duplication). UX: the requested envelope-flight; consistent palette withCommunicationOverlay.No backwards-compat shim / dead code added
Yes — net-new, self-contained; no shim, no dead code, no parallel event source (reuses the existing bus).
Memory consulted
reduced-motionWCAG compliance (the repo's standing concern — the feature opts out underprefers-reduced-motion),feedback_verify_real_artifact_not_proxy_metric(validated the flight lifecycle in tests, not a proxy), SSOT discipline (reused theACTIVITY_LOGGEDbus +CommunicationOverlaypalette rather than a new source).Security-team-21 APPROVE on current head
954fee28f4. Frontend-only decorative envelope animation: consumes existing ACTIVITY_LOGGED source_id/target_id/activity_type only; does not render IDs or sensitive fields as text; data-ws-id is a React attribute from existing node id and selector lookup uses CSS.escape when available; no secret/credential/token exposure, no auth/ACL/routing change, no injection surface, no gate weakening, no content-security markers found. CI still has non-security/non-required work in flight; security review is clean.Security-team-21 APPROVE on current head
954fee28f4. Frontend-only decorative envelope animation: consumes existing ACTIVITY_LOGGED source_id/target_id/activity_type only; does not render IDs or sensitive fields as text; data-ws-id is a React attribute from existing node id and selector lookup uses CSS.escape when available; no secret/credential/token exposure, no auth/ACL/routing change, no injection surface, no gate weakening, no content-security markers found. CI still has non-security/non-required work in flight; security review is clean.APPROVED on current head
954fee28f4.QA review: #2443 is frontend-only and scoped to the envelope flight UX. It adds useA2AFlights, a shared FlightEnvelope, canvas/home renderers, two mount points, and data-ws-id row attributes; no Go/DB/API/schema changes. Correctness: the hook reuses the existing ACTIVITY_LOGGED socket bus, maps a2a_send/a2a_receive/task_update to flight kinds, rejects missing endpoints/self-loops, caps concurrent flights, expires them, and emits nothing when disabled or prefers-reduced-motion is set. Renderers are thin endpoint resolvers: React Flow ViewportPortal for canvas pan/zoom and DOM row rects for concierge home. Tests cover valid emit, kind mapping, non-A2A/non-event filtering, self-loop/missing-target rejection, reduced-motion, disabled mode, and expiry. Content-security is clean: it reads source_id/target_id/activity_type already present on the bus and renders only an aria-hidden envelope, with no concrete internal paths/markers/credentials or sensitive payload content. No gate weakening or scope creep found. Holding merge until Canvas/all-required CI is green.