\n```\n\nthe global `canvasdrop` appears. identity rides the signed-in session; the canvas is identified from its own url. every method throws a typed, `instanceof`-catchable error.\n\n```js\nconst me = await canvasdrop.me(); // { id, email, name, avatarurl, kind }\nconst votes = await canvasdrop.kv.increment(\"votes\", 1);\nawait canvasdrop.kv.user.set(\"theme\", \"dark\"); // per-viewer scope\nconst f = await canvasdrop.files.upload(input.files[0]); // { id, name, size, url }\nfor await (const delta of canvasdrop.ai.stream(messages, { model })) out.textcontent += delta;\nconst ch = canvasdrop.realtime.channel(\"room\"); ch.subscribe(render); ch.publish(\"cursor\", { x, y });\n```\n\n| primitive | what it gives a canvas |\n|-----------|------------------------|\n| **kv** | shared (`kv.*`) and per-viewer (`kv.user.*`) key/value with `list` and atomic `increment`. |\n| **files** | per-canvas upload/list/delete; served as safe, non-executable bytes. |\n| **ai** | anthropic-first proxy behind a provider abstraction: streaming, model allowlist, metered quotas. the provider key stays server-side. |\n| **identity** | `me()`: id, email, name, avatar, and `kind` (`member` or `guest`), resolved from org auth, never the client. |\n| **realtime** | ephemeral broadcast plus presence per canvas. revoking a share drops the socket instantly. |\n\nthe full, agent-optimized contract is served live at **`{base}/llms.txt`**. see also `docs/sdk.md`.\n\n---\n\n## for ai agents\n\ncanvas-drop treats agents as first-class authors. three ways in, all thin clients of the same service layer:\n\n- **deploy api**: a per-canvas key `put`s files straight to a live version (above).\n- **agent skill**: an installable skill (`docs/site/agents/skill.md`) with the full sdk contract, so any coding agent builds canvases out of the box.\n- **mcp server** (`docs/site/agents/mcp.md`): a connect-once remote server at `{base}/mcp`, signing in through your org's own login (oauth 2.1). it exposes identity-scoped tools at **full dashboard parity**, so anything a person can do in the ui (deploy, version, roll back, share, edit the draft, set the preview cover, clone, read usage) an agent can do over mcp. on by default; disable with `canvas_drop_mcp=off`.\n\n---\n\n## configuration\n\neverything is set by environment variables, validated at boot, with a precise message on an invalid combination. full surface in `.env.example`. swappable drivers:\n\n| concern | options | env |\n|---------|---------|-----|\n| database | sqlite, postgres | `canvas_drop_db` |\n| storage | local disk, s3-compatible (aws s3, minio, cloudflare r2) | `canvas_drop_storage` |\n| url mode | path, subdomain | `canvas_drop_url_mode` |\n| auth | `proxy` (recommended prod), `oidc`, `dev` | `canvas_drop_auth_mode` |\n| email (invite notifications) | `log`, `smtp`, `mailgun`, `noop` | `canvas_drop_email_driver` |\n| tenancy (org boundary, off by default) | name an org so guests can't see whole-org canvases | `canvas_drop_org_name` |\n\nthe blessed production profile is **subdomain mode plus an identity-aware proxy** (e.g. cloudflare access) verifying a signed jwt, with postgres and s3.\n\n**tenancy (optional).** set `canvas_drop_org_name` to draw a member-vs-guest boundary: members (by verified email domain) can share to the **whole org**, while brought-in guests only see canvases they're invited to. it's **inert until named** \u2014 deploy first, migrate later. migrating an existing instance is a dry-run-first cutover; see `docs/tenancy.md`.\n\nday-to-day operation lives in the in-app **admin panel**: the all-canvases list with usage, disable/takedown/restore, the ai model allowlist, and global quota defaults. operator-tunable settings are editable there or via env; the auth and rate-limit hot path stays read-only.\n\n---\n\n## self-host (docker, ~5 min)\n\nrequires **docker** and **docker compose v2**. the repo ships a one-command demo stack that runs canvas-drop in its real `proxy` mode behind an identity-aware proxy (oauth2-proxy) and a bundled demo idp (dex), so you can try the production shape with zero external setup:\n\n```bash\ndocker compose up --build # first build takes a few minutes; add -d for background\n# then open http://localhost:8080 and log in as demo@example.com / canvasdrop\ndocker compose down -v # tear down and wipe data\n```\n\nthe app verifies a dex-signed jwt against dex's jwks, the same cryptographic trust path you run in production, with postgres for data and an optional minio profile for s3. the app is never exposed directly; only the proxy is.\n\n> **the demo stack is for local evaluation only.** its secrets and the `demo@example.com` login are public placeholders, and it runs on plain http in path mode. rotate every secret and follow the graduation checklist before any real use.\n\ngoing to production is a configuration change: copy `.env.production.example`, point the proxy/jwks at your real idp, move to subdomain mode behind real tls, and rotate all secrets. full walkthrough: `docs/site/self-hosting/deploy.md`.\n\n---\n\n## security model\n\ncanvas-drop runs inside a **trusted organization**: everyone reaching it has passed org sso, and an email-domain allowlist keeps outsiders out. that posture deletes whole problem classes (anonymous abuse, spam, public bot threats). what remains is a short list of **hard invariants that must never break** (`build_brief.md` \u00a712.0):\n\n1. **no impersonation.** identity always comes from the server-side auth context, never anything the client sends.\n2. **no credential or canvas theft.** api keys and tokens are hashed at rest and shown once; canvas passwords are argon2id.\n3. **no unauthorized access.** a canvas is reachable only by its owner and whoever its access rung allows. an admin gets no special access to canvases it does not own; cross-owner admin power is limited to the dedicated admin routes (list, disable/enable/restore). everything else 404s.\n4. **no cross-canvas reach in subdomain mode.** each canvas is its own browser origin and cannot read, write, or act on another's data.\n5. **lifecycle is honored instantly.** revoke, expiry, disable, delete, slug-regen, and key-regen take effect on the next request and drop live sockets.\n\n**url-mode isolation is a real choice.** subdomain mode (`{slug}.example.com`) gives full browser-origin isolation and is recommended for any multi-user production deployment. path mode (`host/c/{slug}/`) shares one origin and is perfect for localhost and trusted single-user hosting; multi-user path mode requires an explicit opt-in and an admin warning. no secrets ever reach the browser; every endpoint is zod-validated; uploads are zip-slip checked and served as inert bytes; an audit log records auth, deploys, sharing, ai usage, and admin actions.\n\n---\n\n## architecture\n\n```\napps/server hono server, one role-routed process: api, deploy, hosted canvases\napps/dashboard vite + react spa dashboard\npackages/shared zod config, dual-dialect drizzle schema, shared types\npackages/sdk the zero-config browser sdk (the `canvasdrop` global)\ndocs/ build_brief, plans, compounding learnings, docs-site source\n```\n\n**dual-dialect is sacred:** sqlite and postgres stay in lockstep via shared column helpers, and the ci matrix runs the full suite on both. **config is the only `process.env` reader.** everything load-bearing sits behind an interface, so the four drivers swap by config alone.\n\n---\n\n## status\n\n**v1 is feature-complete and hardening toward a public release**, built unit-by-unit from `build_brief.md` with ci green on both dialects at every merge. m1 through m9 plus a wave of post-v1 work (the sharing ladder, shared discovery, the mcp server at full dashboard parity, clone-as-template, staged uploads, custom slugs, optional canvas screenshots, and an admin-flippable design-skin layer) have shipped; ops/packaging (m10) is the only milestone still in progress \u2014 its docker image/compose, **backup/restore + scheduled maintenance** (`docs/ops.md`), and secret-scan are in, with the single-vps load test and the iap colleague pilot still deferred. see `docs/plans/`.\n\n> **maturity, honestly:** canvas-drop is **not yet running in production anywhere serious.** it boots, passes a dual-dialect suite, and self-hosts via docker, but it has not been battle-tested under a real org's load yet. that is exactly what is next. self-host reports, issues, and prs are very welcome.\n\n---\n\n## commands\n\n```bash\npnpm dev # server + dashboard in watch mode\npnpm test # full suite, both dialects (sqlite + pglite) in-process\npnpm lint # biome check\npnpm format # biome check --write (also sorts imports)\npnpm typecheck # tsc --noemit across server, sdk, dashboard\npnpm build # build all workspace packages\npnpm purge [days] # reclaim storage from soft-deleted canvases (dry-run supported)\npnpm backup # full instance backup (db + storage) \u2192 a portable directory\npnpm restore # restore a backup into a fresh, empty instance\n```\n\ndeleting a canvas is a soft-delete (a tombstone row). `pnpm purge` is the maintenance sweep that reclaims the heavy data; it reads the same config as the server. **`backup`/`restore`** capture and recover the whole instance (every table + every content-addressed blob) in a driver-agnostic format \u2014 so they also migrate between drivers (sqlite\u2194postgres, local\u2194s3). in production these are subcommands of the server binary (`node \u2026 apps/server/dist/index.js {backup,restore,purge}`), so cron runs them with the app image. full runbook + a recommended backup/purge cron schedule: `docs/ops.md`.\n\n---\n\n## credits\n\ninspired by [quick: an internal hosting platform for the ai era](https://shopify.engineering/quick) by daniel beauchamp and alex pilon, and daniel's [thread](https://x.com/pushmatrix/status/2064722585019969727) on how a folder of files, a lightweight api, and some trust changed how shopify builds. mit licensed; not affiliated with shopify.\n\n## contributing\n\ncanvas-drop is built by humans and ai coding agents working from the same contract, following the [compound engineering](https://every.to/guides/compound-engineering) practice from every: plan the work, build one unit at a time with its tests, and capture every non-obvious learning so each pass (human or agent) compounds on the last. work flows from plans in `docs/plans/`, ci green on both dialects before merge. start with `contributing.md` and `agents.md`; institutional learnings compound in `docs/solutions/`.\n\n## license\n\nmit, see `license`.", "installation_instructions": null, "categories": [ "Everything" ], "owners": [], "owner": null, "code_snippets": {}, "evaluation_results": [], "found_via_ownership_request": false, "hosting_eligible": false, "knative_enabled": false, "security_scans": [] } }