One list of everything the panel has soft-deleted, with Restore on every row
and a quiet purge for admins. Type-agnostic: `kind` comes from the server's
registry, so a newly restorable model appears here with no change to this
component.
Says out loud what restore does NOT do -- it returns the record, not the
configuration torn down when it was deleted -- because a Recycle Bin that
implies otherwise is worse than none.
The retention countdown per row ("purges in 3d") turns amber in the last
stretch: that is the only warning before a record is gone for good. Purge is
admin-only, sits behind a confirm, and stays a dim icon next to a full-width
Restore -- the safety net should make the safe action the loud one.
Registered as a settings tab with search-index entries, so the command palette
finds "restore", "undelete", "trash".
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Ported from the Domains design mock. A CSS grid, not a <table>: header and
rows share one --sk-grid-tpl track list, which is what makes show/hide and
move-left/right a pure data change with no <colgroup> to keep in sync.
fields.js field model + filter-rule engine (text/enum/num/bool/date),
sort, group, CSV/JSON export
DataGrid grid, sticky head, groups, selection, density, sub-detail
ColumnMenu per-column popover: sort / group / filter / move / hide.
Flat, state-model-agnostic props so <DataTable> can drive
it from sorts[]+hiddenKeys and <DataGrid> from one cfg.
useGridConfig the whole chrome as ONE serialisable object, so a saved
view is a snapshot of it and "dirty" is a deep-equal away
useGridRows filter -> sort -> page -> group, memoised
GridViewPicker saved views rendered as the page heading
GridFilterDrawer Filters + Fields panes
GridChips / GridBulkBar / GridFooter / GridToolsMenu / GridFilterButton
Does not replace ds/DataTable -- column descriptors are a superset, so a
list can move up by adding `type` and `width`.
Adds the --surface-0 token (the recessed step below --surface) that group
headers, drawer tab strips and drawer footers sit on.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The group's information architecture had drifted. The Events tab pointed at
/telemetry, which was nested inside the monitoring TabGroupLayout but ALSO
rendered its own PageTopbar — two stacked headers. Jobs sat as a top-level
sidebar entry while being the same class of thing as Events, so the two read as
rival pages.
- MONITOR_TABS is now Overview / Monitors / Incidents / Rules / Capacity /
Events / Jobs / Doctor (+ Status Pages from the extension). Alerts is gone as
a tab; /monitoring/alerts and /monitoring/fleet-alerts redirect to Incidents,
and FleetAlertsPanel moved there with its own ack/resolve lifecycle intact.
- Jobs moves to /monitoring/jobs and loses its sidebar entry; /jobs redirects.
Activity/Scheduled becomes an in-page SegControl since the group's bar already
owns the tab row. JOBS_TABS is deleted.
- Events (Telemetry) rebuilt on the standard pattern: no PageTopbar of its own,
KpiBand + MetricCard severity filters instead of bespoke telemetry-stat divs,
topbar SearchField + FilterDrawer instead of an inline collapsible panel,
DataTable + DS Drawer. Every capability is kept — correlation drill-down, all
filters, test-event emit, cleanup, pagination.
- The Overview leads with a monitors summary (availability, live check feed,
monitor list); host resources keep everything they had, one section down.
- Status pages can now publish a monitor that already exists, and detach it
again without destroying it — deleting a component now deletes a real monitor.
FilterDrawer grows `children` + `onClear` so a host can add non-list filters
(free text, date bounds) and still have "Clear all" reach them; without onClear
it would silently reset only the chip groups.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two sidebar surfaces that shouldn't have been top-level pages. Both
touch App.jsx + sidebarItems.js, so they land together.
Test Sandbox: the distro-matrix console shipped visible and routable
on every production install. Add a shared useDevMode() hook
(module-scope cache + listeners, same shape as useModules) resolving
`import.meta.env.DEV || (isAdmin && dev_mode)`. The sidebar item gates
on requiresCondition: 'devMode' and the route on the same hook, so a
visible nav entry can never redirect to the dashboard. The guard waits
on `resolved` first, or an admin deep link would bounce before the
dev_mode fetch lands. Settings' toggle calls refreshDevMode() so the
sidebar reacts without a reload.
Webhooks: the inbound receive/verify/forward console is server
configuration, not a daily ops surface, so it becomes a Settings ->
Admin tab next to the outbound subscriptions it complements. /webhooks
and /secrets/webhooks redirect there; the sidebar item is gone, which
empties ADVANCED_ITEM_IDS down to the queue console. The command
palette entry drops its navId (a Settings tab has no nav item for the
workspace permission gate to consult) and settingsIndex.js gains the
three entries check-settings-index.mjs requires for a new tab. The
native confirm() on delete becomes the shared useConfirm() dialog.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Fleet Monitor and /monitoring were the same questions at two scopes, so they
were two pages restating each other: both grew an alerts surface and both
grew thresholds. They are now one page with a scope, not two pages with a
gap between them.
The top bar carries a server picker, URL-backed as ?server= so the choice
survives a tab change, a refresh and a pasted link. Overview, Alerts and
Capacity all follow it. The fourth KPI tile is whichever reading the current
scope actually has — load average on the panel host, container count on an
agent — rather than a blank where the other one would be.
The overview is ported from the design mock: KPI band with trends and
sparklines, two trend charts, and a Host health strip below. That strip is
what the standalone heatmap became, except each card is a button that
re-scopes everything above it instead of a cell you can only read.
There is deliberately no second tab strip inside the page. Overview / Alerts
/ Rules / Capacity / Doctor live in the group's own top bar, the way the mock
puts them — a nav under a nav reads as two competing headers.
Also: the sidebar said "Observability" for a page, a route and a title that
all said "Monitoring", and the delivery channels rendered Discord, Slack and
a raw webhook with the same generic glyph. Both fixed.
/fleet-monitor redirects; the Servers tab strip loses the entry.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Make the panel's identity admin-configurable instead of hardcoded, and let
operators pick a login-page layout — so instances can be branded/varied from
the panel rather than by editing source.
- Settings: new `panel_title` (default "ServerKit") and `login_layout`
(default "centered") keys via SettingsService (key/value — no migration).
- Exposed pre-auth on the existing GET /auth/setup-status so the login page
reads them before any authenticated call.
- Frontend threads panelTitle + loginLayout through AuthContext; document.title
(App.jsx PageTitleUpdater + usePageTitle) and the auth-page headings use the
configured title, with import.meta.env.VITE_PANEL_TITLE as the build-time
default for the first paint.
- Login page refactored to an AuthLayout selector with three variants —
Centered (current), Split-hero, Minimal — chosen by `login_layout`.
- Admin UI: a "Panel Appearance" card (title field + layout dropdown) in the
admin-only Site Settings tab; saves via the generic settings API and calls
refreshSetupStatus so the running SPA updates live. Searchable via a new
settingsIndex entry.
Register/Setup adopt the configured title on their headings. manifest.json
stays static (documented limitation). Verified: eslint clean on all touched
files (0 errors), settings-index guard passes. (The repo's full `vite build`
currently fails on an unrelated pre-existing plugin import, serverkit-tramo →
`tramo/react`, independent of this change.)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
"Marketplace" reads as a store; the page is where you browse and install
extensions, so call it Extensions.
- Nav label + page/tab title + route /marketplace -> /extensions
(first tab drives the topbar title via TabGroupLayout).
- /marketplace and /marketplace/installed now redirect to /extensions.
- Update command palette, page-context map, NotFound, empty-state copy.
- Keep the internal nav id, CSS classes, component/file names, and the
backend /api/v1/marketplace/registry endpoints unchanged; palette keeps
"marketplace" as a search keyword so the old term still finds it.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Rebuild CommandPalette around scored providers (pages, settings, actions,
plugin contributions, docs, async entities) with a single subsequence fuzzy
scorer and shouldFilter={false} so cmdk no longer double-filters. Adds:
- data/palettePages.js, data/settingsIndex.js (84 cards), data/commandActions.js
- utils/paletteScore.js (word-start bonus + category weight) and
utils/paletteFrecency.js (localStorage frecency: count + time decay)
- hooks/usePaletteAuthz.js — mirrors the sidebar's admin + workspace-nav gate
so a member's palette never surfaces admin surface
- F1 and Ctrl/Cmd+Shift+P open aliases alongside Ctrl/Cmd+K
- prefix modes (`>` actions, `?` docs), Recently used empty state, kbd footer
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
/services/new rendered everything at once regardless of state — a "Ready to
Import" rail of placeholder rows, a decorative Connect->Detect->Deploy strip, a
numbered guide, and two boilerplate note boxes. It is now a slim stepper over
one centered column.
- three explicit steps: Source (radio-cards) -> Connect (source-specific input
+ API-driven template picker) -> Review (manifest detection, buildpack, env
list, core fields; nothing renders without real data)
- decomposed the 1,327-line monolith into pages/new-service/: a
useNewServiceForm hook (step state + submit) with presentational SourceStep /
ConnectStep / ReviewStep components
- ?template=/?source= deep links land preloaded on the Connect step; deploy
templates load from the backend catalog (kind: repo) instead of a JS constant
- submit payloads are byte-identical to before (flow re-skin, not a backend
change); _new-service.scss rewritten for the stepper layout
- deletes the hardcoded frontend catalog (data/deployTemplates.js) now that its
last consumer is gone, and the old single-file NewService.jsx
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Per-source explainer strip (SOURCE_GUIDES): the selected source card renders
what it does, what to have ready, and the 2-3 next steps.
- Extract the hardcoded SERVICE_TEMPLATES into frontend/src/data/deployTemplates.js;
the Templates tab gains a 'Deploy templates' section (badged 'Example' for the
demo) with a 'Use template' deep-link (/services/new?template=<id>) that
preselects it in the wizard. One list, two entry points; Template Library
button now targets #deploy-templates.
- New docsLinks.js map + <DocsLink> (book icon, opens serverkit.ai docs in a new
tab, hidden under White Label — the single place that rule lives). Wired into
the New Service source pane (deploy-sources / manifest) and the Marketplace
manual-install modal (extensions/installing).
Lint 0 errors; build clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>