Skip to content

UI refinement - #32

Merged
isTravis merged 5 commits into
mainfrom
tr/ui-polish-26-08
Aug 7, 2026
Merged

UI refinement#32
isTravis merged 5 commits into
mainfrom
tr/ui-polish-26-08

Conversation

@isTravis

@isTravis isTravis commented Aug 7, 2026

Copy link
Copy Markdown
Member

Design system. New src/components/ui.tsx (Button, ButtonLink, Input/Select/Textarea/Checkbox/Field, Alert, Table, Tabs, Badge, EmptyState, SectionHeading) plus radius tokens: --radius-control (4px) on things people touch, --radius-surface (2px) on structural containers. Bare rounded/rounded-sm/rounded-lg went from 99 occurrences to zero; rounded-full survives only on avatars, status dots, and progress bars. Inline primary buttons went 16 → 3 (a toast, a pagination marker, an active pill — none are buttons), and five competing hover treatments collapsed to one.

Settings. Three surfaces (user, org, collection) had three different navigation idioms and a tab bar copy-pasted into seven files with hardcoded active states. They now share one SettingsLayout with a sticky left rail — route-driven for user/org, scroll-tracked anchors for collection settings. Auth moved to requireAuth middleware for org and collection settings, which previously did a render-time window.location redirect. /settings/avatar was an orphan route with zero inbound links; its working upload is folded into Profile.

Collection URLs. Version is now a path prefix, views are path segments, and omitting the prefix means latest:

/:owner/:slug overview (latest)
/:owner/:slug/records /schemas /files views at latest
/:owner/:slug/v/1.2.0[/records|/schemas|/files] the same views pinned to a version
/:owner/:slug/versions and /versions/compare history and diff

This replaces ?tab=files, ?tab=metadata, /schemas?version=, /diff, and the double-v /v/v1.0.0 form — all of which now 302 to their new homes, so no existing link breaks. Page semver is bare (/v/1.2.0) since the API normalizes both forms.

Confirmed the wire protocol does not constrain page URLs: negotiate/push/pull and manifests are /api/* plus content hashes, webhooks carry {owner, slug, semver, hash}, and ARK identifiers are minted from NAAN + semver. The only server-side dependency was the ARK resolver (src/api/ark.ts), updated.

Information architecture. The collection header splits into a collection-wide top row (breadcrumb, visibility, Versions, Settings) and a version-scoped tab row carrying a version picker that preserves your current view when you switch. Previously the "version tab" was a non-clickable <span> that appeared and disappeared. Schemas honors per-version pinning (the data model always did; the loader just never passed ?version=). One overview layout now serves latest and pinned versions alike.

Dashboard. Rebuilt on a server loader over GET /api/collections?mine=true — one request instead of one per organization, which also removes a false "No collections yet" state that showed while ~15 requests resolved. The org sidebar became a facet rail with counts; rows gained description, semver, record count, and last-push time.

Fixes found along the way

  • 404s rendered React Router's developer error page, in production too — loader-thrown Responses need a route-level ErrorBoundary, which no route defined (a class error boundary can't catch them). HTTP status was already correct; only the UI was unwired.
  • Records permalinks were unreachable by keyboard (invisible group-hover:visible) at the far right of a scrolling table. Now a sticky right-edge column. Two rendering bugs behind it: with border-collapse, borders don't travel with sticky cells, and sticky right-0 can leave a subpixel seam.
  • a:visited { color: inherit } outranks Tailwind text colors on specificity, so solid-dark links lost their light text once visited — hit the active pagination link, the active record-type pill, and the "Manage account" button.
  • ARK column misalignment: the header rendered based on the first record only, so a later row with an ARK shifted every column.
  • Share tokens weren't forwarded by the record and schema permalink loaders, dead-ending shared-link viewers who followed provenance.
  • Version and schema provenance counted (collection, version) rows as collections — "appears in 11 collections" for a record in one collection across 11 versions.
  • The Versions page reported versions.length as the total, so any collection past 100 read "100 versions."
  • Diff defaulted to comparing latest against the empty set, reporting every record as added.
  • "Leave this organization" was an unconfirmed bare link while every other destructive action required typed confirmation.

Accessibility and responsive

focus-visible had zero occurrences app-wide; there's now a universal focus ring. Added prefers-reduced-motion (including an explicit guard for the landing hero's requestAnimationFrame loop, which CSS can't stop) and a shared useDismissable hook so dropdowns close on Escape and return focus. The records and overview grids had no breakpoint variants and overflowed on phones; eight table wrappers clipped instead of scrolling.

Notes for review

  • Behavior-affecting changes are limited to: auth moving to middleware on org/collection settings, the URL restructure (with redirects), and the dashboard's single-query loader. Everything else is presentation.
  • Semantic color is deliberately preserved where it carries meaning — diff added/updated/removed headers, expired-key row tints, API response status.
  • llms.txt gained a "Web URLs" section; it documented the API exhaustively but never told an agent how to link a human to a view.
  • QueryExplorer's bespoke dense mono scale was left un-swept on purpose — moving it onto the standard button sizing would visibly change that tool's density.

Verification

pnpm typecheck, pnpm lint, pnpm test (97 passing), and pnpm build all pass. Every surface was checked in the browser, including legacy-URL redirects (curl -I on each old shape) and the sticky-column behavior mid-scroll.

Responsive: the records type-nav grid and the overview sidebar grid had no
breakpoint variants, so both overflowed on phones. They now stack, with the
type nav becoming a horizontally scrolling row. The collection tab bar and
version info bar scroll/wrap rather than overflow.

Tables: wrappers on the landing page, owner page, record and schema detail,
and version compare used overflow-hidden, which clipped wide content instead
of scrolling it. All now use overflow-x-auto (pre blocks left alone — they
scroll internally).

Accessibility: add a prefers-reduced-motion block, and a shared useDismissable
hook so UserMenu, CreateMenu, and the version picker all close on Escape and
return focus to their trigger. Share dialogs close on Escape and carry
role="dialog"/aria-modal.

Share tokens: the /schemas/:id loader fetched without forwarding ?token, so a
shared-link viewer following a schema hash out of a private collection hit a
404. It now uses apiUrlBuilder like its sibling routes.

llms.txt: document the web URL scheme (version prefix + view segment, bare
semver) so agents can link people to a view, not just fetch from the API.
The primitives added earlier only covered the pages rebuilt at the time, so
roughly two thirds of the app still carried the original drift. This applies
them across the rest: landing, explore, owner profile, protocol, invitations,
collection surfaces, record and schema detail, and the steward-only admin
tools.

Radius is now fully tokenized: 99 bare rounded/rounded-sm/rounded-lg classes
are gone, leaving rounded-control (4px) on things people touch,
rounded-surface (2px) on containers, and rounded-full only on avatars, status
dots, and progress bars. MirrorAdmin's rounded-lg panels were the app's only
instance of that radius.

Buttons collapse from 16 hand-inlined copies and 5 competing hover treatments
to the shared variants; several gained real disabled states they were missing
despite being disabled. Alerts, inputs, selects, textareas, tables, badges,
and empty states move onto the primitives. Semantic colors stay where they
carry meaning: diff added/updated/removed headers, expired-key row tints, and
API response status.

Primitives gained what the sweep proved they needed: an lg button size for
landing CTAs, a success variant for approve actions (three sites were
hand-rolling green buttons), and a resize prop on Textarea.

Fixes found along the way: the active pagination link, the active record-type
pill, and the "Manage account" button all lost their light text once visited,
because the global a:visited{color:inherit} rule outranks .text-parchment.
QueryExplorer's sticky table header lost its bottom rule when the body
scrolled, since a border on a tr does not travel with a sticky header. The
landing hero's requestAnimationFrame animation now honors prefers-reduced-
motion, which CSS alone cannot stop. Docs code blocks and the prose styles use
the radius tokens instead of hardcoded 3px/4px.
@isTravis
isTravis merged commit 4378a26 into main Aug 7, 2026
1 check passed
@isTravis
isTravis deleted the tr/ui-polish-26-08 branch August 7, 2026 02:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant