Five small design/layout nits from the UI sweep:
- form.selectPlaceholder "— select —" → "Select…" / "Välj…", matching
the affordance style of every other placeholder (Filter…, Search…).
- FieldForm in edit mode now explains its locked controls with a muted
fields.lockedNote caption ("Key and type can't be changed after
creation.") instead of leaving four silently disabled inputs.
- FieldList rows truncate long labels (min-w-0 on the row button +
truncate on the label, shrink-0 on the badge and required marker)
instead of overflowing the 20rem column.
- The sidebar collapse toggle is hidden on narrow viewports (hidden
md:flex) instead of rendered permanently disabled/grayed — the rail
is forced collapsed there anyway.
- PageTitle gains text-balance so long titles wrap evenly.
Closes#73
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
FieldsPage kept the selected field definition in component state, so
reload lost the selection, fields couldn't be linked/shared, and
back/forward didn't navigate selections — inconsistent with
/vocabularies/:id and /objects/:id.
Move selection into the URL: the route becomes /fields/:key?
(optional segment), FieldList selection navigates, cancel/done
navigates back to /fields, and the page derives the selected def from
the already-cached field-defs query. An unknown or stale key (e.g.
after deleting the selected field) falls back to the create form.
Tests: deep link opens the locked edit form, select→cancel round-trips
through the URL, unknown key falls back to create.
Closes#72
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Nothing in the app respected prefers-reduced-motion — the kit's
data-open/closed animations, the skeleton pulse, and the sidebar width
transition all ran unconditionally. Add a global base-layer rule that
collapses animation/transition durations to a single frame when the OS
asks for reduced motion; one rule covers current and future additions.
Add overscroll-y-contain to the scrollable modal/popup surfaces
(DrawerContent, SelectContent, ComboboxPopup) so flicking past the end
of their content no longer chain-scrolls the page beneath, and to
AlertDialogContent for when it gains scrollable content.
Verified in the built CSS: the media query and
overscroll-behavior-y:contain both compile into dist/assets.
Closes#71
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The delete dialogs (DeleteObjectDialog and the shared
DeleteConfirmDialog) left their confirm button enabled during the
in-flight request, so a double-click fired a second DELETE that 404'd
and surfaced a spurious error. Disable cancel + confirm while pending
and swap the confirm label to a new actions.deleting ("Deleting…" /
"Tar bort…").
The login button disabled itself during login.isPending but kept the
"Sign in" label; it now shows auth.signingIn ("Signing in…" /
"Loggar in…") so slow networks get visible feedback.
Each fix is covered by a gated-MSW (or gated-promise) test asserting
the pending label + disabled state before releasing the request.
Closes#70
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Keyboard focus was invisible on the objects-table sort headers and
page-size select, breadcrumb links, the external-URI link, and the
combobox input/clear/trigger. Apply the shared focusRing helper in app
code and the kit's inline focus-visible classes (matching input.tsx)
in ui/combobox.
Make the search result count a role="status" live region so screen
readers announce updated counts while typing; the existing search test
now asserts the count through getByRole("status").
Closes#69
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Tooltip, toast, and combobox popups still hardcoded light colors
(bg-white, neutral-*, indigo-50) and rendered as white boxes in dark
mode; the objects-table page-size select did the same in app code.
Swap all of them to theme tokens (popover/accent/muted/destructive/
success) and replace the toast's literal "×" with the lucide X icon.
Wire browser chrome into the theme: color-scheme via CSS on
:root/.dark (follows the in-app toggle, not just the OS), a
theme-color meta kept in sync by the preload script and applyTheme(),
plus a unit test for the meta sync.
Extend check-no-raw-colors to also flag shadeless white/black
utilities outside components/ui/ so the objects-table case can't
recur.
Closes#68
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add shared formatTimestamp(value, timeZone, locale) helper — date + short
time in the instance tz/locale, with a UTC fallback on an invalid IANA zone.
Route the objects-table 'Updated' column through it (was inline, date-only,
unguarded). Display-only; storage stays UTC.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Same timing-window race as object-new-page: the 50ms delay let the logout
resolve (menu unmounts on me=null) before findByText caught 'Signing out…'
on the slow CI runner. Hold the logout open with a promise released only
after the pending state is asserted.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The 'narrow: detail renders inside a portaled drawer' test lazy-loads the
drawer chunk and exceeded the 5s default on the slow CI container (setup
alone took ~486s). Bump testTimeout on both vitest projects.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- CI runs the @vitest/browser-playwright (storybook) project, which needs the
chromium browser downloaded — add 'playwright install --with-deps chromium'.
- object-new-page in-flight test held the create mutation open with a 50ms
delay and raced the pending-state assertion (failed under CI timing); gate it
on a promise released only after asserting 'saving…', so it's deterministic.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>