# 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 `