Commit Graph
26 Commits
Author SHA1 Message Date
Juan Denis 6296887bad Remove internal plan citations from app code
Comments, docstrings and a few strings cited plan numbers ("plan 29
#9", "plan 86 §B2") from a git-ignored folder, so they pointed at
nothing; one reached users in a bottleneck hint. Code and frontend
strings are unchanged apart from those citations (checked by AST and
token comparison). A vhost still carrying the old asset comment reads
as undrifted, and a guard test keeps new citations out.
2026-10-10 15:50:45 -04:00
Juan Denis 16837621f6 Use one term per concept across the UI
Applies the copy glossary to about 370 strings and 64 call sites:
service (not app/application/site), sign in/out (not log in/out),
server and panel server (not box/machine/host), Delete destroys /
Remove detaches / Uninstall is for software, New opens a form and
Create submits it, settings, backup vs snapshot vs restore point,
extensions and Features, environment variables, compose project,
two-factor authentication. Confirm dialogs that destroy something now
use the same verb on the title and the button instead of "Confirm".

Backend messages that reach toasts verbatim ("Application not
found" x95 and others) are listed in plan 88 for the typed-error
work rather than edited here.
2026-10-04 04:48:33 -04:00
Juan Denis 611dcdc00c Normalize UI copy: sentence case, no em dashes or filler
1,250 strings across 223 files, edited at their inline English
defaults (en.json regenerated, keys unchanged):
- sentence case for buttons, titles, labels, tabs and nav (827),
  with an allowlist of product names and acronyms
- every em dash rewritten (184); "successfully", neutral "!" and
  short-toast periods dropped; helper sentences end with a period
- "Are you sure?" confirms now state the consequence; "Danger Zone",
  "no going back", "to get started" and "You're all set" gone
- straight quotes, "…", "time zone", unit spacing, "and" for "&"
- Confirm/Cancel and four other hardcoded labels wrapped in t()

scripts/copy-rewrite.mjs applies a {key: English} map at every call
site; the glossary pass reuses it.
2026-10-04 04:30:42 -04:00
Juan Denis 5e6c49c1b6 Put spacing, radii and controls on one geometry scale
Measured across the app: buttons rendered at 25 heights, 452 of them
at the browser's default 13.33px font, with 19 radii and 22 shadows.

- Controls: 28/32/40 tokens; buttons, inputs and selects default to
  32px (the dense-product norm), 28 small, 40 large. Buttons and form
  fields inherit the page font.
- Radii: four theme-driven tokens (badge 4, control 6, card 8,
  overlay 12). Theme radius now reaches every corner, not ~10%.
- Spacing: every padding/margin/gap is a $space-* token on the 4px
  grid, with 2px and 6px half steps; odd values snapped.
- Flat primary button and status dots: no gradient, glow or inner
  highlight. Badges are 20px with a 4px radius.

check-geometry rejects the legacy names and ratchets the raw shadows
and control heights that still need a judgment pass.
2026-10-04 03:29:16 -04:00
Juan Denis 7c303f856c Add a Storage tab to admin settings
Shows where the panel host's disk goes, when the rollback install is
removed, and the retention windows that bound ServerKit's own share,
with cleanup handed to the existing reclaim modal.
2026-09-27 16:40:48 -04:00
Juan Denis d866de5439 feat(connect): apply the managed presentation profile (plan 25)
Panel side of the managed profile: the panel receives a presentation
profile from Cloud over the existing command channel, stores it,
acknowledges it, and hides or simplifies fleet-owner-only surfaces
until the profile is lifted or the link is revoked.

- ui.managed_profile command handler with managed-profile.json
  storage, audit entries, and ack-by-output; profile is cleared on
  disconnect and on consent revoke
- GET /api/v1/connect/managed-profile for the local UI
- ManagedProfileContext with capability-driven filtering of sidebar
  items, command palette actions, and sidebar settings; ManagedCard
  explains the managed state
- In-page collapses for Templates deploy, About self-update, and
  Backup schedules/storage (restore stays available)
- Settings gains a ServerKit Cloud tab showing link and profile state

Local verification: 153 backend tests pass, 198 frontend node tests
pass, lint clean, build ok. No live policy has been pushed to a real
panel yet.
2026-09-13 03:50:02 -04:00
Juan Denis 8e9c4fa6b0 fix(settings): unify forms, notifications, and AI configuration controls 2026-09-08 03:03:19 -04:00
Juan Denis b44f2439f9 feat: add optional walkthrough guides 2026-08-31 22:36:14 -04:00
Juan DenisandClaude Fable 5 8b7cecb62e feat(palette): create tile grid + recipes; the sidebar + opens the palette
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>
2026-08-22 02:43:24 -04:00
Juan Denis c0de9fbcac refactor(i18n): consolidate duplicate locale strings into common.* namespace
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
2026-08-22 01:32:52 -04:00
Juan Denis d6950cc40f feat: add guided operator walkthroughs 2026-08-21 22:04:09 -04:00
Juan Denis cc9c3fb70d feat(i18n): convert all remaining user-visible literals to translation keys
Mass conversion of ~8,000 English literals across 320+ files to
t('app.<file>.<key>', 'English default') calls, plus hand-conversion of
the 43 literals the codemod refused (concatenated toasts, module-level
helpers, class components) using bare t() from i18n/t or restructured
{{placeholder}} keys. Marketplace unsigned-release dialog now translates
the Install/Update verb inside the placeholder.

ESLint: 0 errors. Literal checker: 0 remaining.
2026-08-20 20:23:29 -04:00
Juan DenisandClaude Opus 5 fe3c0d77bc feat(i18n): language selector, and fix two silent locale failures
Plan 79 B3 + acceptance 2/3/7.

The selector lives in Settings -> Appearance beside the theme picker, because
it is the same shape of choice, and is registered in the settings search index
so it is findable. Built on ui/select rather than a hand-rolled dropdown.

Proof is a Playwright probe (.claude/skills/screenshots/probe-locale.mjs --
that tree is git-ignored, like docs/plans). A screenshot in one language
proves nothing: the copy could be hardcoded English that happens to look
right. The probe loads the shell under two stored locales in a browser whose
own language is French, so following the browser cannot pass it, then drives
the REAL selector and asserts what the plan committed to:

  1. the shell renders per-locale and <html lang>/<dir> follow the panel;
  2. selecting Spanish re-renders the sidebar with no navigation, and caches
     the choice for the next pre-auth paint;
  3. with every API call aborted, the pre-auth screen still renders in
     Spanish -- which is the whole reason locales are bundled, not fetched.

It immediately earned itself by catching two bugs that lint, build and reading
the diff all missed:

- `typeof import.meta.glob === 'function' ? import.meta.glob(...) : {}` -- a
  guard added so the module could be imported by node tooling. Vite replaces
  the CALL at build time; there is no such function at runtime, so the guard
  was always false and discarded every locale bundle. Every language silently
  fell back to English and nothing failed.
- Sidebar category headings stayed English in every locale: the keys lived in
  CATEGORY_LABEL_KEYS and the English in CATEGORY_LABELS, two parallel maps.
  Not sibling properties, so the extractor could not pair them and the keys
  never reached en.json. One object per category now holds both.

Both are the failure this plan exists to prevent: copy that looks translated
and is not, with nothing to say otherwise.

es.json covers all 164 keys.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-19 23:59:25 -04:00
Juan Denis 4c4ccb4a18 refactor(frontend): enforce shared browser boundaries 2026-08-18 02:48:56 -04:00
Juan DenisandClaude Opus 5 ac4d3bf227 feat(recycle-bin): Recycle Bin UI under Settings > Admin
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>
2026-08-12 07:25:00 -04:00
Juan DenisandClaude Opus 5 c66d5037ef feat(ds): DataGrid -- the list surface for pages where the table is the page
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>
2026-08-12 05:52:05 -04:00
Juan DenisandClaude Opus 5 ce3420f607 refactor(monitoring): one group for Monitors, Incidents, Events and Jobs
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>
2026-07-28 00:03:27 -04:00
Juan DenisandClaude Opus 5 85359c83eb refactor(nav): dev-gate the Test Sandbox, move Webhooks into Settings
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>
2026-07-27 22:44:28 -04:00
Juan DenisandClaude Opus 5 b413148194 feat(monitoring): fold Fleet Monitor in, and scope the page to a server
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>
2026-07-25 19:03:15 -04:00
Juan DenisandClaude Fable 5 7d4543312c feat(themes): add 11 bundled Venezuelan seed themes
Turpial, Araguaney, Orquídea, Apamate, Ceiba, Azulejo, Guacamaya, Cacao,
Papelón, Onoto (dual dark+light) and Tepuy (radius override) — named after
Venezuelan national symbols and nature. Authored in
frontend/src/data/themes/, synced to backend via sync-bundled-themes.mjs
(17 bundled themes total). check-theme-tokens + backend theme suites pass.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 10:22:23 -04:00
Juan DenisandClaude Opus 4.8 0016b1ff22 feat(themes): theme gallery + runtime skins (plan 60 Phase 1)
A ServerKit theme is data, not code: a validated map of CSS custom-property
tokens. This lands the client-side foundation.

- themeTokens.js: canonical token whitelist, legacy-alias table, and strict
  value validators (reject url()/@/;/}/expression, type-check per group)
- applySkin.js: runtime skin applier via setProperty (injection-safe); dark/
  light resolution with stock-light fallback for dark-only themes
- ThemeContext: per-user skin selection (localStorage), live hover preview,
  and accent precedence workspace > user > skin > default
- 6 bundled seed themes (default/paper/nord-deep/gruvbox/phosphor/high-contrast)
- Theme Gallery in Settings > Appearance (instant apply, 4-swatch cards) +
  command-palette index entry
- hardcoded-hex audit: 3 exact token-value leaks fixed to var(--token)
- check-theme-tokens.mjs proving guard wired into the lint chain

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-22 23:55:19 -04:00
Juan DenisandClaude Opus 4.8 9a1a1f69a3 feat: configurable panel title + switchable login layouts
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>
2026-07-15 13:01:39 -04:00
Juan DenisandClaude Opus 4.8 76a2788d24 refactor(ui): rename Marketplace to Extensions
"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>
2026-07-12 02:37:17 -04:00
Juan DenisandClaude Opus 4.8 3e0fbd2f81 feat(palette): omnisearch core — providers, ranking, VS Code keys
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>
2026-07-10 23:31:25 -04:00
Juan DenisandClaude Opus 4.8 bd87b06a60 feat(new-service): a real three-step wizard, not a form-wall
/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>
2026-07-10 02:39:23 -04:00
Juan DenisandClaude Opus 4.8 6accbf9655 feat(services): New Service wizard clarity — guides, shared templates, docs links
- 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>
2026-07-09 02:12:24 -04:00