The Templates page only featured a hard-coded list, so `featured: true`
in a template (Prompture Hub, Torre) never reached the Featured-first
view. The page now honors the flag alongside the list, and bundled
templates carry it into the catalog and the published index.
FileRow and FileCard were wrapped in memo, but every handler passed to
them was re-created on each render, so a keystroke or a selection
re-rendered every row of a large directory. useStableHandler gives the
rows fixed handler identities that still run the latest code.
The picker hardcoded ids like sp-hour, so two on one page (one per
schedule card) produced duplicate ids and every label pointed at the
first picker's field.
The pickers dropped an id, so the five labels above them named
nothing. They now put it on the trigger button; clicking the label
opens the picker. Ratchet 46 -> 41.
Three hand-rolled copies printed untranslated "3m 0s" and never
reached hours ("75m 0s"). They now use the intl formatter ("1h 15m",
in the panel language), keeping each caller's placeholder. The cron
and deploy-console formats with tenths of a second stay as they are.
156 labels named no field: no htmlFor and no control inside, so
clicking one did not focus the input and screen readers announced the
input without a name. Each now points at its field through a useId()
id. The change is additive only (ids and htmlFor), checked by comparing
tokens with the previous version.
A row with onRowClick could only be opened with a mouse or the j/k
cursor: it took no Tab focus and ignored Enter. Clickable rows in the
tables, the grid and the card view are now focusable and open on Enter
or Space, with a focus ring. Keys on a button inside the row still
reach the button, and the cursor listener skips a key a row handled.
The vault secret and variable-group columns were memoized without the
state their delete handlers read. Deleting a secret then refreshed a
null vault and reported failure; deleting a group variable sent group
null; deleting a group after a workspace switch reloaded the old
workspace's groups.
The lookup's answer checked the name as it was when the code was
typed, so a name entered while it was in flight was overwritten with
the agent's hostname.
The listing effect watched only the path. Switching to a remote agent
that advertises no allowed paths keeps the path, so the panel host's
files stayed on screen under the agent's header, and edits went to
the agent against the wrong host's entries.
Both used psutil without importing it, so every call has been a 500
since they were written. Nothing calls them: the process list and
service status come from /processes and /processes/services.
The Coexistence page and the extension crash card were written with
their own stylesheets, but neither file was ever imported, so both have
always rendered without their layout. A new lint step,
check-orphan-files, fails on any src file nothing imports (SCSS
included) unless it is kept with a reason; it is what found these two.
An app-detail SettingsTab and its shared helper left behind when the
page merged into service detail, a re-export shim nobody imports, and
an unused utility; with their 13 strings in every locale.
420 lines styled a query runner component removed long ago. One of its
unscoped rules, .sidebar-section, was still sizing the File Manager
rail by accident; that rule moves into the File Manager stylesheet so
the compiled CSS for it is unchanged (compared before and after).
Picking a priority in the Terminal's journal view highlighted the level
but never sent it, so the list was unfiltered. The API already filtered
on ?priority=; the client now passes it, with the fresh value rather
than the previous render's.
Volumes, Networks and Compose had the same two bugs fixed earlier in
Containers and Images: a late answer from the previously selected
server replaced the current list, and a failed load showed an empty
list (or the old one) instead of the error.
Four inline dialogs kept their open flags, targets and input values as
ten separate useState calls on the page. One dialog state now says which
is open, and NamePromptModal / PermissionsModal own their inputs; the
browser test drives all three flows and passes on the old code too.
The create-file dialog had no way to open it and is removed with its
strings.
Comments cited plan sections as "(§G3)" or "(§8 unification)", which
point into the same git-ignored plans the plan-number citations did.
References next to a real document (docs/REDESIGN_MAP.md §3) stay. Code
and runtime strings are unchanged (AST and token comparison).
The worst cases the audit named: docstrings and header comments that
told how three old copies drifted, or quoted subprocess counts that
were stale the day they were written, instead of stating the contract.
Each now says what the function does and keeps one line of why. Also
drops the always-zero n_cloud counter that existed only to keep a boot
log line's or-chain intact.
107 direct toLocaleString/DateString/TimeString calls followed the
browser's locale (or a hard-coded en-US/en-GB) instead of the language
the panel is set to, and built a formatter per call. They now go
through utils/intl (formatDate/Time/DateTime/Number), keeping each
site's components via parts. A missing date now renders the fallback
dash instead of "Invalid Date". Ratchet ceiling 115 -> 4.
Six view preferences each had a lazy localStorage read plus a copied
mirroring effect. usePersistentState does both and tolerates storage
that throws, so the page no longer crashes on it; the panel as a whole
still needs storage for its auth tokens.
Six Radix primitives (accordion, avatar, progress, scroll-area,
separator, tooltip) and class-variance-authority are imported by no
file in the panel or any extension repo. boneyard-js stays: the
skeleton capture script keeps it on purpose as its upstream source.
Components, hooks and a workflow-builder rules file left behind when
their last consumer was replaced or retired, along with their 76
strings in every locale and a style block only they used. Three
unreferenced components that were never wired in (the DNS cutover
drawer, member actions card and account security nudge) are kept:
whether to mount or drop them is a product call.
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.
Opening a folder while the previous listing was still loading let the
slow answer land last and set the current path back, pulling the user
into the folder they had just left. Only the newest browse may now
write.
Uptime columns defaulted to 100.0 and the pages formatted a missing
value as 100.00%, so a new monitor showed a perfect month. The average
also used 'uptime || 100', turning a fully down component (0%) into
100%. Missing data now reads as a dash and 0 stays 0; both status pages
share one helper.
The API now answers 503 when docker ps fails instead of an empty list,
so the Containers page no longer says "No containers" on a host
running many. The Containers and Images tabs show that error, and a
late answer from the previously selected server can no longer replace
the current server's list. Also fixes the local branch of the remote
Docker listing, which passed all= to a parameter named all_containers
and always failed.
Changing the line count, clearing a search or picking a journal unit
re-fetched through a closure from the previous render, so the request
used the old count, the old search pattern or the previous unit. The
loaders now take the new values explicitly. Also drops a stray brace
that kept the selected journal unit from ever highlighting.
Rolldown's manualChunks shim lets a group capture its dependencies, so
recharts pulled React core into vendor-charts and @xyflow claimed
helpers i18n needs; the entry, Login included, preloaded both chunks.
Priority groups give React first claim and keep charts/flow to their
own package. Entry preload: 2.41 MB -> 1.85 MB (726 -> 573 KB gz).
scrub() walked element children only, so comments, processing
instructions and CDATA passed through. They survive XML serialization
but close early when React re-parses the string as HTML (<!-->, <?x >),
so a registry or plugin icon could smuggle out an <img onerror> and run
script with the tokens in localStorage. Keep only allowed elements and
text; a browser regression runs eight bypass payloads in CI.
The sidebar and top bar moved to --surface; the status bar stayed on
--bg-sidebar, breaking the one-background contract the settings UI
browser regression checks.
Each list page restyled DataTable on top of the shared look: fourteen
re-skin classes, filled grey headers, padded cards inside padded pages
and nth-child column widths (which put Docker's Name column 300px in
once the checkbox column existed). The table wrap is now the only
frame, the header is unfilled, column widths come from the column
definition, and raw tables and ui Table uses render through DataTable.
scripts/check-tables.mjs holds it in lint.
Page bodies drop the 1400px reading column (and the per-page caps on
Domains, Deployments, Dashboard, detail pages and Recipes) for one
20px gutter everywhere; Settings caps only its form column. The top
bar gets the sidebar's surface and edge so the chrome reads apart from
the canvas, and the bulk-selection pill clears the status bar.
Also removes decorative chrome: gradient letter avatars and tinted
stat icons become neutral, Domains rows lose the icon tile and the
"Registrar -" sub-line, and destructive row actions are quiet ghost
buttons that turn red on hover (.btn-ghost.is-danger).
The 404 description and the database tuner confirms were built from
three or four t() fragments glued with +, which dropped spaces
("itbelongs", "CheckExtensions") and left translators guessing
(German rendered a short interruption as a short circuit). Each is
now one key with a proper translation in every locale. Also fixes the
German nav term for Dashboard and drops the retired sidebarView key.
Picking a built-in view on any ResourceListPage list showed Unsaved
at once: the live page bag carries pageSize and an unset search that
built-in views never declare. Compare only the page keys both sides
set, treating an empty search as unset.
On PageLayout the studio body sat in a block-level fill region, so its
flex:1 collapsed it to its content height. The tree also repeated
"Not installed" next to the Install button, truncating MySQL to MyS.
The light sidebar sat ~2% off the page and its border vanished, so the
nav blended into the content. It now uses the surface tone with a
visible hairline. The account menu loses its sidebar presets; the
Customize sidebar page already owns that.
A failed contributions request (the backend restarting under an update)
fell back to the baked plugins manifest and cached that for the whole
session. When the baked manifest is empty, every extension vanished
from the nav until a full reload. Retry with backoff instead.
The select sweep replaced native selects with Radix, so selectOption()
failed in the settings UI test. SelectItem now exposes its value as
data-value and the test opens the trigger and clicks that option.
A clickable card was role=button around its own action button, giving
assistive tech two conflicting controls. The title is now the real
button; the rest of the card stays a mouse-only shortcut.
Attach flows create a new domain row, and POST /domains rejects any
name ServerKit already has, so suggesting existing domains there was
guaranteed to fail on submit. rejectExisting drops the suggestions and
reports a taken name as an error. Other DomainField uses (SSL,
tunnels, canonical domain) still suggest existing domains.
Reveal state is keyed by row index. Deleting a revealed row moved the
next row's secret into that index and showed it unmasked. Delete now
shifts the revealed set with the rows and a pasted .env clears it.