`is-blue` resolved to var(--cyan), making it pixel-identical to `is-cyan`
while Walkthrough Studio offers both in its tone dropdown. The theme defines
no --blue token, so point it at --accent, the one remaining distinct hue
among the tones on offer.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
addStep() appended through a functional setDraft but then selected with
setStepIndex(draft.steps.length), reading the closed-over render snapshot.
The two can disagree under fast interaction, selecting the wrong step or an
index past the end of the list.
Build the list and derive the index from the same snapshot so they cannot
drift apart. Applied to the builtin-extensions source and the bundled copy
together; sync-builtin-frontends --check keeps them identical.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The signal handler searched the whole step list for a matching signal and
completed whichever step it found. These signals are ordinary product events
-- running a monitor check, creating a service -- so a later step's signal
fires readily while an earlier step is still current, marking it done out of
order and skipping every step in between.
Scope the match to currentStep, which is what the route effect directly above
already does. Normalization flattens an explicit `completion` block onto the
step (normalized.signal), so this covers both spellings.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Duplicate page-scoped keys (Title, Duration, Publish, Live preview,
Move up/down) and Walkthrough Studio strings now reuse common.labels/
common.actions instead of re-declaring identical English text.
en.json regenerated: 6430 -> 6420 keys.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Visual authoring tool for walkthroughs: create and reorder steps, pick
highlighted UI targets, choose completion modes (route, signal, manual,
check), validate against the shared schema, preview in a live dock,
publish to the server library, and import/export JSON or copy an
extension contribution block. The Studio dogfoods the new system by
declaring its own getting-started walkthrough in its manifest.
WalkthroughContext now merges built-in walkthroughs with extension
contributions and server-persisted custom guides through a shared
registry, so enabling or disabling an extension adds or removes its
guides at runtime. Adds the walkthrough contribution type to the plugin
SDK (with TypeScript types and vendor shim), Studio API methods, and
i18n strings.
5bfb0f67 dropped the tab-local header but did not regenerate en.json,
so the lint gate failed on catalog drift. Regenerate en.json and delete
the three now-unused recycleBinTab keys from every locale bundle.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The tab-local title, retention blurb and standalone Refresh button
duplicated chrome the grid already provides (Refresh data lives in the
grid tools menu; per-row 'purges in Xd' carries the retention signal).
The page now leads with the view picker and table like other grids.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The self-update feature commit added the 10 new app.aboutTab keys to en
and es only; every other locale fell back to English in an otherwise
fully translated About tab. Add ar, bn, de, fr, id, it, ko, pl, pt, ru,
th, tr, vi, zh-Hans and zh-Hant, matching each file's existing register
(Sie/vous/formal).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The About tab could only detect a new release and link to it; applying the
update meant SSH and scripts/update.sh. Wire that script to the UI:
- panel_update_service launches update.sh via systemd-run in its own
transient unit — a plain subprocess child would be killed when the
updater restarts the panel's own systemd unit. Status is stateless
(transient-unit state + the updater's own update-*.log) so it survives
the mid-update backend restart.
- Capability gates keep the button honest: root systemd installs only.
Docker installs get the docker compose pull instructions; dev servers
and hosts without systemd-run get a reason instead of a button.
- GET/POST /api/v1/system/update (admin_required, confirm-gated,
audited). API surface doc regenerated.
- About tab gains Update Now with inline confirm, live log tail while
the update runs, a restarting phase that tolerates the panel being
briefly unreachable, and success/rollback/timeout outcomes. New
aboutTab locale keys in en + es.
The heavy lifting (blue/green switch, migrations, health check,
automatic rollback) stays in update.sh, which is unchanged.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
30b780f4 corrected the serverkit-status description in two of its three
copies. The extension's manifest also exists at
frontend/src/plugins/serverkit-status/plugin.json, which is generated from
the builtin-extensions/ source by scripts/sync-builtin-frontends.mjs --
so Extensions CI went red on the builtin-frontend drift check, and
bump-version failed downstream of it rather than on its own.
Regenerated with the sync script rather than hand-edited; the diff is the
one description line.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The deploy drawer posted only url/branch/app_type/build_method/port, so a
repo template's `build_command`, `start_command` and `dockerfile_path`
were decorative — declared in the YAML, read by nobody. The endpoint has
always accepted custom_build_cmd / custom_start_cmd / dockerfile_path;
only the caller was dropping them.
The visible cost was nixpacks builds: with no start command it falls back
to `npm start`, so any repo whose package.json has no `start` script
produced an image that built fine and could not boot.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
localkit, email, cloudflare-ops and git are now published, signed and in
the registry index, so the panel no longer needs in-tree copies of them.
This removes ~29k lines that existed in two places at once and flips the
bundled registry so they install from the catalog like any other
extension.
Unblocked by the operator publishing all nine repos (2026-08-30); the two
registry-mirror tests that plan 83 expected to stay red now pass.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
# Conflicts:
# builtin-extensions/serverkit-cloudflare-ops/frontend/components/StoragePanel.jsx
# frontend/src/plugins/serverkit-cloudflare-ops/components/StoragePanel.jsx
PR #121 review follow-ups. The setup summary de-duplicated the install
queue with recommendations winning, so a security-posture extension
that also appeared as an unchecked recommendation was silently dropped
— the posture half now wins regardless of checkbox state. The
Cloudflare storage panel templated English around a translated title
(`No ${title.toLowerCase()} yet.`); each section now passes a fully
translated emptyHint instead.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The plan 52 Phase 6 git split updated the source manifest under
builtin-extensions/ (shell permission, entry_point, url_prefix,
min_panel_version) without running sync-builtin-frontends.mjs, so the
Extensions CI drift check went red on the next PR.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The API client throws on any non-2xx, so a 400 from validate-install
never reached the `!validation.valid` branch — and the catch only read
`err.data.error` (singular), while this endpoint returns an `errors`
array. The operator saw "Request failed (400)" instead of the actual
reason (e.g. the app name being reserved by the Recycle Bin).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
builtin-extensions/serverkit-git + the tracked frontend live copy removed;
the bundled registry entry flips to bundled:false at v1.1.0 (the version
that carries the Gitea backend) with the local zip's sha256; the gated
auto-install (_gitea_was_used) moves to REGISTRY_GATED_SLUGS, leaving
GATED_BUILTIN_SLUGS empty with the machinery intact.
i18n: en.json regenerated (the app.git/app.email/app.cloudflare* namespaces
ride with their extensions, which carry en+es bundles; other languages are
recoverable from git history), all locales pruned, intl-door ratchet
tightens 123 -> 115.
Expected red on this branch: the two registry-mirror tests, until the four
repos are published + signed + re-vendored (see docs/plans/83).
OPERATOR: publish jhd3197/serverkit-git, sign, sync-registry-index.sh.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
builtin-extensions/serverkit-cloudflare-ops + the tracked frontend live
copy removed; FLAGSHIP_SLUGS is now empty (machinery kept);
plugins-manifest.json empties; the bundled registry entry flips to
bundled:false with the local zip's sha256. cloudflare_ops_bridge resolves
the copy-installed package FIRST so a registry install works without the
builtin registration path.
Tests: new conftest.mount_sibling_extension mirrors a real install from
the sibling checkout (the _wp_support.ensure_plugin analogue) and a
cf_extension fixture forces a private app; the zone-settings and round2
suites mount it autouse (74/74 green), the extraction suite pins the
lean-panel shape, and the flagship-era onboarding tests prove the
machinery with a fabricated member instead.
OPERATOR: publish jhd3197/serverkit-cloudflare-ops, sign,
sync-registry-index.sh.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
builtin-extensions/serverkit-email + both tracked live copies removed; the
bundled registry entry flips to bundled:false with the local zip's sha256;
the gated auto-install (_email_was_configured) moves from
GATED_BUILTIN_SLUGS to REGISTRY_GATED_SLUGS so mail users still get it back
once the entry is published.
Panel-core tests re-home onto the sibling checkout via the new
conftest.sibling_extension_dir helper (skip cleanly when absent): the
extraction suite installs from the serverkit-email repo, the dovecot-probe
suite loads its module from there. All 39 affected tests green with the
sibling present.
OPERATOR: publish jhd3197/serverkit-email, sign, sync-registry-index.sh.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Same relocation pattern as email/cloudflare-ops: pages/Git.jsx (1920 lines),
its SCSS partial (with the %sk-state placeholder inlined - @extend cannot
cross compile units) and a new Gitea-only API client move into
builtin-extensions/serverkit-git/frontend/. The page's two halves now show
their seam: Gitea self-host calls go through the extension's own client;
webhook + deployment calls stay on the core api object (they ARE the deploy
pipeline and exist on every panel).
Core files.js sheds the Gitea-half methods but keeps the three fail-soft
getGitea* aliases the app-creation repo pickers probe (they 404 and degrade
to 'not installed' when the extension is absent). serverkit-sdk adds the
ds/status helpers (statusKind & co). pages/ ratchets tighten: raw api calls
431->405, toast extractions 264->218.
Frontend build green, lint fully green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The 1791-line security omnibus splits along tool lines and the install-gated
surfaces leave core for five standalone extensions - serverkit-clamav
(ClamAV+YARA scanning, quarantine, scan jobs), serverkit-fail2ban
(management UI; probes stay core on Fail2banJailService), serverkit-lynis,
serverkit-auto-updates, and serverkit-image-scan (headless CVE/SBOM
scanning; models stay core per the plan 52 D1 data seam). Each mounts its
routes back at the same /api/v1/security prefix when installed, so route
shapes never change; absent extensions mean plain 404s, no dead UI.
Core keeps the zero-host-package baseline: status/config, legacy integrity,
suspicious activity/events, SSH keys, IP lists, the audit (fail2ban check
now probes via the jail service; updates check was already self-contained),
scoped FIM, and the scan/alert logs + notification helpers extensions write
through. Job handlers for security.malware_scan/lynis_scan/image_scan now
come exclusively from the extensions' manifest jobs key.
Security page becomes core-tabs-only; extension tabs contribute into the
security tab group (3a groupId). Overview posture drops the hardcoded
ClamAV rows and offers the Marketplace when no security extension is
installed. Upgrade parity: run_registry_auto_install() installs each
extension from the registry once, gated on the host tool actually being
present, fail-soft until the registry entry is published.
Wizard round 2 (Ph5): a security-posture chooser (minimal/recommended/
hardened) on the Summary step, catalog-resolved so unpublished slugs never
render dead installs; persisted as onboarding_security_posture.
Moved test suites re-home to the extension repos; panel keeps
test_security_suite_extraction.py (18 proving tests: absent-shape 404s,
lean summary, jail-service probes, migration gates, posture wiring).
docs/API_SURFACE.md regenerated (34 operations moved out).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Same WP-Phase-4 pattern as the email relocation: the CloudflareZoneSettings
page, the WAF/Workers/Tunnels/Storage panels, the /cloudflare API client and
the SCSS partial move into builtin-extensions/serverkit-cloudflare-ops/
frontend/; the extension index.jsx imports locally and side-effect-imports
the SCSS. Core drops the cloudflareMethods spread from the api object and
the main.scss partial - DNS records and the Cloudflare connection stay core.
serverkit-sdk (still the unreleased 1.4.0 bump) adds Tabs kit, PageLoader
and copyToClipboard; vendor shim regenerated. The pages/ raw-api ratchet
tightens 460 -> 431 (the two relocated pages left pages/).
Frontend build green, lint fully green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
WP-Phase-4 pattern, in-tree Tier C: pages/Email.jsx, its SCSS partial and a
new self-contained /email API client move into
builtin-extensions/serverkit-email/frontend/; the extension index.jsx now
imports locally (no more core re-export shim) and side-effect-imports the
SCSS so it compiles through the extension's module graph. main.scss drops
the _email partial.
Core keeps only the email-adjacent surfaces core Settings renders: the
DNS-provider methods (core dns_providers blueprint) and the relay tile
methods. serverkit-sdk grows the generic list-page chrome the relocation
needed (DataTableFooter/ListToolbar/SearchField/SortChipBar, ds/grid saved-
view chrome, PageLayout, useTableSort, useColumnVisibility) - SDK 1.4.0,
backend mirror bumped in lock-step; vendor shim regenerated.
Frontend build green, lint 0 errors, i18n check no orphans.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The backend stores an unset service_name as null; spreading the API
response over the defaults pushed that null into a controlled <Input>,
flipping it uncontrolled. normalizeScalePolicy now coerces strings and
integer fields back to form-safe values. (Copilot review on #120)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The drawer showed '<name>.<base> — Published automatically once the deploy
finishes' whenever a managed base domain existed, but publishing was gated
on a per-template auto_domain flag the operator can't see. For most of the
catalog the install landed on host:port instead — a link a default firewall
doesn't even expose — and the domain had to be redone by hand in the
service's Domain settings.
The promise on screen is now the contract: when the drawer displays a
domain it sends auto_domain: true, which rides the install plan to the
finalizer and overrides the template's silence (the forced flag also feeds
the SERVICE_FQDN magic context, so tokens and the published host agree).
Absent flag = the template YAML still decides, as before.
The drawer also stops promising what can't be delivered: the domain field
only renders for compose templates targeting the local server — the panel's
nginx can't proxy a remote container, and the repo pipeline has no
auto-publish step.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The VS Code-style bottom statusbar (z 1130) was added above the old modal
stack (40/50/60), so any full-height drawer or dialog reaching the bottom
edge had its footer buttons painted over — e.g. Deploy/Cancel in the
template deploy drawer. Modals own the screen: raise backdrop/modal/tooltip
to 1140/1150/1160, keeping their internal order and leaving toasts and the
app loader on top.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add ContainerScaleService policies (min/max replicas, CPU thresholds,
cooldown) persisted via migration 093, with manual scale, single
evaluation, and an admin sweep endpoint. Container Ops panel exposes
policy editing; the configured minimum acts as a floor on the next
evaluation. Frontend node:test suites pin policy normalization and the
API contract.
Nothing verified that the ~1200 URL literals in
frontend/src/services/api/*.js resolve to rules in the backend url map,
so a backend route rename surfaced as a dead button found by a user.
New test statically extracts every this.request() literal (mirror of
test_socket_contract.py's cross-language pattern) and asserts each
matches an /api/v1 rule from the session app's route_rules; paths under
a builtin extension's plugin.json url_prefix are excused only while
that manifest still declares the prefix, and a second test keeps the
allowlist from rotting.
First run caught 78 unresolved paths. Extension-owned families
(email/ftp/cloud/status/tunnels) were excused; the rest were dead
frontend code calling routes that no longer exist, removed here:
- 11 workflow wrappers in apps.js (the workflow engine left the
backend in v1.7.33; zero callers remained)
- notification catalog/org-defaults wrappers (routes never existed;
zero callers)
- 6 WordPress standalone lifecycle wrappers (no routes, no callers;
the live status probe stays, allowlisted as extension-provided)
Frontend suite still green after the deletions (141/141, lint clean).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ConfigDiffModal renamed noChange to noChangeValue and DeploymentTimeline
renamed envVar to envVarCount, but the old keys were left behind in the
16 translated locales, failing the i18n lint orphan check.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Reserve the word Recipes for installers. The status-bar segment, dock
tab, hub aria-label and command-palette category now read Walkthroughs
(new app.walkthroughs.title/all keys; stale es allRecipes dropped).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The curated serverkit-recipes index is consumed like the themes registry
(cached remote index, bundled fallback, SERVERKIT_RECIPES_REGISTRY_URL
override). GET /recipes/registry[/<slug>] browses it; POST /recipes/runs
now accepts a registry_slug plus server_id — targeting a server finds or
creates the per-workspace Recipes project — and validated non-secret
params that pre-supply handoff inputs so only true secrets pause the run.
Frontend: Recipes catalog page (cards, category chips, install drawer
with running-server picker, preflight table, up-front inputs and an
asked-during-the-run secrets preview) under new Services and Marketplace
tabs; runs land in the deployment console.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
en.json regenerated from inline defaults (new statusbar/console/palette/
recipes strings; Prompture and old walkthrough-hub strings gone). es picks
up translations for all 26 new keys; every locale drops the now-orphaned
keys so the parity gate stays green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Gradient fill (accent-bright to accent), accent glow, inner top highlight,
and a hover that lifts the gradient toward white — all from theme tokens so
runtime skins keep recoloring it.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The palette's empty state now leads with a Create tile grid (shared list in
data/createItems.js) plus a '+ n more' shelf that folds in the less-common
targets and any extension-contributed 'New ...' commands, then a Recipes
group (picking one starts it and opens the recipes console). Create and
recipe entries also score into regular search results.
The sidebar '+' stops carrying its own little dropdown and opens this
palette instead — one door for every new-thing flow, like the prototype.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The right-side chat drawer now carries the shared console tab strip, so the
assistant reads as the right-docked tab of the same console the bottom
panels belong to (its close moved into the strip). The 'powered by
Prompture' lines are gone from the header and empty state — the empty state
now tells the user what to ask instead.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The bottom-edge surfaces (Operations, Alerts, Recipes, Assistant) now share
one console: every panel carries the same four-tab strip (ShellDockTabs) and
a ShellDockContext keeps exactly one open, so switching surfaces is a tab
click instead of hunting separate popovers. Recipes is rebuilt into the
prototype's split layout — step rail left, one step's story right, with a
deep-link action button, 'Done - next step', and Back.
The workspace/server switchers get real themed menus (dot - name - meta -
check) replacing the native <select> popup that ignored the theme, and the
sidebar keeps its full height again: it was shortened by the statusbar
height even though the bar only spans the content column, leaving a dead
30px hole under the user card.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
en.json drops from 8107 to 6624 keys: repeated UI copy (Cancel x95,
Status x67, Refresh x54, ...) now lives under common.actions /
common.labels / common.loading, and extractor-era suffix duplicates
(refresh2, status3) within a component collapse onto one key.
Trailing ... defaults are normalized to the ellipsis character.
Translations are preserved: all 16 locales were re-keyed, carrying
existing per-component translations onto their new common.* keys
(majority vote where sources disagreed), and stale keys were pruned.
Verified: npm run lint (0 errors), node --test (140 pass), vite build.
edit