fix(admin): replace the profiles card grid with a list + detail pane #58

Merged
alee merged 2 commits from fix/admin-profiles-header-cleanup into main 2026-09-08 03:56:50 +00:00

2 Commits

Author SHA1 Message Date
adlee-was-taken
076bf2eef3 feat(admin): edit profiles inline in the detail pane
Editing a profile no longer opens a dialog stacked on top of the record you
are reading -- the pane itself becomes the form. Duplicate and delete still
use their modals, because both act on a record other than the one on screen.

The modal's field set is reused rather than duplicated:
collectProfileBody() now takes an id prefix ("profile-" for the modal,
"inline-profile-" for the pane) and loadCatalogModels() takes a target
select, so both editors build the same request body and cannot drift apart.

Two things found while testing this against the live API:

- The update endpoint CANNOT rename. _ProfileUpdateBody has no name field
  and _profile_block_dict() does exclude={"name"}, so a submitted name is
  silently dropped and the profile keeps its old one. The inline name field
  is therefore readonly, and says why plus what to do instead (duplicate
  under the new name, then delete). The existing modal still presents an
  editable name box on edit that silently does nothing -- pre-existing, not
  touched here.
- Selection deliberately stays on the original name after a save rather
  than following the submitted one, which would land the pane on a profile
  that does not exist.

A refresh will not clobber a half-filled form: renderProfiles() re-renders
the list but leaves the pane alone while editing. Switching profiles
abandons the edit rather than carrying the form to another record.

Verified end to end against the real API on a throwaway 8081 instance,
writing to this worktree's own gitignored overlay -- confirmed by checksum
that the machine's real config.local.yaml was untouched throughout. Save
persisted latency_tolerance and max_cost changes, the restart hint appears,
and the scratch profile was deleted afterwards. Full suite 1521 passed.
2026-09-07 23:56:09 -04:00
adlee-was-taken
de4b84859d fix(admin): replace the profiles card grid with a list + detail pane
The card shape was the problem, not the styling on it. A ~340px card had to
hold a name, an icon, admitted/interactive counts, a source badge, a
read-only hint and up to three action buttons, and no amount of spacing kept
those six from colliding -- three rounds of polish (#56 plus two follow-ups)
each moved the collision somewhere else rather than removing it.

A list plus a detail pane removes the constraint instead of working around
it: the list carries one line per profile, and the pane has the full width,
so nothing competes for the same row.

- Master list: per-filter icon, name, and a muted one-line summary derived
  from the same precedence profileIcon() uses, so the icon and the line
  never describe two different things. Zero-admit rows keep a warning flag;
  built-ins show a lock, which now reads as contrast against config
  profiles rather than noise on every row.
- Detail pane: name, source and read-only state, the counts as labelled
  figures rather than headline numbers (what distinguishes a profile is its
  filters, not how many models it happens to admit), the full filter set as
  aligned rows, the category-coverage line when gated, and the actions in
  one consistent place.
- Long snake_case keys no longer split mid-word: breakableLabel() adds
  <wbr> after each underscore, and the label column is fixed-width, so
  max_cost_per_1m_completion fits on one line instead of wrapping to
  "max_cost_per_1m_completio" / "n".
- Fixed 4 mojibake sequences (a triple-encoded em-dash, shipped in #56)
  by byte-level replacement, and kept this file's own punctuation ASCII.

All CRUD is untouched and reused: both modals, openEditModal,
openDuplicateModal, promptDelete, saveProfile and the rest. The actions
ternary keeps its exact shape, so the built-in duplicate-only rule and the
test that pins it are unchanged.

Verified live against the real catalog on a throwaway 8081 instance:
selection, the duplicate modal opening pre-filled from a real click through
the delegated listener, and the config-profile branch rendering all three
actions. Full suite 1521 passed.
2026-09-07 23:45:55 -04:00