# Objects Data-Overview Table + Responsive Shell — Design **Date:** 2026-06-06 **Status:** Approved (brainstorming) — ready for implementation planning. **Issues:** #44 (object list → table); subsumes #58 (responsive layout) for the shell. ## Context The Objects screen is where curators triage hundreds of records daily, but today `web/src/objects/object-list.tsx` renders a **thin 20rem list** (object_number + name + visibility badge) inside a master/detail grid, with no columns, sort, or filter. The backend `GET /api/admin/objects` (`list_objects_paged`) takes only `limit`/`offset` and orders by `object_number`. A *separate* `search-panel.tsx` (Meilisearch full-text, infinite scroll, visibility filter) is a parallel browse UI with different ergonomics. Goal: a real, scannable, sortable, filterable **data-overview table** plus a shell that adapts to viewport width and gives every object a shareable URL. ### Facts established during exploration - **Timestamps already exist.** The `object` table has `created_at` + `updated_at` (`migrations/0003_object.sql`); `updated_at` is set to `now()` on every write; the db layer already reads them into `CatalogueObject`. They are simply **not exposed in `AdminObjectView`** — so adding an "Updated" column needs *no migration*, just two fields on `AdminObjectView::from_object`. - **Search is best-effort/optional** (`AppState.search: None` → the search endpoint 503s). So the **Postgres-backed list must remain the always-available browse surface**; full-text search is a layer on top, not a replacement. - **No new dependencies needed:** `lucide-react` is already installed (nav icons); Base UI ships `drawer`, `collapsible`, and `tooltip` primitives (the slide-in detail + sidebar). ### Decisions (from brainstorming) 1. **Layout:** a Linear/email-style shell — collapsible **icon sidebar**; a **full-width objects table** as the overview; selecting a row opens detail as a **right-hand pane on wide viewports / a slide-in drawer when narrow**; **`/objects/:id` is a canonical, shareable URL**. 2. **Search:** **table-first.** The table gets Postgres-backed sort + visibility filter + a quick text filter (object number/name). The dedicated Meilisearch Search screen stays as-is; folding full-text into the table's search box is a **deferred follow-up**. 3. One milestone, **built in phases** (backend → table → shell/responsive/detail). 4. **Storybook** stories for meaningful new components (per the standing preference). ## 1. Shell: collapsible icon sidebar + responsive frame `web/src/shell/app-shell.tsx`: - The sidebar gains a **collapse toggle**; expanded = `w-44` (icon + label), collapsed = an icon rail (`~w-14`, icon-only). State persisted in `localStorage` (e.g. `sidebar-collapsed`). - Each nav item (`objects`, `vocabularies`, `authorities`, `search`, `fields`) gets a `lucide-react` icon. When collapsed, the label is shown via a Base UI **`Tooltip`** on hover and as the `aria-label`/`title` for AT. - Below a width breakpoint the sidebar **auto-collapses** to the rail (the user can still toggle). Nav `NavLink` active state + focus-visible rings preserved/added. - This resolves #58 at the shell level (the per-screen master/detail responsiveness is handled in §3). ## 2. Objects table (`/objects`) Replace the narrow list with a **full-width table** filling the main content area. **Columns (default):** Object № (sortable) · Name (sortable) · Visibility (badge; filterable) · Current location · # objects · Updated (sortable). Real `` semantics with `scope="col"` headers and `aria-sort` on the active sort column. **Toolbar (above the table):** - A debounced **quick text filter** (`q`) — Postgres `ILIKE` on `object_number` + `object_name` (always available; distinct from the Meili Search screen which searches descriptions/fields). - **Visibility filter chips** (`all` / `draft` / `internal` / `public`), mirroring the search panel's pattern (honest `