ARCHITECTURE gains the security-extension seam note; CORE_SLIM Tier B rows 3/7 marked done; CHANGELOG Unreleased entry for the lean-by-default Security page. (Plan docs 47/55 updated too — they live in the git-ignored docs/plans/.) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
9.4 KiB
Core-slim: which core pages become extension-delivered
Decision doc — plan 25 Phase 6 (#18 / Extensions Platform #39). No code moves in this document. It classifies the surfaces, fixes the order, and gives a per-page migration checklist. Each move is its own small follow-up slice, tracked against this doc.
Why now
Plan 25 landed the last missing rail: an extension can ship a prebuilt runtime
frontend bundle that the panel loads without a rebuild, integrity-verified.
Combined with the already-proven install / update / survival / config / socket /
jobs rails, a vertical no longer has to live in core to have a real UI. So the
question is no longer "can a page be an extension" but "which ones should move,
in what order, and how far".
Two speeds already exist (decision D2): a thin-wrapper builtin owns the
route/nav/palette via its manifest while its page component + backend stay in
core (e.g. serverkit-gpu's frontend/index.jsx is just
import GpuMonitor from '../../pages/GpuMonitor'). Core-slim is about finishing
the extraction: moving the UI into the extension dir so the extension is
self-contained — and, where it makes sense, shipping it as a runtime bundle
so it can leave the repo entirely.
Tiers
Tier A — already self-contained (done)
Frontends already live in the extension dir; nothing to move.
| Extension | Notes |
|---|---|
serverkit-crowdsec |
Reference implementation of the sanctioned page skeleton. |
serverkit-dns-server |
Self-contained. |
serverkit-mail |
Self-contained (Stalwart mail engine). |
serverkit-gui |
Self-contained; first runtime-bundle migration (plan 25 Phase 4). |
These are the template to copy. serverkit-gui specifically proves the
runtime-bundle path (externalized build, CSS inlined) — Tier B/C follow it.
Tier B — thin wrappers whose UI still lives in core/pages
Each has an extracted backend + a manifest that owns its route, but its page
component (and helpers/styles) still lives under frontend/src/pages/. Ordered
smallest / least-coupled first so the early moves establish the pattern
cheaply. Sizes are the core page's current line count (the UI to relocate).
| # | Extension | Core page | ~lines | Coupling notes |
|---|---|---|---|---|
| 1 | ✅ serverkit-gpu |
GpuMonitor.jsx |
199 | DONE (plan 32 #7, 2026-07-06). Backend moved to builtin-extensions/serverkit-gpu/backend (entry_point gpu:gpu_bp, url_prefix /api/v1/gpu); page moved to frontend/components/GpuMonitor.jsx (SDK imports); core page + core app/api/gpu.py + app/services/gpu_service.py deleted; sdk_version ^1.0.0 declared. Shipped baked (dual-path per guardrail below); runtime-ESM build scaffold added, live runtime-loader render deferred with plan 32 #9. |
| 2 | serverkit-cloud-provision |
CloudProvision.jsx |
239 | One page; leans on connection APIs already in core. |
| 3 | ✅ serverkit-cloudflare-ops |
CloudflareZoneSettings.jsx |
469 | DONE (plan 52 Ph2 prep, 2026-08-29). Page + WAF/Workers/Tunnels/Storage panels + own API client + SCSS moved into builtin-extensions/serverkit-cloudflare-ops/frontend/; DNS records + CF connection stay core. Standalone repo prepared (operator publishes). |
| 4 | serverkit-remote-access |
RemoteAccess.jsx |
516 | WireGuard pairing UI; agent-fleet coupling via SDK. |
| 5 | serverkit-ftp |
FTPServer.jsx |
687 | Accounts CRUD; self-contained once helpers move with it. |
| 6 | serverkit-status |
StatusPages.jsx |
747 | Has a public status route (/status/:slug) that must stay reachable — verify the public path still resolves post-move. |
| 7 | ✅ serverkit-email |
Email.jsx |
1086 | DONE (plan 52 Ph2 prep, 2026-08-29). Page + own API client + SCSS moved into builtin-extensions/serverkit-email/frontend/; DNS-provider + relay methods stay core (Settings surfaces). Standalone repo prepared (operator publishes). |
| 8 | serverkit-workflows |
WorkflowBuilder.jsx |
1092 | Largest Tier B; pulls in @xyflow/react — externalize it or bundle it into the extension (it is NOT a host-shared lib). |
| 9 | serverkit-git |
Git page group | — | Already plugin-contributed self-rendering (sk-tabgroup shell). Mostly a file-move; last in Tier B because its tab-group wiring is the closest analog to Tier C. |
Shared-lib caution (Decision 2): only react, react-dom,
react/jsx-runtime, react-router-dom, serverkit-sdk are externalized by the
host import map. Anything else a page uses today from core (charts via
recharts, flow via @xyflow/react, icons via lucide-react) must either be
bundled into the extension or first promoted into the SDK. Workflows
(@xyflow/react) and any chart-heavy page are the ones this bites.
Tier C — WordPress ✅ DONE (plan 52 Phases 4-5, 2026-08-05)
serverkit-wordpress is fully extracted: Phase 4 moved the frontend (5 pages,
40 components, 3 SCSS files) into the extension and onto the serverkit-sdk
surface; Phase 5 took it out of the tree into the standalone
serverkit-wordpress repo as a runtime-ESM bundle (SDK 1.2.0). It distributes
through the registry and is offered during onboarding from the bundled index
entry; its schema stays core via the documented core data seam (D1).
Per-page migration checklist
For each Tier B/C page, one slice:
- Move the UI from
frontend/src/pages/<Page>.jsx(+ its private components/styles) intobuiltin-extensions/<slug>/frontend/. Replace the thinimport … from '../../pages/<Page>'re-export with the real component. - Rewrite imports to the SDK: swap deep
@/components/ds, contexts, andapiforserverkit-sdkimports (the lint flags deep@/for external extensions). Keep the same/api/v1/<feature>prefix (decision D9). - Handle non-shared deps: bundle them into the extension, or promote a genuinely common one into the SDK (a deliberate SDK minor bump).
- Delete the core copy: remove the page import +
<Route>+PAGE_TITLESentry fromApp.jsx, thesidebarItems.js/group-tabs entry, and theCommandPalette.jsxentry. The manifest already owns these. - Pre-bundle:
node scripts/sync-builtin-frontends.mjs(baked path) — the CI drift gate (--check) keeps source/artifact in sync. - (Optional) runtime bundle: to ship it as an external (non-baked)
extension, add the
--template frontend-esmbuild (vite.config.mjsexternalizing the shared libs →dist/index.mjs), setfrontend_entry: dist/index.mjs+sdk_version, and validate withnew-extension.mjs --validate. See EXTENSIONS_CI.md. - Verify:
npm run lint && npm run build; the screenshots skill renders the page; forserverkit-statusconfirm the public/status/:slugroute still resolves; backend suite green. - Keep it brand-neutral and free/OSS (project policy).
Migration log
slice 1 — serverkit-gpu (2026-07-06, plan 32 #7)
Checklist status:
- Move the UI ✅ —
builtin-extensions/serverkit-gpu/frontend/components/GpuMonitor.jsx(index.jsx renders it instead of re-exporting the core page). - Rewrite imports to the SDK ✅ —
PageTopbar, KpiBand, api, useToastfromserverkit-sdk;EmptyState+Buttonvia the@/alias (not yet on the SDK surface — tolerated for the baked path; a runtime-ESM build must bundle them or the SDK must promote them, a deliberate minor bump). - Handle non-shared deps ✅ — only
lucide-react(bundled on the baked path). GPU styles (_gpu.scss) stay in core design-system SCSS for the baked slice; a runtime-ESM build would inline them into the bundle (as gui does). - Delete the core copy ✅ — core page,
app/api/gpu.py,app/services/gpu_service.py, and the coregpu_bpregistration removed. The manifest already owned nav/route/palette/title. - Pre-bundle ✅ —
node scripts/sync-builtin-frontends.mjs; CI--checkdrift gate green. - (Optional) runtime bundle 🚧 —
frontend/vite.config.mjs+package.jsonscaffold added (externalizes the shared libs →dist/index.mjs),sdk_versiondeclared. Kept baked for this release (dual-path guardrail); flippingfrontend_entrytodist/index.mjs+ cutting a registry release is deferred to plan 32 #9 (needs push/publish + a test box). - Verify ✅ —
npm run lint && npm run buildclean; backend suite green (test_gpu_extension.pybridge tests + pipeline runtime-builtin SDK-gate fixture). No public route to re-check (gpu has none). - Brand-neutral + free/OSS ✅.
Non-goals / guardrails
- No big-bang. One page per slice; the app stays shippable between slices.
- Backend stays put unless a slice explicitly extracts it — most Tier B backends are already extracted; the UI is the remaining half.
- SDK is the throttle. Every "promote a dep into the SDK" is a versioned
contract change (
SDK_VERSIONbump) — do it deliberately, not per-page. - Dual-path during any runtime move: keep the baked frontend for one release before deleting it, so panels mid-upgrade never lose the page.
Sequence summary
Tier A (done) → Tier B in the numbered order above (gpu → cloud-provision →
cloudflare-ops → remote-access → ftp → status → email → workflows → git) →
Tier C (wordpress). Reassess after serverkit-git whether WordPress moves as one
slice or several sub-tab slices.