fix(admin): replace the profiles card grid with a list + detail pane #58
Reference in New Issue
Block a user
Delete Branch "fix/admin-profiles-header-cleanup"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Why
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. 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: the list carries one line per profile, the pane has the full width, nothing competes for the same row.
What
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.breakableLabel()adds<wbr>after each underscore plus a fixed-width label column, somax_cost_per_1m_completionfits on one line instead of wrapping tomax_cost_per_1m_completio/n.What is untouched
All CRUD is reused as-is: both modals,
openEditModal,openDuplicateModal,promptDelete,saveProfile,collectProfileBody. Theconst actions = isBuiltin ? ...ternary keeps its exact shape, so the built-in duplicate-only rule and the test pinning it are unchanged. Editing still goes through the existing modal -- inline editing in the pane is a clean follow-up, deliberately not bundled here.Verification
Live against the real catalog on a throwaway 8081 instance (never 8080):
Duplicate default/default-copy).max_cost_per_1m_completionon one line.Full suite: 1521 passed. Note the 9 failures seen on other branches do not occur here -- this worktree has no
config.local.yamloverlay, confirming they are overlay artifacts (plans/pending-ops-fixes.md#5), not real failures.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.