Skip to content

feat(plugins): author, build and activate site plugins in a full-screen IDE - #495

Draft
DavidBabinec wants to merge 36 commits into
feat/site-branchesfrom
feat/site-plugins
Draft

feat(plugins): author, build and activate site plugins in a full-screen IDE#495
DavidBabinec wants to merge 36 commits into
feat/site-branchesfrom
feat/site-plugins

Conversation

@DavidBabinec

Copy link
Copy Markdown
Contributor

What

Site plugins: author a plugin inside the site itself, in a full-screen IDE, and activate it through the same lifecycle an uploaded package uses.

  • Authoring in the site draft. A plugin lives at plugins/<local-id>/ as ordinary site files (SiteFileType: 'plugin'), so it forks, previews and merges with the site like any other content.
  • The Plugin IDE at /admin/plugins/develop/<local-id>: file tree, co-edited CodeMirror buffer, diagnostics strip, and an AI chat panel scoped to the plugin. Two admins can type in one file character by character, with per-peer carets, over the existing collab socket.
  • Build and activate. A server-side build runs through the shared @core/plugin-build core with fail-closed import containment, then activation goes through the normal permission review and step-up. Five builds are retained so a bad one can be rolled back.
  • One merged plugin list. Uploaded packages and site-authored plugins appear together; the runtime never branches on provenance, only installed_plugins.source records it.
  • plugins.edit, a dedicated authoring capability, separate from installing.

Feature doc: docs/features/site-plugins.md.

Why

Writing a plugin previously meant leaving the CMS, building a zip, and uploading it. That is a poor loop for the small, site-specific extensions this system is meant to encourage, and it puts the source outside the thing that versions it.

Impact

  • Users: a new "New site plugin" action in the Plugins workspace, and an IDE to develop in. Nothing changes for uploaded packages.
  • Developers: @core/plugin-build is now shared by both paths. Migration 029 adds installed_plugins.source, additive with a constant default.
  • Stacked on feat(branches): site branches with preview links, three-way merge, and version restore #484. This targets feat/site-branches and should merge after it. The branches work is a hard dependency: this branch was rebased onto it, and its migration sits behind the two the branches PR adds.

Verification

bun run build
bun test          # 7005 pass, 0 fail
bun run lint
bunx playwright test tests/e2e/site-plugins.e2e.ts

E2E SITEPLUGIN-001 covers the real loop end to end: scaffold a plugin, confirm it opens in the IDE with real source, build and activate it through the permission review and step-up, see it in the merged list, then edit the server entry and watch the state fall back to "Draft changed". The IDE had no browser coverage before this.

Notes for review

This branch was based on a two-month-old main and carried an early version of the collab layer that main later landed differently. The rebase dropped those superseded commits and replayed only the plugin work, resolving each conflict by keeping the evolved code and porting the plugin's contribution onto it. Two commits were dropped entirely because main already had the same fix in better form.

DavidBabinec and others added 30 commits September 5, 2026 11:42
…source column, plugin file type

- Reserve the site.* plugin-id namespace on the zip-install boundary
  (readPluginPackage) so uploaded packages can't hijack a site plugin's
  identity, grants, settings, or secrets. The manifest parser keeps
  accepting site.* ids — generated site-plugin packages parse through it.
- Migration 022 (both dialects): installed_plugins.source text not null
  default 'installed' — provenance for display + lifecycle routing only.
- Plumb source through the plugins repository and InstalledPlugin type.
- Add SiteFileType 'plugin' with an isolation gate pinning that plugin
  source never enters runtime scripts, user stylesheets, explorer
  sections, or any published-output pipeline.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ed import containment

- New @core/plugin-build: buildPluginPackage (manifest write + per-surface
  bundling), bundleEntrypoint (one Bun.build choke point: externals
  layering, sandbox facades, post-bundle scans), facade generation, and a
  containment resolver plugin.
- The CLI build (instatic-plugin build|dev) becomes a thin frontend:
  config evaluation, dist reset, pack/icon copy, and zipping stay CLI-side;
  bundling behavior is byte-identical (verified by init+build smoke runs
  for module and server kinds).
- containmentPlugin fails resolution closed to the workspace root:
  upward-relative escapes, absolute paths, and import-attribute payloads
  throw; bare specifiers must be explicitly mapped (SDK) or external
  (import-map-resolved). Symlinked roots (macOS /var → /private/var)
  are realpath-normalized so containment can't be silently disabled.
- Barrel gate covers @core/plugin-build; sandbox-invariant gate re-pinned
  to the shared core.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…fecycle endpoints, publish coupling

- @core/site-plugins: folder-convention discovery over draft SiteFile[],
  author-manifest validation (derived fields rejected via
  additionalProperties:false against the runtime manifest's own field
  schemas), runtime-manifest derivation through the REAL parser,
  order-independent content hash, generated 1.0.<n>+<hash> versions,
  scaffold templates, and the runtime-state machine shared by server + UI.
- server/plugins/sitePlugins: temp-workspace materializer (path-safe,
  realpath'd), build orchestrator (single-flight per plugin, validate-only
  mode returning the modules bundle for canvas preview, 30s timeout,
  fail-closed containment with '@instatic/plugin-sdk' mapped to the host
  SDK), and revision retention (active + previous kept for rollback).
- Extracted activatePluginPackageFromDisk from the zip install/upgrade
  path — site plugins ride the exact same lifecycle (install→activate,
  deactivate→migrate→activate with rollback), zero runtime branches.
- /admin/api/cms/site-plugins routes: list (union of draft folders and
  site-local rows with computed states), scaffold (site.structure.edit),
  validate + preview-pack (no elevated capability), activate/rollback
  (plugins.install; step-up only on first activation and grant changes),
  delete (row + assets + draft source; step-up).
- Publish coupling: visitor-facing activations republish before revision
  GC so baked pages never reference a deleted revision.
- sitePluginRoute SDK helper for stable frontend→backend calls.
- manifest.ts split into schemas + parser modules (size ceiling); the
  siteDocument file-type validator now derives from SITE_FILE_TYPES
  (killed a parallel hardcoded list that silently dropped 'plugin' files).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Collab core — granular shell files:
- The shell's files key becomes a per-file Y.Map (entry per file id,
  content as Y.Text): different files never collide, one file's content
  merges character-level, and deleting a file can't clobber a neighbour's
  concurrent edit. Patch translation handles membership via pre/post
  id-set diffs, skips pure index shifts by object identity, and splices
  content minimally; projection returns the ordered array (legacy LWW
  array layouts still project and upgrade on first write).

The IDE (/admin/plugins/develop/:localId):
- Renders in the shared workspace canvas shell: resizable file tree
  (Tree primitives + context menu; inline new/rename), co-edited
  CodeMirror buffer via a new CollabCodeMirrorEditor lazy sibling
  (y-codemirror.next: live splices, per-peer colored carets/selections,
  local-only per-file undo), diagnostics strip fed by debounced automatic
  validation, and the structured manifest panel (permissions checklist
  with the editor.code danger note, network hosts, derived fields
  read-only) writing plugin.json as minimal Y.Text splices.
- Its own collab session binds ONLY site:default over the site socket —
  server-seeded, no editor store, presence published in the shared
  awareness (site editors see IDE peers in their roster; IDE rows show
  who's editing which file).
- Toolbar: state chip + ONE smart primary action + Preview in canvas for
  module drafts + overflow (rollback/deactivate/restart/diagnostics/
  settings/delete); disabled-with-reason, never hidden.

Plugins page: Site plugins group (state chips, Open IDE) + New site
plugin dialog (live kebab-case id, template picker). Scripts section
relabeled 'Frontend scripts' with placement guidance; 'plugin' files stay
out of the site editor entirely.

Gates: workspace/circular/codemirror/bundle-size/token gates updated in
the same change per repo rules.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…tees, invariant gates, docs

- Preview in canvas: the site editor's ?previewSitePlugin=<localId> loads
  the draft's validate-only module bundle from the no-store preview-pack
  route, activates it through the SAME browser-side pack loader installed
  plugins use, and badges the modules 'Draft' in the inserter. Nothing
  registers server-side; leaving the page restores the active revision's
  registrations. Publishing a page that uses a site.* module with no
  active registration logs a publish warning naming the plugin.
- Export/import pinned: bundles carry plugin SOURCE ('plugin' shell files),
  never generated artifacts or runtime rows; imported drafts validate
  cleanly and require rebuild+activate on the target.
- site-plugin-invariants gate: zero 'site-local' branches outside the
  repository/engine/UI allowlist; worker/VM/host machinery never mentions
  site plugins; the zip boundary rejection, the containment policy call
  shape, and the plugins.install + conditional step-up authority are
  source-pinned.
- docs/features/site-plugins.md + cross-links from plugin-system.md,
  site-shell.md, and the CLAUDE.md stack bullet.
- Explorer tests updated for the Frontend scripts relabel.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…fact rebake on activation, consent dialog

Findings from the end-to-end browser smoke (scaffold → co-edit → validate
→ review → step-up → activate → preview → publish), all fixed:

- Mapping '@instatic/plugin-sdk' to the FULL SDK barrel inlined TypeBox
  (~390 kB) and tripped a bundler tree-shake edge ('Object3 is not
  defined') that broke module packs in BOTH the browser preview and the
  QuickJS publisher. New src/core/plugin-sdk/inlineRuntime.ts exposes
  exactly the pure-data surface (defineModule, control, html/raw/safeUrl,
  sitePluginRoute) + type-only re-exports; bundles drop to ~4 kB and run
  everywhere.
- republishAllPages re-rendered pages but never re-baked Layer-A static
  artefacts, so activation left published HTML referencing the OLD
  revision (the exact staleness the publish coupling exists to prevent).
  It now takes an optional uploadsDir and updates each page's artefact
  in place; the site plugin activation passes it.
- IDE lifecycle actions now fire notifyCmsPluginsChanged so open editor
  sessions reload the new revision's module pack without a page reload.
- Grant-changing activations show a permission-review dialog (new grants
  with labels, revocations, editor.code danger note) BEFORE the build —
  the consent moment the design requires, on top of server-side step-up.
- ideCollab upgrades a legacy LWW files array from its EXISTING entries
  (an empty replacement would have projected a shell that lost every
  other site file) and reads metas across both layouts.
- Build diagnostics rewrite throwaway-workspace paths to the draft's
  plugins/<id>/ paths — no server temp-dir leakage, the author's file
  is named.

Verified live: multi-author co-editing with peer carets across two
sessions, character merges, containment rejection of a used host-file
escape import, activation → auto-republish serving the new render, and
the scaffolded routes plugin answering through the QuickJS runtime.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…me, canvas surface, explorer-identical tree

Layout/design review feedback:

- Side panels no longer paint their own gray backgrounds — FileTreePane
  and ManifestPane now render inside the shared Panel shell (transparent
  over the sidebar's --bg-body black, standard PanelHeader with title +
  actions + close), exactly like the DOM panel and Content explorer.
- The editor column gets the standard elevated canvas treatment (the
  Data grid pattern): --bg-surface-2 with 16px rounded top corners over
  the black shell; the diagnostics strip is transparent chrome on that
  surface. All invented backgrounds removed.
- Tree rows recomposed to the Site Explorer's exact pattern: TreeRow as
  the shell with a ghost-Button hit surface (treeRowButton reset),
  TreeChevron + TreeIconSlot(icon=) + TreeLabelGroup, F2/double-click
  inline rename using the explorer's select-all/commit-on-blur behavior.
- The Files panel's ✕ collapses to the rail (state lifted to the page);
  the rail button and the manifest edge-notch reopen their panels —
  same affordances as Content/Data.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…e's ws proxy is broken under Bun

Testing on the standard `bun run dev` stack surfaced that the collab
WebSocket never connects through the Vite proxy: Vite's bundled
http-proxy calls socket.destroySoon on the upgrade path, which Bun's
net.Socket does not implement — upgrades die before establishing (and
the failure can crash Vite outright). Every collab surface (site editor
co-editing AND the Plugin IDE) was stuck at 'Connecting to the live
draft…' in dev.

- vite.config.ts injects __INSTATIC_CMS_DEV_ORIGIN__ (the CMS origin the
  proxy already targets); in dev the collab client connects the socket
  DIRECTLY to it, skipping the proxy hop. Cookies are host-scoped
  (port-agnostic on localhost) and the CMS Origin allowlist admits the
  Vite dev origins, so the direct hop stays fully authenticated.
  Production serves admin + API from one origin and keeps the
  same-origin path (import.meta.env.DEV gates the branch).
- ManifestPane no longer claims 'plugin.json is missing' while the doc
  is still syncing — it shows the connecting state until first sync.
- bundle-size gate: the per-icon-chunk check now verifies chunk CONTENT
  (svg body without a grouped-chunk import) — rolldown legitimately
  names shared feature chunks after member modules ('upload' helpers)
  that collide with icon names.

Verified on a fresh `bun run dev` (vite-on-Bun, :5173/:3001): account
setup → New site plugin → IDE fully loads (tree, buffer, diagnostics,
manifest), site editor reaches 'Draft synced', zero WebSocket errors.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The right-side ManifestPane duplicated what the raw plugin.json buffer
already does for the IDE's developer audience: the diagnostics strip names
every manifest mistake, and the activation review dialog is where
permissions get human-readable treatment. Removing it simplifies the IDE
to file tree + co-edited buffer + diagnostics.

- delete ManifestPane (+ CSS module) and the contentRightPanel wiring
- remove the now-dead replaceFileContent from IdeCollabSession
- drop the pluginIde 'manifest' notch label from the workspace layout
- docs: describe raw plugin.json as the only manifest surface

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Plugin source authoring previously borrowed site.structure.edit. Writing
plugin code is a different trust level than editing site structure, and a
plugin-developer persona needs no site rights — so it gets its own
capability, granted to Owner/Admin via the boot-time role sync (no
migration).

- new 'plugins' write-policy category: every change to a type:'plugin'
  file requires plugins.edit on both transports (collab relay + HTTP
  save); full site-writers without it no longer skip the guard
- socket write gate + full-writer fast path include plugins.edit
- scaffold endpoint and IDE/Plugins-page gates move to plugins.edit
- test harness now runs syncSystemRoles like server boot, so new
  capabilities reach the seeded Owner/Admin in tests
- capability picker metadata + docs

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Fills the reserved 'plugin' ToolScope seat following the content-scope
blueprint:

- shared TypeBox input schemas in @core/ai (browser + server validate the
  same source of truth)
- browser-bridged file tools (list/read/write/patch/rename/delete/open)
  mirroring the site code-asset shapes: paginated reads + SHA-256 hash,
  patch with expectedHash optimistic concurrency
- server-resolved lifecycle tools: plugin_list_plugins, plugin_validate
  (the diagnostics-strip build), plugin_activate (same-grant rebuilds
  only — grant changes remain a human consent moment in the IDE header)
- activation engine extracted from the HTTP route
  (runSitePluginActivation) so both transports share one orchestration;
  audit path accepts req=null for non-HTTP callers
- ToolContext gains uploadsDir (threaded router → chat handler → tools;
  MCP server passes its runtime value)
- plugin-scope system prompt teaching folder conventions, manifest rules,
  containment, and the validate-until-clean → activate workflow

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ools

The IDE's left rail gains an AI assistant button (ai.chat-gated, the
Content sidebar pattern): the shared AgentPanel docked on the 'plugin'
chat scope, backed by a per-mount agent store, a module-level bridge
handle, and a dispatcher that re-validates every tool input against the
shared @core/ai schemas before touching the CRDT session.

- agent file edits are Y transactions (minimal splices) — they merge
  character-level with concurrent human typing and show up live for
  co-editors; replaceFileContent returns to IdeCollabSession with the
  agent as its consumer
- MCP parity: EditorBridgeScope/McpWorkspaceScope grow 'plugin'; the IDE
  registers its tool bridge for the whole page mount, so external MCP
  connectors reach the open IDE even with the panel closed; plugin tools
  join the MCP registry (lifecycle tools headless)
- sitePlugins handler split into sitePlugins/{index,service}.ts — HTTP
  routing vs transport-independent service layer (the module crossed the
  700-line god-file ceiling); gate tests updated for the new layout
- cmsMigrations role-seed gate reworked: the migration seed is an initial
  snapshot (boot sync owns propagation), so the gate now checks seeded
  capabilities are real instead of demanding completeness
- docs: site-plugins.md AI panel section, mcp-connectors.md plugin scope

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- selection-time capability filtering (reads vs plugins.edit writes vs
  plugins.install activate; open_file as pure editor-state switch)
- lifecycle handlers against the capability harness: validate via
  explicit localId AND via snapshot; first activation refused as a
  consent moment; same-grant rebuild + skip; list states
- browser dispatcher against a Y.Text-backed fake session: relative
  paths + hashes, hash-guarded patches, replaceAll ambiguity, folder
  escape rejection, plugin.json protection, canEdit refusal, closed-IDE
  error
- plugin_list_plugins wraps its payload in aiToolOk for envelope
  consistency

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ws, schema teaching, code/diff blocks

Four findings from a real agent session in the IDE:

- BRIDGE STALENESS (the 'Create failed for server/index.ts' class): the
  tool bridge read file metas from React state, which lags the Y doc when
  tool calls arrive back-to-back without a render turn — a create could
  not see its own file. Metas now come straight from the live session
  (session.pluginFiles()), for tools and the snapshot both.
- TOOL ROWS: plugin_* names fell through to the raw 'Running plugin read
  file' default. toolCallDisplay now maps every plugin tool to a human
  title + detail (file path, edit count, rename arrow, localId).
- SCHEMA GUESSING: the model invented adminPages content shapes. The
  system prompt now spells out the content union — including the
  kind:'resource' CRUD page, which is the right answer for 'manage
  customers' requests — and says node_modules isn't on disk. The
  'Expected union value' diagnostic gains the same shape hint for humans.
- CODE VISIBILITY: write/patch tool rows (plugin AND site code-asset
  tools) get a 'View code / changes' toggle rendering a lazy read-only
  CodeMirror block — unified diff via @codemirror/merge for patches.
  New AgentCodeView joins the codemirror-lazy-only allowlist; theme +
  language stacks shared with CodeMirrorEditor.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Plugins page simplification (user feedback):
- site plugins and installed plugins are ONE list — an activated site
  plugin is just an installed row with an 'Open IDE' action next to
  Disable; draft-only site plugins (no runtime row yet) join the same
  list as DraftSitePluginCard reusing the PluginCard chrome
- 'New site plugin' moves into the page header next to Upload Plugin;
  SitePluginsSection is gone (fetching extracted to useSitePlugins)

Agent code blocks:
- always visible — no View/Hide toggle; plain-text <pre> fallback while
  the CodeMirror chunk loads so rows don't jump
- font drops to the fluid --text-xs token (CSS module policy compliant)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…acks

The theme/highlight/language exports added to CodeMirrorEditor.tsx broke
react-refresh (component files must export only components). They move to
a non-component module consumed by all three viewers; the lazy-only gate
lists it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… agent

Live testing showed the model guessing contracts (an .html app-page entry
that validated clean and only failed at runtime) and learning by error.
Two fixes:

- plugin_docs tool: curated author-facing topics (manifest, admin-pages,
  server, modules, editor, frontend, workflow, examples) hand-written
  from the real SDK types and host loaders — the system prompt now
  directs the agent to read the matching topic before writing code in an
  unfamiliar area. Exposed over MCP too (headless).
- derive-time app-entry validation: adminPages kind:'app' entries get the
  same source→bundle rewrite as frontend assets, non-JS entries and
  dangling paths now fail the BUILD with an instructive message instead
  of exploding in the admin shell at runtime.

Also tightens the prompt's SDK summary to the real ServerPluginModule /
defineModule / activate(api) contracts.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
String.prototype.replace with a string replacement interprets $$, $&, $` and $'
in the replacement text. An agent patching a template literal to $${props.price}
silently got ${props.price} written back, and the returned hash was of the
corrupted content. Both patch tools now go through one slice-and-join engine
(applyExactReplacements in @core/ai) that never interprets the replacement.
…ource

Bun evaluates `import x from './m.ts' with { type: 'macro' }` inside the host
process at bundle time with full Node/Bun access. Path containment cannot catch
it (the macro module resolves inside the workspace) and the sandbox scan runs
on the output after the macro already executed, so it was reachable through
the site.read-gated validate and preview-pack routes. Bun's own Transpiler.scan
runs macros too, so the guard is a textual onLoad check that fails closed:
only json/text/file/toml attribute clauses pass; a macro, a comment inside the
clause, an escaped key or a trailing field fails the build. The gate test
covers the real build path (the marker file must not appear) and the clause
spellings.
…ws before sweeping

Only the build was single-flight; two overlapping activations both read the
same row version, derived the same next counter and raced the upgrade path.
Activation, rollback and delete now serialize per local id through one keyed
promise-chain helper shared with the build.

The post-activation republish only re-baked pages, so entry-template data
rows (/posts/hello) kept HTML pointing at the revision the sweep then deleted.
The data-row bake takes a write target, the full publish and the in-place
republish share it, and the republish runs under the publish lock at N+1
before bumping, the same ordering the full publish uses, so hole shells are
never stamped stale.
basicSetup put history() and its Mod-z binding ahead of the Yjs undo keymap,
so remote peers' deltas entered local history and Cmd+Z reverted a peer's
typing (and broadcast the revert). The base stack is now spelled out from the
@codemirror packages with a localHistory switch; the collab buffer mounts
without history() and gives the Y.UndoManager keymap high precedence. The
codemirror meta-package is no longer imported and is dropped.
… lifecycle actions

A file created before the server's initial sync built an empty files map
client-side; the server seeds with client id 1, so that map won the merge and
the persisted shell lost every other site file. Every mutating session method
now throws IdeNotSyncedError until synced, the file tree keeps New file,
rename and delete disabled with the reason, and the agent bridge answers
"still connecting".

A relay reset (scaffold, delete, settings save, import) destroyed the bound
doc while the IDE kept typing into it. The session now rebinds, bumps a
generation the buffer remounts on, drops the undo managers that referenced
the dead text, and the editor reads the live Y.Text through a sync
subscription so the compiler cannot hand back the destroyed instance. The
auto-select effect ignores the empty list during the reset window instead of
jumping to the manifest.

Deactivate, Restart and Activate are gated on plugins.lifecycle with an
inline reason, matching the server. The bridge's patch tool rides the shared
verbatim replacement engine and re-reads the live text after the digest.
A syntax error in a module file came back as "Failed to bundle
plugins/x/__modules-facade.ts" with two bare bundler messages and no line
number. Bun reports a position for every log; it was dropped. Diagnostics
now read plugins/x/modules/foo.ts:22:16: <message> (1-based column), and
the modules pack's generated facade is labelled as the modules pack instead
of leaking its synthetic file name.
… the overflow

The diagnostics strip shows the active revision's runtime error while the
plugin is parked, the one problem validation cannot see. "View logs" and
"Open plugin settings" both navigated to the Plugins list; both now say
"Open on Plugins page". The duplicate "Preview in canvas" overflow item is
gone. The overflow trigger takes focus after opening so Escape closes the
menu (the menu itself is not focusable). The permission review says a
password prompt follows. Presence updates the active-file field instead of
replacing the whole awareness state, which showed peers a leave and a
rejoin on every file switch. The IDE route now maps to its workspace so the
persisted sidebar width applies on a direct load.
…epublish failures, one validate path

A same-grant rebuild (the everyday agent loop over MCP included) silently
re-enabled a plugin an operator had deactivated; the engine now refuses
with a pointer to the Activate action. A failed post-activation republish
was logged and forgotten while the response said success; it now comes
back as a warning on the activate and rollback responses and the AI tool
result. The validate route, the preview-pack route and plugin_validate ran
three copies of the same discover-and-build; they share
buildDraftSitePlugin in the service layer. The agent reference's style
asset example used src where the manifest requires href.
… snapshot schema, hook cleanup

The two code-asset bridges carried line-for-line copies of hashing and
pagination; both now use hashText and paginateText from @core/ai. The
content and plugin agent stores were the same factory with a different
config; createScopedAgentStore replaces both files. The IDE snapshot schema
lives once in @core/ai and both the browser handle and the server tools
derive their type from it. The view-model hook drops six useCallbacks no
dependency array reads, folds five lifecycle actions into one helper that
also toasts republish warnings, loads the summary through useAsyncResource,
and lets the session cache the file list so keystrokes never re-render the
tree. Dead exports removed: the seven Plugin*Input types, the bridge's parse
alias, the orphaned dialog error class. The scaffold dialog toasts request
failures and stops emitting aria-pressed on radio buttons. The base editor
installs no change listener when nothing owns persistence. The draft
preview badge subscribes to its store. Docs: the site-plugins design doc no
longer links two plan files that do not exist, site-shell's document model
mentions the granular files map, agent.md lists the plugin scope, and the
store references point at createScopedAgentStore.
…e like the site toolbar

The IDE never said which plugin was open; the left panel is now titled with
the plugin's name. The runtime state chip was its own pill; it now renders
through ToolbarStatus, the dot-and-label indicator extracted from the site
toolbar's publish group, so "Draft changed" in the IDE looks like "Draft
synced" in the site editor. Tones: active is success, permission review is
warning, build and runtime failures are danger, everything else neutral.
…he name, no panel footer

The toolbar carried three separate controls (Preview in canvas, the smart
primary action, an overflow menu). They are now one SplitButton, the same
control the site toolbar's Publish uses: the state-appropriate action on
the left, everything else grouped in the menu with inline reasons when
unavailable. For an active plugin the left half stays "Build & activate"
and says the draft already matches the active revision.

SplitButton menu rows gain tooltip, danger and separatorBefore, mapped onto
the ContextMenu features they already had; and the chevron now takes focus
after opening and closes on Escape, which also fixes the site Publish menu
after a mouse click.

The files panel shows the active version muted beside the plugin name (the
semver part; the content hash stays in the title attribute) and drops the
"Files live in plugins/…" footer.
Retention kept the active build plus the one below it, so "Rollback to
previous revision" had exactly one possible target and rolling back swept
the build you had just left. The sweep now keeps the five highest builds
plus whatever is active (a deep rollback keeps its target), and every
retained build is a rollback target: the summary lists them newest first
with the build time, the rollback route takes a version and validates it
against the retained directories before touching a path, a target with a
different grant set still steps up, and rolling back no longer discards
the newer build, so rolling forward is the same action.

The IDE menu's single rollback row becomes a "Roll back to…" submenu
(v1.0.3 · 48m · active, v1.0.2 · 16h, …); SplitButton rows gain nested
children for that, rendered through the shared ContextMenuSubmenu.
…n click

A changed grant set was its own runtime state, "Permission review needed",
with its own primary action. Grants live in plugin.json, so every grant
change is a draft change, and the review dialog already opens from the
Build & activate click when the declared and granted sets differ. The state
is gone: the chip reads Draft changed, the click reviews then builds, and
the summary keeps newPermissions / removedPermissions for the dialog. The
state machine test, the lifecycle rollback test, and the docs follow.
- TreeGroup joins the shared tree primitives: a row plus its children, painted
  as one surface while the parent is selected and expanded. The Layers panel
  uses it instead of its own group styling.
- The IDE file tree renders folders recursively, folders first, with the
  chevron at the end of the row so files and folders share a left edge, and
  keeps the same right inset as the Layers tree so row paint stays rounded.
- New file from a file's context menu lands beside it.
…base

The branch was based on a pre-branches, pre-language-service main. Landing it
on feat/site-branches needed:

- site-plugin handlers pass MAIN_SCOPE to the now branch-scoped site
  repositories; plugin sources live on main until branch instances land.
- the IDE binds MAIN_SITE_DOC_ID, the branch-aware name for the site doc.
- the MCP editor bridge gains the plugin scope, with its own precondition.
- the shared CodeMirror module carries main's TypeScript hover and lint
  theme rules and its jsx/javascript language cases, and the buffer's update
  listener keeps main's diagnostics alongside the IDE's optional onChange.
- the collab relay keeps main's write-capability gate, widened with
  plugins.edit; the superseded dev-socket helper and its define are gone.
- migration 022 becomes 028, after the branches migrations.
- site-plugin and collab tests pass MAIN_SCOPE / the branch id to the
  repositories and the patch translator, which now take them.
- The raw-SQL branch rule required a table name followed by SQL, so an
  English log line reading "from site plugin" tripped it; the pattern now
  needs a clause keyword or terminator and is checked both ways.
- SitePage's budget covers the plugins.edit gate and the IDE route entry;
  the editor body still lands in its own chunk.
SITEPLUGIN-001: scaffold a site plugin from the plugins workspace, confirm
it opens in the IDE with real source, build and activate it through the
permission review and step-up, see it in the merged plugin list, then edit
the server entry and watch the state fall back to Draft changed.

The IDE had no browser coverage at all, so nothing caught a regression in
the draft to build to activate coupling.
Site plugins put uploaded packages and site-authored ones in one list, so
its region is labelled "Plugins" rather than "Installed plugins". The
capability spec's helper still looked for the old label and took the whole
plugin-boundaries block down with it.
Main took 026 for plugin media sources while these branches were open, so
site branches moved to 027 and the merge review to 028. The site-plugin
source column follows at 029. Nothing here has shipped, so renumbering is
safe; a dev database from an earlier build of this branch needs a reset.
@socket-security

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addedy-codemirror.next@​0.3.61001009586100
Added@​codemirror/​merge@​6.12.21001009287100

View full report

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant