Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
6729af2
feat(plugins): site plugins foundations — reserved site.* namespace, …
DavidBabinec Jul 12, 2026
c3140d0
refactor(plugin-sdk): extract shared plugin-build core with fail-clos…
DavidBabinec Jul 12, 2026
46cdc80
feat(site-plugins): server engine — source model, workspace build, li…
DavidBabinec Jul 12, 2026
21c97bf
feat(plugins): full-screen Plugin IDE with live multi-author co-editing
DavidBabinec Jul 12, 2026
5753540
feat(site-plugins): session-local draft canvas preview, export guaran…
DavidBabinec Jul 12, 2026
f7b9ea7
fix(site-plugins): live smoke-test findings — slim SDK inlining, arte…
DavidBabinec Jul 12, 2026
80e2f89
fix(plugins): IDE visual parity with the workspace shell — panel chro…
DavidBabinec Jul 12, 2026
ce689c6
fix(collab): connect the site socket directly to the CMS in dev — Vit…
DavidBabinec Jul 12, 2026
2786e80
refactor(plugin-ide): drop the structured manifest panel
DavidBabinec Jul 12, 2026
e03fab9
feat(auth): plugins.edit — a dedicated site-plugin authoring capability
DavidBabinec Jul 12, 2026
46994e7
feat(ai): plugin chat scope — toolset, system prompt, activation engine
DavidBabinec Jul 12, 2026
ec96fbd
feat(plugin-ide): AI chat panel — plugin scope agent with live file t…
DavidBabinec Jul 12, 2026
32f7f65
test(ai): plugin toolset selection, lifecycle handlers, browser bridge
DavidBabinec Jul 12, 2026
faad2ad
fix(plugin-ide): live-test fix round — stale metas, humanized tool ro…
DavidBabinec Jul 12, 2026
ad9e86c
feat(plugins): one merged plugin list + always-visible agent code blocks
DavidBabinec Jul 12, 2026
cd799f4
refactor(code-editor): extract codeMirrorShared — theme + language st…
DavidBabinec Jul 12, 2026
aa55f87
feat(ai): plugin_docs — authoritative on-demand reference for the IDE…
DavidBabinec Jul 12, 2026
b0f7a46
fix(ai): insert patch replacement text verbatim
DavidBabinec Sep 2, 2026
21f0d46
fix(plugin-build): refuse build-time macros before Bun parses draft s…
DavidBabinec Sep 2, 2026
ee8c4e1
fix(site-plugins): serialize lifecycle per plugin and re-bake data ro…
DavidBabinec Sep 2, 2026
22eb4d3
fix(code-editor): keep CodeMirror history out of the co-edited buffer
DavidBabinec Sep 2, 2026
9e10865
fix(plugin-ide): gate writes on sync, rebind after relay resets, gate…
DavidBabinec Sep 2, 2026
4f8c609
fix(plugin-build): report bundler positions against the author's file
DavidBabinec Sep 3, 2026
1d58fe2
fix(plugin-ide): runtime errors inline, honest actions, Escape closes…
DavidBabinec Sep 3, 2026
aaa169d
fix(site-plugins): refuse rebuilds of a deactivated plugin, surface r…
DavidBabinec Sep 3, 2026
8c34c66
refactor(plugin-ide): shared text helpers, scoped agent store, single…
DavidBabinec Sep 3, 2026
04769c3
feat(plugin-ide): name the files panel after the plugin and show stat…
DavidBabinec Sep 3, 2026
36dbfd0
feat(plugin-ide): one split button for every action, version beside t…
DavidBabinec Sep 3, 2026
a78379d
feat(site-plugins): keep five builds and pick the rollback target
DavidBabinec Sep 3, 2026
17dcc88
refactor(site-plugins): fold the permission review into the activatio…
DavidBabinec Sep 3, 2026
073304e
fix(plugin-ide): nest the file tree like the Layers panel
DavidBabinec Sep 3, 2026
34777ab
fix(site-plugins): adapt the Plugin IDE to site branches after the re…
DavidBabinec Sep 4, 2026
1d2b561
test(site-plugins): adapt the suites to branch-scoped repositories
DavidBabinec Sep 4, 2026
05d9ee4
test(site-plugins): cover the Plugin IDE end to end
DavidBabinec Sep 5, 2026
5e363a4
test(plugins): point the capability helper at the merged plugin list
DavidBabinec Sep 5, 2026
7c59b25
fix(site-plugins): settle the branch on main after the rebase
DavidBabinec Sep 11, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ Read [`docs/architecture.md`](docs/architecture.md) for the system overview, [`d
- **Real-time co-editing:** Yjs CRDT engine. One Y doc per row (`page:<id>`, `component:<id>`, `layout:<id>`) + one site-shell doc, multiplexed over `/admin/api/cms/site-socket`. The editor store stays the render source of truth: local mutations apply directly AND translate to Y ops (`@core/collab`); remote/undo changes project back. The server relay (`server/collab/`) persists continuously (blob + derived row JSON) — there is NO client-side save pipeline, no autosave, no Cmd+S. Undo is per-editor per-doc `Y.UndoManager`. Feature doc: [`docs/features/site-shell.md`](docs/features/site-shell.md) → "Real-time co-editing".
- **Validation:** TypeBox at every untyped boundary. Schemas are source of truth (`type Foo = Static<typeof FooSchema>`, never a parallel `interface`). `zod` is banned repo-wide (the AI drivers pass TypeBox schemas through as JSON Schema, so no typebox→zod adapter is needed). Helpers + patterns: [`docs/reference/typebox-patterns.md`](docs/reference/typebox-patterns.md).
- **Sanitization:** DOMPurify at the publisher boundary (`src/core/sanitize.ts`).
- **Plugins:** Zip packages with a `plugin.json` manifest, lifecycle hooks. Server entrypoints and canvas module packs run inside a **QuickJS-WASM sandbox** — no Node/Bun ambient access, network gated by `network.outbound` permission + `networkAllowedHosts`. The VM bootstrap (SDK factory + `__run*` dispatchers) is authored as typed TS in `server/plugins/quickjs/bootstrap/src/` and bundled to committed string artifacts in `bootstrap/generated/` — after editing the source run `bun run bootstrap:sync` (gated by `plugin-bootstrap-fresh.test.ts`). Permission enforcement everywhere (VM, host, editor) validates against `grantedPermissions`, never the declared `permissions` array. Feature doc: [`docs/features/plugin-system.md`](docs/features/plugin-system.md).
- **Plugins:** Zip packages with a `plugin.json` manifest, lifecycle hooks. Server entrypoints and canvas module packs run inside a **QuickJS-WASM sandbox** — no Node/Bun ambient access, network gated by `network.outbound` permission + `networkAllowedHosts`. The VM bootstrap (SDK factory + `__run*` dispatchers) is authored as typed TS in `server/plugins/quickjs/bootstrap/src/` and bundled to committed string artifacts in `bootstrap/generated/` — after editing the source run `bun run bootstrap:sync` (gated by `plugin-bootstrap-fresh.test.ts`). Permission enforcement everywhere (VM, host, editor) validates against `grantedPermissions`, never the declared `permissions` array. Feature doc: [`docs/features/plugin-system.md`](docs/features/plugin-system.md). **Site plugins** are authored in the site draft (`plugins/<local-id>/`, `SiteFileType: 'plugin'`), developed in the full-screen Plugin IDE (`/admin/plugins/develop/<local-id>`, live co-editing over the collab socket), built server-side through the shared `@core/plugin-build` core with fail-closed import containment, and activated as ordinary `installed_plugins` rows (`source: 'site-local'`) — the runtime never branches on provenance. Doc: [`docs/features/site-plugins.md`](docs/features/site-plugins.md).
- **Routing:** In-house router at `src/admin/lib/routing/`. Replaces `react-router-dom`. Use it for all internal admin navigation, including links rendered from the site editor. `react-router-dom` is banned, raw `<a href="/admin...">` hard navigations are banned in admin UI, and `src/core/` + `src/modules/` must not import the admin router. Gated by `admin-router-usage.test.ts`.
- **Icons:** `pixel-art-icons/icons/<name>` — deep-imported, tree-shakeable. Vendored at `vendor/pixel-art-icons/`. No `lucide-react`, no inline SVG strings — gated by `no-third-party-icons.test.ts`, `direct-icon-imports.test.ts`. Add a new icon by importing it and running `bun run icons:sync`.
- **AI providers:** No provider SDKs. Each driver in `server/ai/drivers/` talks directly to its provider's REST API over HTTP/SSE, sharing one multi-turn tool loop (`drivers/http/toolLoop.ts`). `@anthropic-ai/sdk`, `@anthropic-ai/claude-agent-sdk`, `@openai/agents`, and `@openrouter/agent` are banned repo-wide. The official split `@modelcontextprotocol/server` / `@modelcontextprotocol/client` v2 packages are **scoped, not banned**: allowed only under `server/ai/mcp/` (Instatic's MCP *server* implements a real wire protocol), still banned in the drivers and the browser. Gated by `ai-driver-isolation.test.ts`.
Expand Down
11 changes: 8 additions & 3 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions docs/e2e/feature-matrix.md
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,7 @@ AI-002 note: Data-scope default selection, save feedback, reload persistence, cl
| PLUGIN-005 | P2 | partial | Plugins | Inspect and control plugin schedules | Active plugin with schedules | Plugin schedule dialog | Schedules list and mutating controls respect permissions | stale paused state, duplicate runs |
| PLUGIN-006 | P2 | partial | Plugins | Install a plugin-provided site pack | Active plugin with pack | Plugins page | Pack content imports with clear feedback | conflicts, partial imports |
| PLUGIN-008 | P2 | ✅ | Plugins | Upload invalid plugin package | Logged in | Plugins page | Error is specific and recoverable | generic failure, stuck upload |
| SITEPLUGIN-001 | P1 | ✅ | Plugins | Author, build and activate a site plugin in the Plugin IDE | Logged in with plugins.edit | Plugins page, Plugin IDE | Scaffold opens in the IDE, builds cleanly, activates through the permission review, and an edit reads as Draft changed | stale build served after an edit, activation without consent |

PLUGIN-001 note: JSON manifest review/install step-up is automated in `capabilities.e2e.ts`; ZIP package review/install and activation are exercised by the packaged lifecycle/surfaces fixtures in `plugins.e2e.ts`; malformed ZIP/path-traversal/package-edge coverage remains lower-level or future browser expansion.

Expand Down
10 changes: 5 additions & 5 deletions docs/features/agent.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ src/admin/pages/site/agent/

src/admin/pages/content/agent/
├── agentSliceConfig.content.ts — content-workspace config: scope, snapshot builder, executor wiring
├── contentAgentStore.ts — standalone per-mount Zustand store (AgentSlice only)
├── (store: src/admin/ai/createScopedAgentStore.ts — one AgentSlice-only Zustand instance per mount, shared with the Plugin IDE)
├── contentBridge.ts — content workspace write-tool executor
├── contentBridgeHandle.ts — live ContentPage operation handle
└── useContentToolBridge.ts — always-mounted handle + content-scope MCP relay
Expand Down Expand Up @@ -316,7 +316,7 @@ The handler (`server/ai/handlers/chat.ts`):
7. Calls `runChat(...)` with the full history as `req.messages`. Direct HTTP drivers have no server-side session, so each driver maps the whole `AiMessage[]` log into the provider's native message array every turn (the Anthropic driver pairs assistant `tool_use` blocks with their following `tool_result` turns). The runner pipes all stream events to the HTTP response. Before recording a terminal usage event, the runner flushes any pending assistant text so text-only replies have an assistant message row for per-turn usage and audit rollups. The multi-turn agentic loop lives in `drivers/http/toolLoop.ts`, not in a provider SDK.
8. Emits a terminal `ai.chat.completed` / `ai.chat.failed` audit event.

Valid scopes are `site`, `content`, `data`, and `plugin`; only `site` and `content` currently register tools and prompts. The handler rejects a request when the URL scope does not match the `ai_conversations.scope` row.
Valid scopes are `site`, `content`, `data`, and `plugin`; `site`, `content`, and `plugin` register tools and prompts, `data` has none yet. The handler rejects a request when the URL scope does not match the `ai_conversations.scope` row.

### `GET /admin/api/ai/audit?since=ISO&tz=IANA`

Expand Down Expand Up @@ -520,7 +520,7 @@ When a node-targeting write tool (`site_insert_html`, `site_get_node_html`, `sit
Content-scope tools are registered under `server/ai/tools/content/`. They use the same `POST /admin/api/ai/chat/content` stream and `POST /admin/api/ai/tool-result` bridge as the Site editor, but the snapshot and browser executor are content-specific:

- `ContentAgentMount` builds a `ContentSnapshot` from the live Content workspace: visible `postType` collections, active collection id, active document fields/schema, and current user identity.
- `contentAgentStore.ts` mounts a standalone `AgentSlice` instance per `ContentPage` mount. The Content workspace is hook-based rather than a global Zustand store, so the bridge is exposed through `contentBridgeHandle.ts`.
- `ContentAgentMount` creates a standalone `AgentSlice` instance per `ContentPage` mount through `createScopedAgentStore` (`src/admin/ai/`, shared with the Plugin IDE). The Content workspace is hook-based rather than a global Zustand store, so the bridge is exposed through `contentBridgeHandle.ts`.
- Server read tools hit the data, media, and user repositories through `ctx.db`; write tools are browser-bridged so unsaved draft state in `useContentEntryDraft` and the Tiptap body editor stay authoritative.

**Server-side content reads — 7**
Expand Down Expand Up @@ -634,7 +634,7 @@ export const siteAgentSliceConfig: AgentSliceConfig = {

`getAgentStoreApi` reads the live store via `storeRef.ts`, wired in `store.ts` after store creation (`setAgentStoreApi(useEditorStore)`). This avoids a static import cycle: executor → store → agentSlice → executor.

The content workspace uses the same factory with `contentAgentSliceConfig` mounted in a standalone per-page store (`contentAgentStore.ts`).
The content workspace uses the same factory with `contentAgentSliceConfig` mounted in a standalone per-page store (`createScopedAgentStore`, shared with the Plugin IDE).

`agentProviderUpdate.ts` owns the existing-conversation provider/model PUT and its failure reconciliation. A definite 4xx can roll the picker back to the re-read row; a timeout, network failure, or 5xx stays fail-closed unless the re-read already proves that the requested selection committed. `agentSlice.ts` keeps the ordering queue and Send lock because those coordinate store actions rather than HTTP persistence.

Expand Down Expand Up @@ -864,7 +864,7 @@ unblocks deletion of the credential that had been protected by the default FK.
- `src/admin/pages/site/agent/types.ts` — `ServerStreamEvent`, `AgentMessage`, `AgentRequestBody`, …
- `src/admin/pages/site/agent/index.ts` — public barrel
- `src/admin/pages/content/agent/ContentAgentMount.tsx` — content workspace AgentPanel mount + live bridge handle registration
- `src/admin/pages/content/agent/contentAgentStore.ts` — standalone content-workspace agent store
- `src/admin/ai/createScopedAgentStore.ts` — standalone per-mount agent store factory (Content workspace, Plugin IDE)
- `src/admin/pages/content/agent/contentBridge.ts` — content write-tool browser dispatcher
- `src/admin/pages/content/agent/contentBridgeHandle.ts` — imperative bridge handle registered by ContentPage
- `src/admin/pages/site/panels/AgentPanel/AgentComposer.tsx` — resolves model window/pricing/capabilities and places the meter in the action row
Expand Down
2 changes: 1 addition & 1 deletion docs/features/content-workspace.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ The mode switch is client-only. The markdown body is the source of truth in both

The Content workspace has its own `content` chat scope, mounted as the `agent` panel in `ContentSidebar` when the current user has `ai.chat`.

`ContentAgentMount` creates a fresh per-page `AgentSlice` store (`contentAgentStore.ts`) for the visible chat panel. Independently, `ContentPage` always mounts `useContentToolBridge`, which registers a `ContentBridgeHandle` and the `content` MCP stream for as long as the workspace is open. The handle reads the current collections, selected entry, draft fields, schema, and current user via refs so either caller sees the same state the user sees. Tool writes go through that handle and then through `useContentWorkspace` / `useContentEntryDraft`, which keeps unsaved body/title/SEO/media changes and sidebar selection in sync. `content_set_active_document` loads an uncached row by id, switches across post-type collections without waiting for the target sidebar list, and commits the workspace and draft focus before it returns so an immediately following write targets the selected document.
`ContentAgentMount` creates a fresh per-page `AgentSlice` store (`createScopedAgentStore`) for the visible chat panel. Independently, `ContentPage` always mounts `useContentToolBridge`, which registers a `ContentBridgeHandle` and the `content` MCP stream for as long as the workspace is open. The handle reads the current collections, selected entry, draft fields, schema, and current user via refs so either caller sees the same state the user sees. Tool writes go through that handle and then through `useContentWorkspace` / `useContentEntryDraft`, which keeps unsaved body/title/SEO/media changes and sidebar selection in sync. `content_set_active_document` loads an uncached row by id, switches across post-type collections without waiting for the target sidebar list, and commits the workspace and draft focus before it returns so an immediately following write targets the selected document.

The server registers 15 content-scope tools:

Expand Down
Loading
Loading