cd /news/developer-tools/show-hn-canvas-multiplayer-html-mcp-… Β· home β€Ί topics β€Ί developer-tools β€Ί article
[ARTICLE Β· art-57643] src=github.com β†— pub= topic=developer-tools verified=true sentiment=Β· neutral

Show HN: Canvas, multiplayer HTML (MCP-first)

Canvas, an agent-agnostic multiplayer HTML deck editor, launches with MCP-first design allowing AI agents like Codex and Claude Code to propose edits as diffs for team review. The standalone tool features slide locks, threaded comments, versioning, and export to HTML, PDF, and PowerPoint, aiming to streamline collaborative presentation creation with AI assistance.

read6 min views1 publishedJul 13, 2026
Show HN: Canvas, multiplayer HTML (MCP-first)
Image: source

Agent-agnostic multiplayer editor for HTML decks. Each slide can be owned by a user; Codex, Claude Code, or another MCP-compatible agent proposes edits as diffs the team reviews. Threaded comments and slide locks keep parallel work coherent. Canvas is standalone with its own Supabase project and auth; see ADR-0004 for the split and ADR-0009 for the client model.

Why this exists, in one screen:DESIGN.md** Canonical vocabulary (Deck, Slide, Edit, Snapshot, MCP Token, …):CONTEXT.md Schema:**app/supabase/migrations/(sequential; 0000 is the workspace tenancy foundation, 0001+ are Canvas-specific)

Authβ€” Google + magic link (standalone Supabase project, seeADR-0004)Deck list, create / importβ€” paste or upload an HTML deck; the parser decomposes it into slides + theme + nav and lifts inline base64 images out to Storage3-pane editorβ€” slide list / live iframe preview / right rail (comments + proposals + danger)** Slide locks**β€” 15-min soft lock; warns when people or their agents converge on the same slide** Proposals**β€”canvas_deck_edit

rows with statuspending β†’ applied | rejected

. Inline diff, slide-over sheet, and a per-deck/inbox viewThreaded commentsβ€” top-level pinned to a slide (anchor at fractional x,y), replies thread under. Mentions stored as a uuid array** Versioning**β€” every applied edit produces a new immutablecanvas_slide_version

; the slide table'shtml_body

/slide_styles

/title

are denormalized caches kept in sync by the apply-edit RPC. Restores are forward-onlySnapshotsβ€” named deck-wide cuts (manual + auto on export / share / consolidate / pre-restore). Stored as theme/nav + a(position β†’ slide_version_id)

map; cheap to createConnectionsβ€” per-user MCP tokens plus an optional personal OpenRouter key. Bearer/api/mcp

works with streamable-HTTP MCP clients; in-app chat can use the local Codex/Claude bridge or OpenRouter per turnExportβ€” HTML, PDF, and PowerPoint with auto-snapshots and visible render progress

cd app
cp .env.example .env.local            # fill in the keys (see below)
npm install
npm run dev                           # http://localhost:3001
Var Notes
NEXT_PUBLIC_SUPABASE_URL
https://<your-project-ref>.supabase.co (browser-safe, not a secret)
NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY
Browser-safe sb_publishable_...
SUPABASE_SECRET_KEY
Server-only sb_secret_... . Used by the HTML deck parser (uploads assets via service role), MCP token revocation, and the workspace-create server action (RLS blocks direct INSERT on public.workspaces ). Never expose to the browser, never commit
CANVAS_CREDENTIAL_ENCRYPTION_KEY
Server-only base64-encoded 32-byte key (openssl rand -base64 32 ) used to AES-256-GCM encrypt personal OpenRouter API keys. Keep stable across deploys; never expose or commit
NEXT_PUBLIC_APP_URL
http://localhost:3001 locally; https://canvas.21xventures.com in prod

Bring your own Supabase project: create a project, run every migration in app/supabase/migrations/

in order (supabase db push

from app/supabase/

, or paste each file into the SQL editor), then copy the URL + keys from your project's API settings. app/.env.example

is the source of truth for which vars are needed.

npm run dev             # next dev on :3001
npm run build           # next build
npm run start           # next start on :3001
npm run lint            # eslint (next config) β€” must be clean before merging
npm run test            # vitest run (parser + MCP server unit tests)
npm run test:watch      # vitest watch
npm run e2e             # full path against live Supabase + dev server (see below)
npm run sweep-orphans   # delete Storage objects whose deck row no longer exists

End-to-end exercise of the full Canvas v1 path against a live Supabase project. Requires npm run dev

running in another terminal, plus E2E_USER_ID

and E2E_WORKSPACE_ID

set to a user + workspace that exist in your project. Imports the seed deck at app/tests/fixtures/seed-deck.html

(gitignored β€” drop in any full HTML deck), mints an MCP token for the test user, walks the JSON-RPC surface, fetches preview / asset / export endpoints, then cleans up everything it created. Source: app/scripts/e2e-canvas.mts.

One-shot maintenance β€” lists every object under the decks

bucket and deletes any whose deck_id

path segment has no matching canvas_deck

row. Safe to run repeatedly. Source: app/scripts/sweep-orphans.mts.

21x-canvas/
β”œβ”€β”€ README.md                       you are here
β”œβ”€β”€ CONTEXT.md                      domain glossary β€” names of things
β”œβ”€β”€ DESIGN.md                       why it exists, v1 success criteria
β”œβ”€β”€ docs/adr/                       architectural decisions (planned)
└── app/                            the Next.js app
    β”œβ”€β”€ AGENTS.md                   gotchas for AI coding assistants (Next 16, proxy.ts)
    β”œβ”€β”€ CLAUDE.md                   includes AGENTS.md
    β”œβ”€β”€ src/
    β”‚   β”œβ”€β”€ proxy.ts                middleware (Next 16 renamed `middleware.ts` β†’ `proxy.ts`)
    β”‚   β”œβ”€β”€ app/
    β”‚   β”‚   β”œβ”€β”€ (auth)/             login flow
    β”‚   β”‚   β”œβ”€β”€ api/                preview, asset, export, mcp routes
    β”‚   β”‚   β”œβ”€β”€ canvases/           list, new, [id] editor, inbox, history, proposals
    β”‚   β”‚   └── settings/mcp/       per-user MCP token issuance
    β”‚   β”œβ”€β”€ components/             shared UI + proposal-diff / proposal-iframe
    β”‚   └── lib/
    β”‚       β”œβ”€β”€ auth/               server-action helpers + user resolution
    β”‚       β”œβ”€β”€ canvas/             parser, assembler, mcp server, edit RPCs
    β”‚       └── supabase/           ssr / browser / admin clients
    β”œβ”€β”€ supabase/migrations/        0001 schema β†’ 0005 proposals
    β”œβ”€β”€ tests/                      vitest specs + fixtures
    └── scripts/                    e2e + sweep-orphans

Vitest covers the deck parser and the MCP server. tests/fixtures/mini-deck.html

is the committed minimal fixture; tests/fixtures/seed-deck.html

(a larger real-world deck) is gitignored β€” drop in any full HTML deck if you want to run the e2e or reproduce parser bugs against a complex layout. Any *.real.html

you drop into tests/fixtures/

is similarly gitignored.

This app runs on Next 16.2 + React 19.2, which has breaking changes vs. anything older. The most likely-to-trip-you-up ones:

. The exported function isproxy

, notmiddleware

. - Server Components are the default; client components must be explicitly marked.

  • React 19's react-hooks

lint rules are stricter β€” setState in effects and refs-during-render are errors, not warnings. Use the "adjust state on prop change" pattern (track previous prop, compare during render) rather than effects when resetting derived state.

When in doubt about an API, read the relevant guide in app/node_modules/next/dist/docs/

before writing code. See app/AGENTS.md.

Project: bring your own Supabase project (the reference deployment is namedcanvas-prod

)Region:us-east-1

, Postgres 17Schema: Canvas tables live inpublic

with thecanvas_*

prefix (not a separate Postgres schema β€” avoids the "exposed schemas" config step in Supabase Studio). Tenancy tables (workspaces

,users

,workspace_memberships

,workspace_invites

) sit inpublic

without a prefix; they're a verbatim port of the workforce-management foundation β€” see migration 0000 andADR-0004.Storage bucket:decks

(created by migration 0003; assets underassets/{deck_id}/...

)Migrations: apply in order viasupabase db push

from theapp/supabase/

directory, or pasted into the dashboard SQL editor. They're idempotent enough that re-applying is generally safe, but read the file before you doRLS: every Canvas table usespublic.is_workspace_member

/public.is_workspace_admin_or_owner

.public.workspaces

hasno INSERT policy forβ€” workspaces are created only via the service-role admin client (authenticated

createWorkspaceAction

insrc/lib/auth/actions.ts

)

The MCP route resolves token β†’ user/workspace and then enforces deck access explicitly. Content writes are propose-first; the optional trusted fast lane is deck-scoped, patch-only, and requires render verification.

Domain:canvas.21xventures.com

(DNS / hosting TBD β€” Vercel likely)Auth callback: Canvas uses its own OAuth client. Wirehttps://<your-project-ref>.supabase.co/auth/v1/callback

into that client's redirect URIs, and put your production app URL into Supabase Auth β†’ Redirect URLs.

npm run lint

β€” cleannpm test

β€” greennpx tsc --noEmit

β€” clean- If you touched migrations, sanity-check the SQL against the dashboard before pushing (RLS is easy to break)

── more in #developer-tools 4 stories Β· sorted by recency
── more on @canvas 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain β€” perfect for shipping the agent you just read about.

$git push zahid main
β†’ Live at https://your-agent.zahid.host βœ“
Get free account β†’ Pricing
from €0/mo Β· no card required
LIVE [news/show-hn-canvas-multi…] indexed:0 read:6min 2026-07-13 Β· β€”