2814 Commits
Author SHA1 Message Date
Juan Denis 23d950b528 Translate inline copy in ds and shared modules
Grid menus, the filter drawer, KPI band and view pickers sit on every
list page, so their English sort, group and count labels showed in
every locale. Module-level helpers (operations, service types, monitor
scope, the AI context) now call t() at use time or carry labelKeys.
operations.test initialises i18next like errorMessage.test, since
operations.js now translates its labels.
2026-10-04 18:13:37 -04:00
Juan Denis 1a5137b685 Translate inline copy in shared components
Service, database, security, Docker, monitoring and deploy-console
components showed English busy labels, status arms and fallbacks in
every locale. They now go through t() in house style, with _one/_other
plurals for counts. Sentences that were split around an element
(". The statements are unlikely to apply cleanly.") become whole
keys, so a translator can reorder them. LogFileList and
ImportDumpModal resolve their declared keys instead of raw labels.
2026-10-04 18:12:33 -04:00
Juan Denis 878295dccf Translate inline copy on the pages
Page-level busy labels, status arms, empty-state fallbacks and
templated counts were English in every locale. They now go through
t() in house style: sentence case, "…", "Couldn't X." errors, and
_one/_other plurals where a count used to be hand-rolled. CronJobs
renders its state labels through their keys, and auth.signIn reads
"Sign in" to match the glossary.
2026-10-04 18:11:52 -04:00
Juan Denis 3951221fd3 Translate inline copy in settings and setup
Busy-state ternaries, fallbacks and templated labels in the settings
tabs, the connections hub and the setup wizard rendered English in
every locale because the census could not see them until afe81eac.
They now go through t() in house style, with real plurals for counts.
ProviderCard and the sidebar presets also resolve their declared
labelKeys instead of showing the raw label.

es.json drops errors.* keys that en.json does not define yet; they
stay in the working tree for the typed error codes still in progress.
2026-10-04 18:10:48 -04:00
Juan Denis 586d9de205 Rewrite error copy as "Couldn't X." with plurals
Errors read five ways ("Failed to X", "X failed", "Could not X",
"Error loading X", "Couldn't X") and many dropped their period.
407 core strings now say "Couldn't {verb} {object}." with any
{{placeholder}} kept after it, and the generic common.error strings
say what to do next.

Interpolated English verbs could not be translated and produced
"Failed to restart container"-style grammar for every action, so 17
sites (container, compose, service, unit, process, alert and on/off
toasts, sort labels) now pick one string per action. 37 hand-rolled
plurals ("(s)", item{{value}}, fragment + `count === 1 ? '' : 's'`)
use i18next _one/_other keys: extract-i18n reads defaultValue_one /
defaultValue_other into k_one / k_other, and check-i18n accepts a
translator's extra forms (k_few, k_many) beside the English pair.

Validation says what is valid (IP or CIDR range, JSON payloads,
verification codes, sign-in links). The PRIMARY backup badge and the
upper-cased service type are plain text; container status labels go
to sentence case. Orphaned translations pruned; the i18n literal
ceiling drops to 840.
2026-10-04 17:46:37 -04:00
Juan Denis 708c73c5da Show the server's reason in every error toast
Error toasts said either what failed ("Failed to delete service")
with the reason thrown away, or only the server's reason ("Not
found") with no subject. utils/errorMessage.js is one door for both
halves: errorReason(err) reads data.error, data.message, then
err.message (a translated error code wins), and turns bare transport
failures (Failed to fetch, Request failed (500)) into a next step;
toastError(toast, message, err) renders "What failed. Why."

Moved onto it: 236 `err.message || t(...)` sites, 58 bare t() toasts
in catch blocks that dropped the error (51 gained a catch parameter),
and 63 toasts that showed only err.message, which now say what failed
under new keys. 56 "{{message}}" interpolations and ErrorState read
the reason through errorReason. The pages/ toast ratchet counts
toastError calls, since they are the same per-page presentation.
2026-10-04 17:39:07 -04:00
Juan Denis afe81eac29 Count inline and templated copy in the i18n census
The literal census missed copy a user reads through an expression:
both arms of a JSX ternary ({saving ? 'Saving...' : 'Save'}), the
fallback of a || or ??, the right side of &&, template literals
(read as one sentence with holes as {{x}}), and the same shapes as
values of label/title/description keys with no matching *Key. It now
counts them, and reads only the arms of a ternary in copy attributes,
not its test.

Untracked extension copies under src/plugins are skipped: they are
censused in their own repos, and counting them made the host number
depend on what a machine has installed.

The ceiling moves from 0 to the honest count, 844.
2026-10-04 17:37:10 -04:00
Juan Denis 8fddc31e20 Normalize builtin extension copy
Applies the panel copy rules to the five builtin extensions. Tab,
page-title and command-palette labels go to sentence case (Cloud
servers, FTP server, Remote access, Status pages); display names stay
as extension names.

Walkthrough Studio: no period on short toasts, "Couldn't" errors, a
delete confirm that states the consequence, and four hardcoded strings
wrapped in t() (kicker, step eyebrow, default duration and action),
with Spanish for the new keys. 30 strings in all: Studio 16, cloud
provisioning 4, FTP 4, remote access 3, status 3.

Synced into frontend/src/plugins and the tracked live serverkit-status
manifest.
2026-10-04 17:33:01 -04:00
Juan Denis 6d2f90b581 Fit dashboard quick actions to the control height
Each shortcut row was 42px with card padding and radius, so a
one-row widget cropped its third action. Rows now use the 32px
control height and control radius like every other button.
2026-10-04 17:21:25 -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 89c59b055b Size every control, table, card and badge from the geometry tokens
Second geometry pass, over every stylesheet: control heights are 28,
32 or 40; tables have a 32px header and 40px rows (the databases grid
stays compact at 32); cards use one radius and 16px padding; about 30
page-specific pill classes follow the 20px badge spec; and shadows are
only on things that float (menus raised, modals and drawers overlay),
with focus as one ring. Glows, hover lifts and card shadows are gone,
and page overrides of the shared primitives are removed.

Sign-in and setup screens use the 40px size. Fixed chrome (status bar,
icons, avatars, chart boxes) is marked // geometry: and exempt.
Measured: distinct button heights 25 -> 13, table headers 8 -> 3,
badges 7 -> 3. check-geometry ceilings are now zero for raw shadows
and control heights.
2026-10-04 04:10:02 -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 bf3bcfa5e3 Show load errors on list pages instead of empty states
On about 30 pages a failed request rendered the empty state, often
with a call to action: SSL offered to install Certbot, Domains said
"No domains yet" (its error branch was unreachable), the catalog and
monitoring pages showed empty lists, Fleet printed "null%" and Server
Templates went blank. ResourceListPage now takes error/onRetry, and
every page separates loading, error (server reason + Try again) and
empty, keeping existing rows with a compact error when a refresh
fails. Settings tabs a user can't open redirect to Profile.
2026-10-04 02:48:47 -04:00
Juan Denis b571f01465 Show load errors in settings and security instead of empty states
A failed request in these tabs read as "nothing here": the firewall
tab offered to install a firewall, Cloud said not connected, About
invented version 1.0.0, and the security config, site settings and
2FA policy forms rendered with defaults, so saving after a failed
load would overwrite the real settings. Each tab now separates
loading, error (with the server's reason and Try again) and empty,
and keeps existing data with a compact error when a refresh fails.
2026-10-04 02:43:05 -04:00
Juan Denis 1d48311911 Fix the Telemetry page crashing on any error
It called useToast().showToast, which does not exist, so every load,
emit or cleanup failure threw inside its own catch and the page fell
over instead of reporting the error.
2026-10-04 02:29:54 -04:00
Juan Denis eb0e9c5d35 Fix "Service stoped successfully" and other verb toasts
Service and FTP start/stop/restart toasts built the past tense by
appending "ed" to the action, which printed "stoped" and could not be
translated. Each action now has its own string. Also points the two
uses of the undefined --radius-md token at --radius-sm, so those
corners stop rendering square.
2026-10-04 02:10:18 -04:00
Juan Denis 246eb605a7 Keep monospace for machine values only
Table columns, detail rows, the status bar, the sidebar and the
dashboard widgets showed dates, counts, enum words and labels in the
monospace face. They now use the UI face (sk-cell-dim for dim table
cells); monospace stays on hosts, IPs, ports, versions, paths, IDs,
hashes, code and logs. check-type-scale ratchets the number of
monospace rules so it only goes down.
2026-10-04 01:42:34 -04:00
Juan Denis 0223c5ebe3 Drop uppercase labels and monospace on plain text
About 200 labels were uppercase with wide letter-spacing and about 180
label, title, meta, time and count rules used the monospace face, the
"terminal dashboard" look that reads as generated rather than chosen.
Labels are now sentence case in the UI face; monospace stays on
machine values (IPs, hosts, ports, versions, paths, commands, IDs,
code, logs). Body text uses tabular figures so numbers still line up
in tables without monospace.
2026-10-04 01:28:03 -04:00
Juan Denis ddd817f768 Put all text on one seven-step type scale
Core SCSS used about 30 font sizes (9, 9.5, 10.5, 11.5, 12.5px...)
and six weights, so the same kind of text looked different on every
page. It now uses -xs..2xl (12, 13, 14, 16, 18, 24, 32px) and
three weights, following the dense-product norm: 14px body and a
12px floor, as in Primer, Carbon and Atlassian, which raised its own
minimum from 11px to 12px for legibility. Small labels that were
9-11.5px grow to 12px.

The old $font-size-* names stay as aliases on the new scale so
extension stylesheets keep compiling; check-type-scale rejects them,
raw sizes and off-scale weights in core.
2026-10-04 01:24:25 -04:00
Juan Denis a5bb7d3a7c Extract i18n keys for the last select sweep
Also lowers the logical-property ceiling to 427.
2026-10-03 23:33:07 -04:00
Juan Denis b32a90b36d Ratchet one field per job in lint
check-shared-fields fails on a native <select>, a second server
picker, or a page calling the base-domain or port-check APIs instead
of DomainField and PortField. A new exception needs a reason in the
allowlist.
2026-10-03 23:33:06 -04:00
Juan Denis d1c310bb94 Replace the last native selects with the shared dropdown
Table footers, column menus, the filter drawer, the schedule picker,
the widget editor, the log viewer and the env edit row now use the
compact shared Select. Also drops CSS left dead by the field sweep.
2026-10-03 23:33:02 -04:00
Juan Denis 075989f922 Treat an open Select as an editable control
Keyboard shortcuts and DataTable row navigation only skipped native
<select> elements. With Radix Select, Escape closed the widget editor
and arrows moved the table cursor while a list was open.
2026-10-03 23:32:58 -04:00
Juan Denis f81345ea75 Let DomainField callers state their own DNS step
Tunnel hostnames are not an A record at this server: a Cloudflare
route creates its own record and a WireGuard tunnel needs the name
pointed at the edge. hint(name) replaces the default line there.
2026-10-03 23:22:03 -04:00
Juan Denis d194d5d2ab Add a compact size to the shared dropdown
Toolbars, table footers and inline rows kept native selects because the
Radix trigger is a full-width 40px form field. size="sm" gives them a
content-width 30px trigger, so they can use the same dropdown.
2026-10-03 23:20:34 -04:00
Juan Denis fdaeddef3a Extract i18n keys for the shared-field sweep
Also lowers the logical-property ceiling to what the sweep left.
2026-10-03 23:19:50 -04:00
Juan Denis e5cd50227b Move pages onto the shared field components
Every page that picked a server now uses ServerPicker, so TargetPicker
and the templates-only picker are gone. Install commands and
connection strings are CopyFields, single-hostname inputs are
DomainFields, host ports are PortFields, and native selects are the
shared dropdown. The style guide shows each shared field.
2026-10-03 23:19:34 -04:00
Juan Denis 1451297317 Move shared widgets onto the shared field components
Log, journal, AI model and connection pickers, the capacity and
threshold panels and the deploy console toolbar use the shared
dropdown and ServerPicker; error cards and private URLs copy through
CopyButton/CopyField instead of calling the clipboard directly.
2026-10-03 23:19:28 -04:00
Juan Denis 1186594989 Move app, database and Docker panels onto shared fields
Run Container takes host and container ports as PortField rows and env
as an EnvEditor, database credentials and connection strings are
masked CopyFields, engine ports are checked live, and server, service
and database detail rows are InfoList. Native selects become the
shared dropdown.
2026-10-03 23:19:17 -04:00
Juan Denis eecb487d7a Move settings onto the shared field components
Secrets (API key, webhook secret, 2FA setup) are CopyFields that stay
masked until revealed, SMTP and firewall ports are PortFields, the
canonical and base domain inputs are DomainFields, detail rows are
InfoList, and every native select is the shared dropdown.
2026-10-03 23:19:08 -04:00
Juan Denis 965a4fdc22 Expose the shared field components in SDK 1.6.0
Extensions hand-roll their own server selects, domain inputs and copy
boxes because core never exported its versions. Exporting them keeps
an extension page looking and validating exactly like a core page.
2026-10-03 22:53:14 -04:00
Juan Denis 3f772c57f6 Add ServerPicker, CopyField, PortField and EnvEditor
One component per job, so pages stop hand-building them:
- ServerPicker: which machine; local first, capacity, capability filter
- CopyField: any value meant to be copied; secrets stay masked
- PortField: host ports checked live against /ports/check, naming the
  app that holds a taken port and offering the next free one
- EnvEditor: KEY=value rows for forms; a pasted .env fills the rows
Call sites move onto them in the following commits.
2026-10-03 22:50:21 -04:00
Juan Denis 2f52c87a39 Add /ports/check and /ports/suggest
The deploy paths already know which host ports are taken (app rows,
slot rows, Docker mappings, a bind test) but only used that to pick a
port silently. Exposing it lets the shared port input say "8001 is
used by shop-api" and offer the next free port, instead of failing
at deploy time.
2026-10-03 22:46:54 -04:00
Juan Denis 30a3bfda82 Add DomainField, one input for picking a domain
Every screen that asked for a domain had its own input: a copied
regex in service settings, a separate "give it a subdomain" modal, a
bare required field on the Domains page. DomainField offers a managed
subdomain (label + base domain) or the user's own domain, normalizes
what is pasted, and says what will happen with DNS before submit.
attachDomain() sends either kind to the right endpoint.

Service settings now has one add-domain flow instead of an attach
form plus a subdomain modal, and the Domains page add modal uses the
same field.
2026-10-03 21:16:02 -04:00
Juan Denis 3e7aedbf17 Move extension, engine and server-template cards onto CatalogCard
The three remaining catalogs each had their own card: a 140px cover
banner with source, category, first-party and trust badges on
Extensions, a legacy SharedCard on Server Templates, and a bespoke
button card in the engine drawer. All three now use the shared
CatalogCard: one tag, author and trust as plain text, an outline
action, and a flat surface. Server-template facts also get singular
forms ("1 package", not "1 packages").
2026-10-03 21:12:32 -04:00
Juan Denis 29dc925e6e Add CatalogCard and move Templates onto it
Extensions, templates, server templates and database engines each
drew their own card with their own badges. CatalogCard is the one
shape for browsable things: icon, name, version line, one plain-text
tag, a two-line description, facts and one action. Templates is the
first user; the icon-only website/docs markers and the hover-reveal
deploy button went with the old card.
2026-10-03 21:08:14 -04:00
Juan Denis e691a0842e Lower the logical-property ceiling to 438 2026-10-03 21:06:05 -04:00
Juan Denis 2b1028f29a Put the Databases header on the shared top bar
The explorer drew its own toolbar with an h1 titled differently from
the sidebar entry and a hand-rolled New menu. It now uses PageLayout
like the other workbench pages, with the shared button and dropdown
primitives; the tree and workspace below are unchanged.
2026-10-03 21:04:15 -04:00
Juan Denis 5930ed5ac4 Put Docker on the shared page shell
Docker was the one list page with its own layout: an inner sidebar
for Containers/Images/Volumes, an Inventory panel repeating the KPI
counts, and a bespoke workbar title. It now uses PageLayout like every
other standalone page, with those sections as top-bar tabs and the
target picker, prune and create actions in the bar. /docker redirects
to /docker/containers so the first tab lights up. The old sidebar,
workbar and container-card CSS was unused and is removed.
2026-10-03 21:02:19 -04:00
Juan Denis 595a686981 Cut filler subtitles and the Marketplace kicker
Section headings in Settings, the new-service wizard, Agent Fleet and
restore points each had a one-line description that restated the
heading. The Installed section header said "installed" three times.
Only lines that say something specific (what disabling a module does,
install instructions, user data) are kept.
2026-10-03 20:54:39 -04:00
Juan Denis c7ea5ef3db Drop the servers hex-colour baseline to zero 2026-10-03 20:54:35 -04:00
Juan Denis d4aaede728 Flatten gradient surfaces, drop dead servers CSS
Detail headers and table heads used a top-lit gradient plus a radial
accent wash. They are now a flat surface, matching every other card.
The old Servers card layout (hero, health panel, card grid, empty
state) was no longer referenced anywhere and carried most of the
remaining gradients, so it goes.
2026-10-03 20:51:29 -04:00
github-actions[bot] 37115b4f42 chore: bump version to 1.11.17 [skip ci] 2026-10-03 04:48:00 +00:00
Juan Denis 93a3cd783e Let GET /api/v1/servers accept a scoped API key
Vela holds one ServerKit API key as a host-side secret and polls the
fleet list, so this read route moves from bare @jwt_required() to
auth_required() + require_scope('servers:read') — a deliberate,
per-route jwt_only_census exception, not a sweep. JWT callers are
unchanged; a restricted key without servers:read gets 403 from the
scope middleware, no credentials get 401.

Scope choice: the existing servers:read catalog entry already covers
'list servers, agents, and metrics', and the serverkit-gui read routes
(capabilities/frame/surface, sibling repo) take the same scope, so one
key scope serves the whole Vela polling surface.

The census ceiling ratchets down to 600 and the migration inventory is
regenerated, as both ratchets require. tests/test_servers_api_key_scope.py
pins the four cases: scoped key 200, wrong-scope key 403, JWT 200,
anonymous 401.
2026-10-03 00:31:36 -04:00
Juan Denis 9da032588a Merge pull request #159 from jhd3197/dev
Open the panel from ServerKit Cloud, plus security dependency bumps
v1.11.16
2026-10-01 23:21:02 -04:00
github-actions[bot] 8a13bdde62 chore: bump version to 1.11.16 [skip ci] 2026-10-02 02:55:09 +00:00
Juan Denis 4c70658f90 Harden the Cloud tunnel per review
Session keys must name their purpose, and both loopback legs skip
environment proxies so the grant and bearer token never leave the box.
In the tunnel the API always goes through the relay, which attaches the
grant, and the two raw root anchors become router Links so they stay
under the /t/<device> prefix.
2026-10-01 22:47:48 -04:00
github-actions[bot] d8ff654620 chore: bump version to 1.11.15 [skip ci] 2026-10-02 02:43:21 +00:00
Juan Denis 73a53b8692 Classify the Cloud tunnel route and threads
The connect-session route authenticates by the loopback-only Cloud
grant rather than a session, and the tunnel's Socket.IO bridge starts
two raw threads; both guards failed CI until they were recorded.
2026-10-01 22:36:22 -04:00