From de4b84859d23b53535630a2c387869af7e8f8418 Mon Sep 17 00:00:00 2001 From: adlee-was-taken Date: Mon, 7 Sep 2026 23:45:55 -0400 Subject: [PATCH 1/2] 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 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. --- admin/frontend/profiles.html | 352 ++++++++++++++++++++++++----------- 1 file changed, 246 insertions(+), 106 deletions(-) diff --git a/admin/frontend/profiles.html b/admin/frontend/profiles.html index 3b128f7..f8fedfd 100644 --- a/admin/frontend/profiles.html +++ b/admin/frontend/profiles.html @@ -86,27 +86,69 @@ header.navbar{padding-top:2px!important;padding-bottom:2px!important} @media (prefers-reduced-motion: reduce){ .navbar-brand a:hover .brand-speedlines line{animation:none} } -/* ── Profile card specifics ── */ -/* Admitted/interactive counts -- compact badge in card header */ +/* -- Profiles: a list plus a detail pane, NOT a grid of cards. + Cards were removed deliberately. 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 -- each fix just moved the collision. The list column carries + one line per profile; the detail pane gets the full width, so nothing + competes for the same row. -- */ +/* Both panes stretch to a shared height so this reads as a two-pane layout + rather than two boxes floating in an empty page -- with five profiles the + content alone would fill about a quarter of a tall viewport. */ +#profiles-layout{display:grid;grid-template-columns:minmax(230px,270px) 1fr;gap:1.25rem; + align-items:stretch;max-width:1180px;margin:0 auto;min-height:min(62vh,560px)} +@media (max-width:767.98px){#profiles-layout{grid-template-columns:1fr;min-height:0}} + +/* -- Master: the profile list -- */ +#profile-list{display:flex;flex-direction:column;gap:1px;padding:6px;border-radius:10px; + background:rgba(31,41,55,.55);backdrop-filter:blur(18px) saturate(140%);-webkit-backdrop-filter:blur(18px) saturate(140%); + border:1px solid rgba(255,255,255,.07);box-shadow:0 10px 30px -14px rgba(0,0,0,.55),inset 0 1px 0 rgba(255,255,255,.04)} +.profile-list-item{display:flex;align-items:center;gap:.55rem;width:100%;text-align:left; + padding:.5rem .6rem;border:0;border-radius:7px;background:transparent;color:inherit;cursor:pointer;transition:background .12s ease} +.profile-list-item:hover{background:rgba(255,255,255,.05)} +.profile-list-item.active{background:rgba(255,255,255,.10);box-shadow:inset 2px 0 0 rgba(96,165,250,.85)} +.profile-list-item>[data-icon]{flex:0 0 auto;opacity:.75;display:inline-flex} +.profile-list-item.active>[data-icon]{opacity:1;color:#93c5fd} +.profile-list-text{min-width:0;display:flex;flex-direction:column;line-height:1.25} +.profile-list-name{font-weight:600;font-size:.9rem;white-space:nowrap;overflow:hidden;text-overflow:ellipsis} +.profile-list-sub{font-size:.72rem;color:var(--tblr-secondary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis} +.profile-list-flag{flex:0 0 auto;margin-left:auto;display:inline-flex;opacity:.55} + +/* -- Detail pane -- */ +#profile-detail{border-radius:10px;padding:1.35rem 1.5rem; + background:rgba(31,41,55,.55);backdrop-filter:blur(18px) saturate(140%);-webkit-backdrop-filter:blur(18px) saturate(140%); + border:1px solid rgba(255,255,255,.07);box-shadow:0 10px 30px -14px rgba(0,0,0,.55),inset 0 1px 0 rgba(255,255,255,.04)} +.profile-detail-head{display:flex;align-items:flex-start;justify-content:space-between;gap:1rem;flex-wrap:wrap} +.profile-detail-title{display:flex;align-items:center;gap:.6rem;min-width:0} +.profile-detail-title h2{margin:0;font-size:1.35rem;font-weight:600;letter-spacing:-.01em} +.profile-detail-title>[data-icon]{display:inline-flex;opacity:.8;color:#93c5fd} +.profile-detail-meta{display:flex;align-items:center;flex-wrap:wrap;gap:.5rem;margin-top:.4rem} +.profile-detail-section{margin-top:1.5rem} +.profile-detail-section-label{font-size:.7rem;font-weight:700;letter-spacing:.08em;text-transform:uppercase; + color:var(--tblr-secondary);opacity:.75;margin-bottom:.6rem} +/* Stats read as labelled figures, not headline numbers -- what distinguishes + a profile is its filters, so the counts stay secondary. */ +.profile-figures{display:flex;gap:2.25rem;flex-wrap:wrap} +.profile-figure-val{font-size:1.5rem;font-weight:600;font-variant-numeric:tabular-nums;line-height:1.1} +.profile-figure-label{font-size:.72rem;color:var(--tblr-secondary);margin-top:.15rem} +/* Definition rows: fixed label column, so long snake_case keys never push + into the value. from breakableLabel() handles the wrap points. */ +.profile-def-row{display:grid;grid-template-columns:minmax(0,15rem) 1fr;gap:.75rem; + padding:.5rem 0;border-top:1px solid rgba(255,255,255,.05)} +.profile-def-row:first-of-type{border-top:0} +.profile-field-label{color:var(--tblr-secondary);font-size:.82rem;font-weight:600;letter-spacing:.01em;overflow-wrap:break-word} +.profile-field-val{color:rgba(255,255,255,.88);font-size:.92rem;overflow-wrap:break-word} .profile-admit-badge{font-variant-numeric:tabular-nums;white-space:nowrap;font-size:.82rem} -/* Promoted definition rows -- larger/brighter than the old .78rem muted styling */ -.profile-field-label{color:var(--tblr-secondary);font-size:.8rem;font-weight:600;letter-spacing:.01em} -.profile-field-val{color:rgba(255,255,255,.8);font-size:.92rem} -.profile-zero-alert{margin-left:auto} -/* Coverage note -- compact key:value row */ -.profile-coverage{font-size:.78rem;color:var(--tblr-secondary);margin-bottom:.5rem;line-height:1.3} -.profile-coverage .profile-field-label{font-size:.76rem;font-weight:500;letter-spacing:normal} -.profile-coverage .profile-field-val{font-size:.8rem;color:var(--tblr-secondary)} .profile-coverage-info{display:inline-flex;align-items:center;margin-left:4px;opacity:.6;cursor:help;vertical-align:middle} .profile-coverage-info:hover{opacity:1} .empty{color:var(--tblr-secondary);text-align:center;padding:2rem 1rem} -/* ── Read-only lock hint on built-in cards ── */ .profile-readonly-hint{color:var(--tblr-secondary);font-size:.72rem;display:inline-flex;align-items:center;gap:4px} -/* ── Card header actions ── */ -.profile-actions{display:flex;align-items:center;gap:6px} -/* ── Profile grid: CSS auto-fill so 5 cards don't leave orphans ── */ -#profile-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(340px,1fr));gap:1rem;max-width:1200px;margin-left:auto!important;margin-right:auto!important} -#profile-grid>.col-12{grid-column:1/-1} +.profile-actions{display:flex;align-items:center;gap:6px;flex:0 0 auto} +/* Icon + label inside an action button need their own gap -- the inline SVG + sits flush against the text otherwise ("[icon]Duplicate"). */ +.profile-actions .btn{display:inline-flex;align-items:center;gap:.4rem} +.profile-actions .btn>[data-icon]{display:inline-flex} /* ── Modal glass treatment ── */ .modal-content{ background: rgba(31,41,55,.75); @@ -191,15 +233,12 @@ header.navbar{padding-top:2px!important;padding-bottom:2px!important}
-
-
-
-
-
Loading…
-
-
-
-
+
+ +
+
Loading…
+
+