From 319d4b551bd3a90c24689444ad589a75d9c75775 Mon Sep 17 00:00:00 2001 From: Ashish Huddar Date: Sun, 10 May 2026 01:08:16 +0530 Subject: [PATCH] docs(canvases): plan-eng-review locks v0.4, defers behind v0.2 + v0.3 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ran /plan-eng-review on the v0.4 canvas-renderer plugin slot plan. Four sections, 7 architecture / code-quality / performance forks (all chose Option A), one codex outside-voice consult, 3 cross-model tensions resolved (1A + 2A + 3A). KEY DECISIONS LOCKED: 1A. Defer v0.4 — ship v0.2 (CanvasProducer wiring) and v0.3 (mux push) first. Codex outside-voice was decisive: v0.4 is high-infrastructure + low-immediate-user-value, and the deferred-CLI scope was confusing on its own terms. v0.2 + v0.3 deliver immediate user value (PR-status canvases, sub-second updates) for less eng cost. 2A. When v0.4 ships, scope is Maximalist (CLI auto-rebuild + warning UI included). The original "Selective" scope was rejected because deferring CLI tooling makes the build-time-discovery design ship a confusing path to users. 3A. AST package-contract scanner stays in v0.4 but reframes as compatibility lint, not a safety mechanism. The trust statement ("installing a renderer plugin grants full dashboard code execution") does the security work; AST scanner just keeps plugins shaped right. ARTIFACTS: - docs/canvases-v0.4-plan.html (NEW): the locked v0.4 plan with all 9 numbered decisions, codex's 12 design constraints folded in, failure-mode-organized test scope, ship-cost breakdown, NOT-in-scope. - docs/canvases-feature.html: roadmap row v0.4 updated to reflect new ordering + Maximalist scope + 2.5-4 eng-week estimate. Known-tradeoff section's "rebuild required" framing aligned with CLI-included scope. - TODOS.md (NEW): captures v0.2, v0.3, v0.4 (with 10 implementation lanes A-J), and v0.5+ deferred work. Test scope is organized around failure modes (stale artifact, missing rebuild, npm-pack vs symlink, Zod peer mismatch, etc.), not test count theater. Review persisted to ~/.gstack/projects/ for the dashboard. Test plan artifact written for future /qa consumption. Co-Authored-By: Claude Opus 4.7 (1M context) --- TODOS.md | 96 ++++++++++++++++ docs/canvases-feature.html | 4 +- docs/canvases-v0.4-plan.html | 212 +++++++++++++++++++++++++++++++++++ 3 files changed, 310 insertions(+), 2 deletions(-) create mode 100644 TODOS.md create mode 100644 docs/canvases-v0.4-plan.html diff --git a/TODOS.md b/TODOS.md new file mode 100644 index 000000000..58188ba15 --- /dev/null +++ b/TODOS.md @@ -0,0 +1,96 @@ +# TODOS + +Cross-cutting work captured outside any single PR. Each item references the plan that motivates it; pick up by working sequentially through the sections. + +--- + +## v0.2 — `CanvasProducer` plugin invocation (NEXT to ship) + +**Why first:** Highest leverage per engineer-day. PR-status, linked-issues, cost canvases ship the moment this lands. Every existing AO user benefits with no plugin install. + +**Source:** [docs/canvases-feature.html](docs/canvases-feature.html) Tier 2 + roadmap row v0.2. + +**Scope:** +- Wire `CanvasProducer.listCanvases(session, project)` calls into the canvases API endpoint. +- Iterate over registered agent / SCM / tracker plugins; merge their returned canvases with file canvases + synthesized git diff. +- Same first-write-wins merge semantics as the file path (see `packages/web/src/app/api/sessions/[id]/canvases/merge.ts`). +- Tests: each producer source produces canvases, multiple producers merge correctly, producer crash → skipped with named warning, producer respects existing size caps. + +**Effort:** ~3 days. + +--- + +## v0.3 — Mux WebSocket push (after v0.2) + +**Why second:** Fixes the felt-slowness UX gap of 5s polling. v0.4 inherits this gap if shipped first. + +**Source:** [docs/canvases-feature.html](docs/canvases-feature.html) roadmap row v0.3. + +**Scope:** +- New mux topic `canvases:{sessionId}`. Core publishes when a canvas file is added / modified / deleted in the workspace. +- `useSessionCanvases` subscribes per session; falls back to 5s REST poll if mux is unavailable. +- Visibility-aware pause stays. +- Invalidate-and-fetch pattern (don't ship full payload over the topic; signal a refetch). +- Tests: subscribe + receive event, fall back to poll on disconnect, multi-tab dedupe. + +**Effort:** ~4 days. + +--- + +## v0.4 — Canvas-renderer plugin slot (after v0.3) + +**Source of truth:** [docs/canvases-v0.4-plan.html](docs/canvases-v0.4-plan.html) (locked decisions from plan-eng-review 2026-05-10). + +**Scope is Maximalist** per cross-model tension 2A: core machinery + example tiny plugin + real-npm-pack CI fixture + `ao plugin install` CLI auto-rebuild + install-warning UI. + +### Implementation lanes (parallelizable after schema registry lands) + +| Lane | Module path | Effort | Depends on | +|---|---|---|---| +| **A. Schema registry foundation** | `packages/core/src/canvas-schema-registry.ts` (new) | ~2 days | — | +| **B. Discovery generator** | `packages/cli/src/lib/canvas-discovery.ts` (new) + `scripts/generate-canvas-renderers.ts` (new) | ~3 days | A | +| **C. AST compatibility lint** | `packages/cli/src/lib/plugin-contract-lint.ts` (new) | ~3 days | — (parallel with A) | +| **D. Web renderer dispatch** | `packages/web/src/components/CanvasRail.tsx` (extend) + `packages/web/src/components/CanvasErrorBoundary.tsx` (new) | ~2 days | B | +| **E. `ao plugin install` CLI** | `packages/cli/src/commands/plugin.ts` (new) | ~3 days | B | +| **F. Install-warning UI** | `packages/web/src/components/PluginTrustWarning.tsx` (new) | ~2 days | E | +| **G. `ao canvas prune` CLI** | `packages/cli/src/commands/canvas.ts` (new) | ~1 day | A | +| **H. Example tiny plugin** | `packages/plugins/canvas-counter/` (new package) | ~2 days | A, B, D | +| **I. CI fixture (real npm pack)** | `packages/integration-tests/canvas-plugin.integration.test.ts` (new) | ~3 days | H | +| **J. Tests + docs** | distributed across the above modules | ~3 days | all | + +### Failure modes the implementation MUST handle (codex-derived) + +These are the failure cases the test suite is organized around (replaces the "29 tests" framing with concrete failure modes): + +- **Stale build artifact:** plugin installed after last `pnpm build` — show "rebuild required" placeholder, not "unsupported type". +- **Missing rebuild:** user `npm install`-d but never ran `pnpm build` — `ao plugin install` CLI auto-runs the build. +- **Real npm-pack vs workspace symlink:** CI fixture uses `npm pack` + tarball install, not `pnpm` workspace. +- **Zod peer-dep mismatch:** plugin's Zod is a different major than core's — discovery rejects with named error. +- **Renderer chunk failure:** lazy chunk fails to load (network error, integrity check) — Suspense fallback shows "renderer unavailable" state, not blank canvas. +- **Schema import throws:** plugin's `dist/index.js` throws during import — per-plugin try/catch, plugin omitted from registry. +- **Schema import hangs:** plugin's import takes >5s — timeout, plugin omitted. +- **Type-id collision:** two plugins claim the same `canvasType` — fatal, AO refuses to start, names both. +- **Built-in shadow:** plugin claims `markdown`/`diff`/`table`/`stats` or any `core-*` prefix — discovery refuses to load that plugin. +- **Plugin uninstall:** workspace has stale canvas JSON referencing removed canvasType — `ao canvas prune --type X` cleans them. +- **Unsupported type disambiguation:** placeholder UI distinguishes "plugin missing" / "stale build" / "failed discovery" / "AO version mismatch." +- **Next.js production build:** CI fixture runs `next build` (production), not dev mode — proves file tracing includes dynamic imports. + +### Effort summary + +2.5–4 engineer-weeks of core work for the full Maximalist scope (lanes A–J). + +--- + +## v0.5+ — Deferred (do not start until v0.4 is in production) + +- **Iframe sandboxing** for community / untrusted renderers. Only with a real use case + a designed-end-to-end protocol (CSP headers, postMessage validation, focus management, viewport sizing). +- **Image rendering in built-in markdown.** Needs allowlist policy + `data:` policy. +- **Write-back canvases / action buttons** that mutate session state. Needs CSRF, auth scope, consent UI. +- **Plugin hot-reload during dev.** Plugin authors restart AO at v0.4; this is a quality-of-life improvement. +- **Plugin marketplace UI.** AO-hosted catalog of community plugins. + +--- + +## Maintenance / non-canvas + +(Add as discovered. Don't let TODOs land here without a referenced plan or motivating issue.) diff --git a/docs/canvases-feature.html b/docs/canvases-feature.html index 2b9833f6d..fcf9a7fce 100644 --- a/docs/canvases-feature.html +++ b/docs/canvases-feature.html @@ -619,7 +619,7 @@ EOF v0.1File reader, GET endpoint, 4 renderers, synthesized git-diff, right-rail with auto-expand, 5s REST pollShipped v0.2CanvasProducer.listCanvases invoked on agent / scm / tracker plugins (Tier 2 above)Queued v0.3Mux WebSocket topic for live updates, replacing 5s pollQueued - v0.4canvas-renderer plugin slot (Tier 3 above). Plugin discovery at AO startup, Node-side schema registry, generated packages/web/src/generated/canvas-renderers.ts barrel of static imports for the Next.js build, renderer dispatch with error boundary, type-id collision detection, install-time trust warning, example flamegraph plugin, tests across core and webQueued — 1.5–3 engineer-weeks of core work, the OSS extensibility unlock + v0.4canvas-renderer plugin slot. Per the locked v0.4 plan: single build-time discovery, Node-side schema registry hydrated at startup from a build-time JSON artifact, generated packages/web/src/generated/canvas-renderers.ts with lazy React.lazy() imports + per-canvas <Suspense>, per-canvas error boundaries, built-in canvasType reservation, AST-based compatibility lint at discovery, validate-once + narrow-in-dispatcher payload typing, named-error-and-skip on plugin failures, ao plugin install CLI with auto-rebuild, install-time trust warning UI, example tiny renderer plugin, real-npm-pack CI fixture, stale-canvas cleanup tooling. Ships AFTER v0.2 and v0.3 per the plan-eng-review codex consult.Queued (after v0.2 + v0.3) — 2.5–4 engineer-weeks of core work for the full Maximalist scope MobileBottom-sheet or full-screen takeover for canvases on small viewportsDeferred Sandboxed iframeRuntime-isolated escape hatch for genuinely-untrusted content (e.g. third-party HTML emitted by an agent we can't trust)Only if a real use case shows up @@ -667,7 +667,7 @@ EOF

Honest acknowledgment of where the v0.1 surface has real limits:

diff --git a/docs/canvases-v0.4-plan.html b/docs/canvases-v0.4-plan.html new file mode 100644 index 000000000..f1b84a751 --- /dev/null +++ b/docs/canvases-v0.4-plan.html @@ -0,0 +1,212 @@ + + + + + +Canvas-renderer plugin slot · v0.4 plan · Agent Orchestrator + + + +
+ +
Agent Orchestrator · v0.4 plan · post plan-eng-review
+

Canvas-renderer plugin slot — v0.4 plan

+

The locked design for v0.4 after a full plan-eng-review (4 sections, 7 architecture forks, 1 codex outside-voice consult, 3 cross-model tensions resolved). v0.4 ships AFTER v0.2 and v0.3, with the full Maximalist scope.

+ +
+ Reviewed 2026-05-10 + 7 forks resolved + Maximalist scope + Ships after v0.2 + v0.3 + 2.5–4 eng-weeks +
+ +
+
Goal
Let community contributors ship new canvas types as @aoagents/ao-plugin-canvas-{name} npm packages without core PRs.
+
Scope
Maximalist: core machinery + example tiny plugin + real-npm-pack CI fixture + ao plugin install CLI auto-rebuild + install-warning UI.
+
Sequence
Ship v0.2 (CanvasProducer wiring) and v0.3 (mux push) first. v0.4 lands when those are stable.
+
Trust
Installing a renderer plugin grants full dashboard code execution. The install-time warning UI says so explicitly.
+
Tests
29 new tests across 6 files, all mandatory at land — no test deferral.
+
+ +

Why v0.4 ships after v0.2 + v0.3

+

The plan-eng-review consulted codex (the outside-voice / second opinion step). Codex's verdict on v0.4-as-next-step was direct: "v0.4 should not be next. Ship v0.2 or v0.3 first. Renderer plugins are high-infrastructure, low-immediate-user-value."

+
    +
  • v0.2 (CanvasProducer wiring) ships PR-status, linked-issues, cost canvases the moment it lands — every existing AO user benefits with no install. Highest leverage per engineer-day.
  • +
  • v0.3 (mux push) fixes the felt-slowness of 5s polling. Real UX gap that v0.4 would otherwise inherit.
  • +
  • v0.4 (renderer plugins) is platform infrastructure that benefits zero existing users on day 1 and only kicks in when someone publishes a plugin. Ships best with a clear UX story (CLI auto-rebuild, install warning, real-npm-pack tested), which is what the Maximalist scope provides.
  • +
+ +

Locked decisions

+

Each row is the outcome of an interactive plan-eng-review fork. Defaults are not editable in the plan — to change one, re-open the review.

+ +
+
D1 → A

Strategic — defer v0.4 until v0.2 and v0.3 are live

LOCKED
+

v0.2 (CanvasProducer wiring) and v0.3 (mux push) ship first. v0.4 starts after v0.3 is stable. Codex outside-voice was decisive: the OSS-extensibility story needs the rebuild UX it can only justify shipping with the Maximalist scope, and meanwhile v0.2 + v0.3 deliver immediate user value.

+
+ +
+
D2 → A

Scope — Maximalist when v0.4 ships

LOCKED
+

Selective scope (defer CLI + warning UI to v0.4.1) was rejected after codex pointed out that build-time discovery makes "install + rebuild" the marquee user experience and deferring the CLI ships an intentionally confusing path. v0.4 ships with: core machinery + example plugin + CI fixture + ao plugin install CLI auto-rebuild + install-time trust warning UI. ~2.5–4 engineer-weeks of core work.

+
+ +
+
D3 → A

Discovery — single build-time walk, JSON + barrel are siblings

LOCKED
+

One discovery moment (build-time) produces both packages/web/src/generated/canvas-plugins.json (manifest + plugin paths) and packages/web/src/generated/canvas-renderers.ts (renderer barrel). AO startup reads the JSON and dynamically imports each plugin to hydrate Zod schemas — no node_modules walk in the hot path. Eliminates the two-moment-drift class of bug where startup and build-time disagree on which plugins exist.

+
+ +
+
D4 → A

Built-in canvasType reservation

LOCKED
+

Discovery refuses to load any plugin whose canvasType is in {markdown, diff, table, stats}. Plus reserve the core-* type prefix (per codex finding) for future built-in expansion. Refused with named error before AO starts. Closes the trojan-plugin-shadow attack surface.

+
+ +
+
D5 → A

Per-canvas error boundaries

LOCKED
+

Each rendered canvas wraps in its own React <ErrorBoundary>. A buggy plugin canvas blanks only that canvas with a stub showing the canvas type + plugin name + a link to the plugin's repo. Sibling canvases keep rendering. Per-rail boundary was rejected (one bad canvas would blank trusted core-git-diff + table results + PR status).

+
+ +
+
D6 → A

Plugin package contract — strict, validated at discovery

LOCKED (re-framed)
+

Build-time validation enforces:

+
    +
  1. package.json exports map has both "." (Node-safe schema) and "./renderer" (browser-safe component).
  2. +
  3. peerDependencies.react is set; dependencies.react is not set (avoids two-React hooks errors).
  4. +
  5. ./renderer entrypoint is compiled JS (not raw TSX).
  6. +
  7. AST-level static check that renderer has no node:* imports and schema has no window/document references.
  8. +
+

Refuses non-conforming plugins with named errors. Codex re-frame applied: the AST scanner is a compatibility lint, not a security mechanism. It catches honest plugin-author footguns (Node API in browser bundle, DOM API in Node-side schema) but cannot reduce blast radius — transitive imports, dynamic import(), globalThis.window, malicious deps all bypass it. The trust statement does the security work; the AST scanner just keeps plugins shaped right.

+
+ +
+
D7 → A

Validate-once, narrow-in-dispatcher payload typing

LOCKED (scope clarified)
+

Core validates plugin payload via the registered Zod schema during ingest. Dispatcher narrows the type via z.infer<schema> and passes typed payload to the plugin renderer. Plugin authors get payload.samples directly, not payload: unknown.

+

Codex scope clarification: end-to-end static typing across community plugins is not possible — CanvasArtifact at AO's type system level is BuiltIn | { payload: unknown }. Type narrowing works within a plugin's own component (we pass z.infer<theirSchema> locally via the generated barrel) but the dispatcher itself only knows unknown. Plan language updated to say "typed locally, not end-to-end."

+
+ +
+
D8 → A

Plugin load failure policy — named-error-and-skip

LOCKED
+

Per-plugin try/catch in discovery. Failures are logged with plugin name + reason, and the plugin is omitted from the registry. AO continues to start with reduced functionality. Type-id collisions remain fatal (refuse to start, names both plugins). Codex addition: schema imports are arbitrary startup code (plugins can do I/O, mutate globals, hang). Per-plugin try/catch + a per-plugin import timeout (5s) to handle abuse + latency.

+
+ +
+
D9 → A

Lazy renderer imports + per-canvas Suspense

LOCKED
+

Generated barrel emits React.lazy(() => import("...")) per plugin renderer, with a per-canvas <Suspense> boundary in the rail. Each plugin loads as its own Next.js code-split chunk only when a canvas of that type appears. Bundle size scales with usage, not with installed plugin count. Eager imports were rejected — they would force a v0.5 generator-script rewrite the moment the ecosystem grew past ~5 plugins.

+
+ +

Design constraints from codex outside-voice (folded in)

+

Twelve additional codex findings were not big enough to be cross-model tensions but reshape the implementation. All accepted as default constraints:

+ + + + + + + + + + + + + + + + +
ConstraintWhat changes
Plugin install locationv0.4 must specify: plugins live in packages/web/package.json dependencies (the bundler's build context). The ao plugin install CLI adds them there, not at the monorepo root.
Schema import timeoutPer-plugin import wrapped in a 5s timeout. Slow / hanging plugins are skipped with a named error rather than blocking AO startup.
Zod peer-dependencyPlugins declare zod as peerDependency alongside React. Standardize on the Zod major core ships. Use safeParse only — no instanceof ZodError across version boundary.
ZodEffects / transforms / async refinementsForbidden in plugin schemas at v0.4. Validated at discovery. Reduces validate-once semantics to "no transforms, no async."
Real npm-pack CI fixtureReplace the original "workspace fixture" plan with: npm pack the example plugin, install the tarball into a temp AO/web app, run discovery, build Next, start dashboard, render canvas. Proves the published-to-npm path, not just monorepo imports.
Unsupported-state disambiguationThe "Unsupported canvas type" placeholder must distinguish "plugin missing" / "stale build artifact" / "plugin failed discovery" / "AO version mismatch." Stub UI shows the state + remediation hint.
Stale-canvas cleanup toolingAdd ao canvas prune --type X CLI to remove orphaned canvas JSON files in workspaces after plugin uninstall.
Reserve core-* canvasType prefixD4 was for the four specific built-in names. Also reserve the core-* prefix at canvasType level (mirroring the existing id-level reservation) for future built-in expansion.
Fixture choice — not flamegraphFlamegraphs have heavy payload + virtualization concerns. The first contract fixture should be a tiny renderer (e.g. canvas-counter: a single-metric tile with one prop). Keep flamegraph as a perf demo, not the slot-proves-it-works fixture.
Plan language — type narrowing scopePlan + docs say "typed locally" instead of "typed end-to-end" so plugin authors don't expect AO core to know their payload shape.
Test count tied to failure modesThe 29-test count was process theater. Tests are organized by failure mode: stale artifact, missing rebuild, npm-packed plugin, Zod peer mismatch, renderer chunk failure, schema import throw, collision, uninstall, unsupported type, Next production build.
Next.js production-build coverageThe CI fixture must run next build (production) not just dev mode — file tracing in production output omits dynamic imports unless they were known at build time, which is the failure mode the architecture has to survive.
+ +

Ship cost

+

2.5–4 engineer-weeks of core work for the full Maximalist scope, including:

+
    +
  • Discovery generator script (~3 days)
  • +
  • Schema registry + canvas-log integration (~2 days)
  • +
  • Web renderer dispatch + Suspense + error boundaries (~2 days)
  • +
  • AST compatibility lint (~3 days, given dynamic import + transitive scan complexity)
  • +
  • ao plugin install CLI + auto-rebuild (~3 days)
  • +
  • Install-time trust warning UI (~2 days)
  • +
  • Example tiny plugin + real-npm-pack CI fixture (~3 days)
  • +
  • ao canvas prune CLI (~1 day)
  • +
  • 29 tests across 6 files (~3 days)
  • +
  • Docs + migration guide for plugin authors (~2 days)
  • +
+

Sequenced: any of CLI, AST lint, generator can run in parallel after the schema registry is in place.

+ +

NOT in scope (deferred)

+
    +
  • Iframe sandboxing. Credible v0.5+ option for genuinely-untrusted renderers. Half-baked iframe is worse than no sandbox.
  • +
  • Hot-reload of plugin renderers. Plugin authors restart AO; the dev loop is documented but not optimized for v0.4.
  • +
  • Plugin marketplace UI. v0.4 plugins are discovered via npm — no AO-hosted catalog, no in-app browsing.
  • +
  • Write-back from plugin canvases. Read-only at the canvas level. v0.5+ explores consent-gated action buttons.
  • +
  • Image rendering in built-in markdown. Separate v0.5 question with its own allowlist policy.
  • +
+ + + +
+ +