Skip to content

Feat(GUI3): master-detail model view, remove preset rails (#2355 Slice 1) - #2424

Merged
fl0rianr merged 8 commits into
kpoin/ui-testingfrom
feat/gui3-model-detail-redesign
Jun 25, 2026
Merged

fl0rianr merged 8 commits into
kpoin/ui-testingfrom
feat/gui3-model-detail-redesign

Conversation

@kpoineal

Copy link
Copy Markdown
Collaborator

Summary

Implements Slice 1 of the master-detail model view (#2355). Replaces the old preset-rail model/preset flow with an email-client-style split layout in prototype/ui-redesign/.

Part of #2355

What shipped in Slice 1

Left panel — ModelListPanel

  • Searchable, filterable role=listbox with compact model rows (name, size, backend badge, downloaded dot, capability badge)
  • Funnel filter button (the ONLY new icon — 'funnel' added to Icon.tsx) with role=dialog popover
  • Keyboard nav: ArrowUp/Down/Home/End on the listbox moves focus AND selection (ARIA APG single-select)
  • Visible selected state (aria-selected="true" + .model-list-item--selected)
  • + Custom model / + Omni collection buttons in footer — wired to existing openCustomForm() in ModelManager

Right panel — ModelDetailPanel

  • Title, key metadata, primary actions (Load/Unload/Download/Delete/CancelPull) with model-qualified aria-labels
  • role=tablist with tabs: README (fetch + markdown-it + DOMPurify), Presets (loadApplied/saveApplied via presetStore.ts), Files (stub)
  • ArrowLeft/Right/Home/End tab keyboard navigation

Preset linking (Presets tab)

  • Shows currently-linked preset (aria-current="true") and compatible presets
  • Attach/detach actions via client-local presetStore.ts (localStorage — never in lemond)
  • role=status aria-live=polite attach confirmation

Preset rail removed

  • renderPresetRail() (~2344-2395) removed per plan
  • Rail state vars (presetRailCollapsed, selectedRailPresetId, etc.) removed
  • Custom-model form re-wired as conditional overlay in the detail slot

Accessibility (WCAG 2.1 AA — non-negotiable)

All keyboard and screen-reader patterns are implemented: searchbox label, listbox/option ARIA, funnel popover, tablist/tab/tabpanel, attach action, aria-live regions.

Tests: 121 passed / 7 skipped / 0 failed

  • A91–A105 (15 new tests): layout, search, funnel, listbox/option ARIA, keyboard nav, tablist, Presets tab, custom-model buttons, full axe-core scan with mock data
  • Updated A46–A50, A71–A74, A79 to match new interaction model
  • Updated features test 20 (HF zone removed in new layout)

Files changed

File Change
src/components/ModelDetailPanel.tsx New — detail panel with tablist
src/components/ModelListPanel.tsx New — searchable listbox with funnel
src/components/ModelManager.tsx Remove preset rail, wire new panels
src/components/Icon.tsx Add 'funnel' icon
src/styles/styles.css New component styles (~400 lines)
tests/a11y.spec.ts A91–A105 + A46–A50 updates
tests/features.spec.ts Test 20 update
ACCESSIBILITY.md Group E section, count 121/7/0

Deferred to follow-up slices

  • Files tab (stub only — marked in UI)
  • Full recommended-preset card polish
  • Sorting refinements (alphabetical, popularity, etc.)
  • a11y: Model preset update while loaded #2356 (Update-preset-while-loaded — builds on this detail panel)

…e 1)

- Add ModelListPanel: searchable, filterable listbox with funnel filter,
  compact model rows, keyboard nav (Arrow/Home/End), visible selected state.
  The only new icon is 'funnel' added to Icon.tsx.
- Add ModelDetailPanel: title, key metadata, primary actions (Load/Unload/
  Download/Delete/CancelPull), role=tablist with README (markdown-it +
  DOMPurify), Presets (loadApplied/saveApplied via presetStore.ts), and
  Files (stub, deferred).
- Remove renderPresetRail() and all preset-rail state from ModelManager.tsx
  (presetRailCollapsed, selectedRailPresetId, etc.).
- Wire custom-model / omni-collection form back via ModelListPanel footer
  buttons and conditional overlay panel in the detail slot.
- Fix WCAG scrollable-region-focusable: listbox gets tabIndex=0 when no
  item is selected; data-model-id attribute enables arrow-key selection.
- Update tests/features.spec.ts test 20 for new layout (HF zone removed).
- Update tests/a11y.spec.ts A46-A50 for master-detail interaction model.
- Add A91-A105 covering: layout, search, funnel filter, listbox/option ARIA,
  keyboard nav, tablist structure, Presets tab, custom-model buttons, axe scan.
- Update ACCESSIBILITY.md: Group E, bump test count to 121/7-skipped/0-failed.

Deferred to follow-ups: Files tab, full preset-card polish, sorting, #2356.

Part of #2355

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions github-actions Bot added app enhancement New feature or request labels Jun 25, 2026
@kpoineal kpoineal added the squad:mattingly Assigned to ⚛️ Mattingly (UI / Frontend) label Jun 25, 2026
@kpoineal kpoineal added this to the GUI3 milestone Jun 25, 2026
A: README checkpoint derivation — deriveHFRepo() tries model.checkpoint,
   model.checkpoints.main, then first checkpoints value; strips :variant
   suffix; validates with /^[\w.-]+\/[\w.-]+\$/ before fetching. HF link
   also uses deriveHFRepo. ModelReadmeTab now takes model prop.

B: Sort controls — labeled <select id='model-list-sort'> in ModelListPanel
   with Name (A-Z, default), Size, Last used, Download count. Graceful
   fallback to name when last_used/downloads data absent.

C: Responsive list-first — at max-width:700px, manager--detail-mobile-open
   class toggles list/detail panels exclusively (not stacked). Back button
   (model-detail-panel__back-btn) with aria-label injected via onBack prop.
   Focus: detail heading on open, selected list item on Back.

D: Presets tab Change — Change button (aria-haspopup=dialog, aria-expanded)
   on linked preset card opens inline role=dialog chooser listing compatible
   presets excluding current. Attach buttons in compatible list relabelled
   to Switch when a non-default preset is already linked.

Tests: A106-A115 (10 new); full suite 131 passed / 7 skipped / 0 failed.
ACCESSIBILITY.md: Group F added, test count updated.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@kpoineal

Copy link
Copy Markdown
Collaborator Author

Reconciliation complete — fl0rianr 2026-06-25 clarifications

This commit closes all 4 gaps from @fl0rianr's 6-point clarification on #2355.

What changed

A — README checkpoint derivation (tightened)

  • hfReadmeUrl replaced with deriveHFRepo(checkpoint, checkpoints). Tries model.checkpoint → model.checkpoints.main → first value in model.checkpoints map. Strips :variant suffix, validates against regex ^[\w.-]+/[\w.-]+$ before any fetch attempt. Non-matching → placeholder, no network call. HF header link also uses the new helper. ModelReadmeTab now takes the full model prop; cache key is the derived URL.

B — Sort controls (added)

  • Labeled in ModelListPanel with four options: Name (A–Z, default), Size (largest first), Last used, Download count. Default (Name) keeps the running→downloaded→available status-group ordering. Size/Last used/Downloads gracefully fall back to name when the respective data fields are absent — no crash, no frozen UI. C — Responsive list-first (added) At ≤700px, the .manager--detail container toggling .manager--detail-mobile-open shows detail-only (not stacked). A is rendered inside ModelDetailPanel via the new onBack prop. Focus moves to the detail heading on model selection; Back returns focus to the [data-model-id] list item. D — Presets tab Change inline chooser (added) Non-default linked preset card now shows a Change button (�ria-haspopup="dialog", �ria-expanded) alongside Reset to default. Clicking opens an inline ole="dialog" chooser listing compatible presets (excluding the current). Selecting one calls handleAttach and closes the chooser, returning focus to the Change button. Compatible presets list relabels "Attach" → "Switch" when a non-default preset is already linked (points 5 and 6 of fl0rianr's clarification on deduplication of change path). Tests 131 passed / 7 skipped / 0 failed. New: A106–A115 (10 tests covering sort control label/options/keyboard, narrow-viewport list-first/back, and preset Change ARIA + chooser open/close).

…trip) (#2355)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@kpoineal

Copy link
Copy Markdown
Collaborator Author

README tab now renders raw HTML correctly (root cause + fix)

Root cause: The GUI3 model README tab built its markdown-it instance with { html: false } (ModelDetailPanel.tsx). With html:false, markdown-it escapes any raw HTML embedded in the source — and Hugging Face model READMEs are full of it (<div align="center">, <img> badges, <p>, tables). So the user saw literal <div ...> tags as text instead of formatted content.

Fix:

  1. Flipped the README markdown-it instance to { html: true }. This is safe: the rendered output already passes through the strict README_PURIFY_CONFIG DOMPurify allowlist (no script/style/iframe/object/form/event-handler attrs) before dangerouslySetInnerHTML injection — the exact render→sanitize pattern used in MarkdownMessage.tsx.
  2. Added a defensive stripFrontmatter() helper: HF READMEs open with a YAML frontmatter block (--- … ---); with html:true that would otherwise render as a stray <hr> + dumped key/value text. We strip only a well-formed leading block; never throws.
  3. Conservatively widened the DOMPurify allowlist with common safe tags/attrs (tfoot, caption, colgroup, col, picture, source, sup, sub, kbd, samp, var; srcset, align, colspan, rowspan). No script-enabling tags or on*/style attrs.

Tests: Added A116 (raw HTML renders as real DOM elements div[align]/strong/img, and the page does NOT contain literal <div/&lt;div) and A117 (leading YAML frontmatter is stripped). HF README fetch is mocked via Playwright route. Full suite green: 133 passed / 7 skipped / 0 failed.

@fl0rianr thank you for the screenshot — that made the repro instant. Could you pull feat/gui3-model-detail-redesign and re-verify the README tab renders formatted content now? 🙏

fl0rianr follow-up: the master-detail ModelListPanel dropped the original
rail's pin/favorite affordance. Re-wire the existing client-local pin store
(scoped `pinned_models` localStorage, no lemond) into ModelListPanel so pinned
models float to the top.

A11y: the per-row pin is a non-button span (avoids axe nested-interactive
inside role="option"); keyboard/AT users toggle via aria-keyshortcuts="P" on
the focusable selected row; pinned state is exposed in the row aria-label.

Tests A118-A123 added; full suite 139 passed / 7 skipped / 0 failed.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@kpoineal

Copy link
Copy Markdown
Collaborator Author

Round update — left-rail parity (pin/favorite) + clarification on #2355 feedback

@fl0rianr Following your latest #2355 feedback ("real left rail still missing its features" + "presets still in cards layout"), here's what shipped this round and what I need from you.

Shipped (this commit):

  • Client-local pin / favorite restored to the model rail. The master-detail ModelListPanel had dropped the original rail's pin affordance; I re-wired the existing client-local pin store (scoped pinned_models localStorage — no lemond, per the per-client-state invariant). Pinned models float to the top while keeping the chosen sort order.
  • A11y: the per-row pin is a non-button <span> (so it doesn't nest an interactive control inside role="option" — avoids axe nested-interactive); keyboard/AT users toggle via aria-keyshortcuts="P" on the focusable selected row; pinned state is exposed in each row's aria-label.
  • Tests: added A118–A123. Full suite 139 passed / 7 skipped / 0 failed. ACCESSIBILITY.md updated (Group H).

The new rail already carries search (Ctrl+K), type filter (funnel), sort (name/size/last-used/downloads), per-row status (running pulse / downloaded dot / live %), size badge, backend chip, capability icon, live count, and full keyboard nav.

Open clarifications (posted in detail on #2355, link):

  1. Rail: do you want the explicit Downloaded-vs-Available section headers back (the new panel uses a sort-based single list), and should per-row quick actions (load/unload/delete) live on rows or stay in the detail panel? BackendManager rail stays out per your clarification Please support non-english system language #4.
  2. Presets: your "still in cards layout" note is ambiguous to me — is it (a) the model-detail Presets tab (and what should change), or (b) the global Presets page cards (intentionally out of a11y: remove preset rails and introduce a completely new and elegant model view concept #2355 scope per clarification Support for FIM completions #3)? A one-line either/or gets me unblocked.

Holding the larger rail items until you confirm — didn't want to over-build on a guess.

@fl0rianr

Copy link
Copy Markdown
Collaborator

Still far away form the intended style.
image
The middle rail (currently on the left) is fine, but the left one is missing. It has many features like the storage indication. It's crucial.

The presets are not neat cards a slightly smaller, focused version of the cards in the global preset view.

Builds the missing LEFT navigation rail (ModelNavRail) to match fl0rianr's canonical 3-pane target: left nav rail + existing ModelListPanel (middle) + ModelDetailPanel (right).

Left rail (client-side derived, no lemond):
- Primary nav: All Models / Downloaded / My Models / Favorites with counts + aria-current; filters the middle list.
- Collapsible Categories (All/LLM/Omni/Image/Audio/TTS/Embed) reusing the existing filterTab state.
- Backends select (distinct recipes + counts) filters by recipe.
- Collapsible Tags (model-family + size chips) with aria-pressed; filters the list.
- Storage meter as role=progressbar; used derived from downloaded sizes, total is MOCK (no client disk-usage source, lemond off-limits).

Also per fl0rianr:
- Status stays a simple dot (no separate Downloaded badge).
- Moved + Custom model / + Omni collection buttons from the bottom footer to a grounded button group with background at the TOP of the list area.
- Presets remain in the model-detail Presets tab ('2) a'); global Presets page untouched.

Shared filter predicates exported from ModelListPanel are the single source of truth for both the middle-list filtering and the rail counts. Responsive: rail collapses behind a keyboard-reachable Filters toggle at <=700px.

Tests: A124-A136 (13 new) in tests/a11y.spec.ts. Full suite 152 passed / 7 skipped / 0 failed. ACCESSIBILITY.md Group G added.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@kpoineal

Copy link
Copy Markdown
Collaborator Author

GUI3 left navigation rail — three-pane model view shipped (PR #2424)

@fl0rianr I built the missing left navigation rail to match your canonical 3-pane target screenshot. The middle (ModelListPanel) and right (ModelDetailPanel) panes are untouched in layout, as you asked.

Left rail (top → bottom), all derived CLIENT-SIDE from the model list already loaded — lemond was NOT touched:

  • "Search presets…" + "+ New" at the top.
  • Primary nav with right-aligned counts: All Models / Downloaded / My Models / Favorites — selecting one filters the middle list (active = aria-current).
  • CATEGORIES collapsible (All / LLM / Omni / Image / Audio / TTS / Embed), reusing the existing list filter state.
  • Backends select (distinct recipes + counts) — filters by backend.
  • TAGS collapsible chips (Llama, Qwen, Phi, Mistral, Gemma, Bonsai, Small, + Recommended +) — filters the list.
  • Storage meter at the bottom (128 GB / 512 GB + progress bar).

Your specific notes applied:

  • ✅ Status stays a simple dot — no separate "Downloaded" text badge.
  • ✅ Moved + Custom model / + Omni collection from the bottom to a grounded button group with a background at the top of the list area.
  • ✅ Presets kept in the model-detail Presets tab per your "2) a" — global Presets page untouched.

MOCK data note: there's no client-available disk-usage source and lemond is off-limits, so the Storage total is a mock placeholder (512 GB); used is derived from downloaded model sizes when present. Everything else (counts/categories/backends/tags) is real, derived from the model list.

Tests: added A124–A136 (13 new a11y tests). Full suite 152 passed / 7 skipped / 0 failed. WCAG 2.1 AA: keyboard nav, aria-current/aria-pressed/aria-expanded, Storage role="progressbar", counts paired with sr-only text. Responsive: rail collapses behind a keyboard-reachable Filters toggle at ≤700px.

One question for you: the rail's top "Search presets…" + "+ New" — in your screenshot it sits above the model nav. Since presets now live in the model-detail Presets tab, I shipped these as accessible POC placeholders (labelled input + aria-labeled button) and did not wire them to a global preset action. Want them wired to a global preset quick-create, or should they filter something specific? Otherwise everything maps to existing client-side data without needing lemond.

Please verify against your target screenshot 🙏

#2424)

fl0rianr feedback: the model-detail Presets tab rendered linked + compatible presets as full-width stacked rows. Restyle so the linked preset is a single highlighted card and recommended presets render as a neat responsive grid of compact cards (icon + name, short description, compact temp/ctx/tools meta, Attach/Switch action) consistent with the global preset (.recipe-card) style but smaller and focused.

a11y: grid is a ul role=list of li cards (not role=listbox/option) to avoid nested-interactive with per-card Attach buttons; linked/active state via aria-current + text badges (not color only); each Attach/Switch button names its preset + model; inline Change dialog preserved; Browse/Manage Presets deep-link via client-local lemonade:navigate event. Tests A137-A141; 157 passed / 7 skipped / 0 failed.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@kpoineal

Copy link
Copy Markdown
Collaborator Author

Model-detail Presets tab restyled — rows → neat compact card grid

@fl0rianr — addressed your 18:12Z note: "The presets are not neat cards — a slightly smaller, focused version of the cards in the global preset view."

Before: Linked + Compatible presets rendered as full-width stacked rows (Quality / Preview / Turbo with a Switch button each).

After:

  • Linked preset now sits above as a single highlighted card (Active badge, Change + Reset-to-default actions).
  • Recommended presets render as a neat responsive grid of compact cards — icon + name, 1–2 line description, a compact meta line (temp · ctx · tools for LLMs / steps · cfg for image models), and an Attach/Switch action.
  • Styled as a slightly smaller, focused version of the global Presets-page cards (.recipe-card) for visual consistency — the global Presets page itself is untouched.
  • Added a Browse / Manage presets deep-link back to the global Presets view (client-local lemonade:navigate event, no lemond involvement).

Behavior preserved: inline role="dialog" Change chooser, Attach/Switch wiring, Reset to default, model-context scope (linked + recommended only — no full global editor here).

a11y: grid is a <ul role="list"> of <li> cards (not role="listbox"/option) so the per-card Attach button doesn't trip nested-interactive; linked/active state exposed via aria-current + visible text badges (not color alone); each Attach/Switch button names its preset + model; cards wrap/grid down gracefully on narrow widths. ACCESSIBILITY.md updated (Group H).

Tests: added A137–A141 and updated the prior Presets-tab assertions. Full Playwright suite: 157 passed / 7 skipped / 0 failed.

Could you verify against your target screenshot (the "Recommended Presets" grid in the 3-pane mock)? One check for you: I matched the global .recipe-card (Presets page / PresetManager.tsx) as the canonical card style — if you consider a different component canonical, let me know and I'll align to that instead.

@fl0rianr

Copy link
Copy Markdown
Collaborator

This is a good update. Here are some detailed improvement requests:

  • favorites need the star icon and we need the option to declare a model as a favorite (ok to do it in the detail section)
  • remove "search preset" and "new" preset from the left rail
  • for desktop "Back to models" ist not needed
  • with the funnel we should filter now only for capabilities, also make the background of the opened field non transparent
  • There are some settings combos in the left rail the whole screen is cuttet with at the lower fifth
image

…ilter & scroll fixes

1. Add a favorite star (aria-pressed) toggle to ModelDetailPanel that reuses the existing client-local pin store; Favorites nav count reflects it.
2. Remove the 'Search presets' box and '+ New' button from ModelNavRail (model search lives in the middle pane).
3. Hide 'Back to models' at desktop widths; keep it for narrow/mobile viewports.
4. Scope the funnel filter to capabilities only and give the popover a solid opaque background (was undefined --surface-overlay -> transparent).
5. Fix the nav-rail layout clipping: .manager--detail inherited grid-template-rows auto 1fr from .manager, so panes grew past the viewport; bound the row with minmax(0,1fr) so the rail scrolls independently and the Storage meter stays reachable.

Tests A142-A148 added; A136 updated. 164 passed / 7 skipped / 0 failed.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@kpoineal

Copy link
Copy Markdown
Collaborator Author

@fl0rianr — pushed fixes for all five review items on PR #2424 (commit 9ac168b):

  1. Favorites star. Added a star toggle (☆/★) in the model detail panel header. It reuses the existing client-local favorite/pin store (no parallel store), so the Favorites nav count updates instantly and it persists. The icon button is an aria-pressed toggle whose label names the model ("Add … to favorites" / "Remove … from favorites").
  2. Rail cleanup. Removed the "Search presets…" box and "+ New" button from the left nav rail. Model search stays in the middle pane.
  3. Back to models. Now hidden at desktop widths; it only appears on narrow/mobile where the list and detail are mutually exclusive.
  4. Funnel filter. Scoped to capabilities only (LLM/Omni/Image/Audio/TTS/Embed) and relabelled accordingly. Fixed the see-through popover — it used an undefined --surface-overlay token (→ transparent); now a solid --surface-3 surface so nothing bleeds through.
  5. Rail cutoff. Root cause: .manager--detail inherited grid-template-rows: auto 1fr from .manager, so the panes sat in the content-sized auto row and grew past the viewport with no way to scroll. Bounded the row with minmax(0, 1fr) so the rail scrolls independently and the Storage meter stays reachable even with Categories/Backends/Tags expanded.

Tests: added A142–A148, updated A136. Full suite 164 passed / 7 skipped / 0 failed; tsc --noEmit clean.

Could you confirm these match what you had in mind? Happy to adjust the star's placement or the rail behaviour.

@fl0rianr

Copy link
Copy Markdown
Collaborator

Good but still some fixes ahead:

  • favorites is NOT the same as pinned, so change that, also add the star icon on the left rail (pin icon atm)
  • If no model is selected detail page shows that on the very top of the current list in middle rail
  • for the funnel i wanted to filter for e.g. tool, vision, popular etc. those icon have to be used in the middle panel as well instead of the current ones. A model can have multiple icons.
  • The storage show field on the bottom has to be correct, not 32/512 GB fix value. Used space/total space of the drive where the models are stored
  • When we used the search bar hugging face search is tiggert like before. In that case on the left panel below "My Models" and "Favorites", "Hugging Face" appears and the count of the matching models for the search is displayed.

If those points work we can merge.

1. Favorites is now a DISTINCT client-local store (favorite_models) from
   Pinned (pinned_models). Detail star toggles favorite; left-rail Favorites
   uses a star icon and counts favorites. Pinned still floats rows to the top.
2. Move the 'no model selected' empty state out of the middle list into the
   right ModelDetailPanel (noModelsAvailable prop); the middle list only shows
   a search-no-match message.
3. Funnel filters by functional capability tags (multi-select) derived from the
   model data; middle-panel rows render multiple capability icons each with an
   accessible label. Popover stays opaque (--surface-3).
4. Storage meter consumes real disk stats via api.getStorageInfo() when lemond
   provides them, else a graceful derived estimate (no 32/512 literal). POC has
   no real disk source yet — surfaced to fl0rianr on the PR.
5. A 'Hugging Face' nav entry appears in the left rail below My Models/Favorites
   when a model search yields HF matches, shows the live count, and filters the
   middle list to the HF results; it clears when the search is cleared.

Tests: A149-A153 added; A143/A145/A146 updated. 168 passed / 7 skipped / 0 failed.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@kpoineal

Copy link
Copy Markdown
Collaborator Author

@fl0rianr — pushed the 5 merge items (commit 8f008f1). All 168 Playwright tests pass (7 skipped, 0 failed).

1. Favorites ≠ Pinned (separated). New client-local store favorite_models (distinct from pinned_models). The detail-panel star now toggles FAVORITE; the left-rail "Favorites" entry uses a STAR icon (was a pin) and its count reflects the favorites store. Pinned stays its own thing — pinned models still float to the top of the middle list.

2. "No model selected" moved to the right pane. Removed the empty-state <li> from the top of the middle list. The placeholder now lives in ModelDetailPanel ("No model selected" / "No models found"). The middle list only shows a message for an active search with no matches.

3. Funnel = capability tags + multi-icon rows. The funnel is now a MULTI-SELECT of functional capabilities (Popular, Chat, Omni, Vision, Tool use, Reasoning, Code, Audio, Image, Speech, Embeddings, Reranking) derived from the model labels — a model can have several. Each middle-row renders one small icon per capability (each with an accessible label). Popover background stays opaque (--surface-3).

4. Storage = real used/total, no 32/512 mock — NEEDS YOUR INPUT. I removed the hardcoded literals and wired api.getStorageInfo() so the meter shows REAL disk used/total the moment a source provides it. POC limitation: since lemond is off-limits to me and the prototype is browser-served against a mock window.api, I verified there is currently NO disk source — /api/v1/system-info and /api/v1/system-stats expose CPU/GPU/NPU/RAM/VRAM but no disk usage. getStorageInfo() probes system-info for a future storage/disk field and returns real bytes if present; otherwise the meter falls back to a clearly-labelled estimate ("(est.)", aria-label says "estimated"): used = sum of downloaded model sizes, total = a headroom-rounded capacity (never a fixed literal). Question: how do you want real disk stats sourced? The natural fix is a small new field on lemond's system-info (or a dedicated endpoint) owned by the backend team — I won't touch lemond myself. Tell me the shape and the meter will light up with zero further client changes.

5. Hugging Face nav entry. When the model search triggers an HF search with matches, a "Hugging Face" entry appears in the left rail BELOW My Models and Favorites, showing the live count of HF matches. Clicking it filters the middle list to the HF results; it disappears (and resets to All Models) when the search is cleared.

Tests: added A149–A153, updated A143/A145/A146. Could you confirm 1/2/3/5 look right and let me know your preference on the storage sourcing for #4?

@fl0rianr fl0rianr left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, I will merge, can you please move on to #2355 and make a PR for that as well?

@fl0rianr
fl0rianr merged commit 16b8355 into kpoin/ui-testing Jun 25, 2026
38 checks passed
@fl0rianr
fl0rianr deleted the feat/gui3-model-detail-redesign branch June 25, 2026 23:17
@fl0rianr

Copy link
Copy Markdown
Collaborator

Sorry for the typo, i wanted to ask for #2356, we need a PR to solve that one.

fl0rianr pushed a commit that referenced this pull request Jul 17, 2026
…e 1) (#2424)

* Feat(GUI3): master-detail model view, remove preset rails (#2355 Slice 1)

- Add ModelListPanel: searchable, filterable listbox with funnel filter,
  compact model rows, keyboard nav (Arrow/Home/End), visible selected state.
  The only new icon is 'funnel' added to Icon.tsx.
- Add ModelDetailPanel: title, key metadata, primary actions (Load/Unload/
  Download/Delete/CancelPull), role=tablist with README (markdown-it +
  DOMPurify), Presets (loadApplied/saveApplied via presetStore.ts), and
  Files (stub, deferred).
- Remove renderPresetRail() and all preset-rail state from ModelManager.tsx
  (presetRailCollapsed, selectedRailPresetId, etc.).
- Wire custom-model / omni-collection form back via ModelListPanel footer
  buttons and conditional overlay panel in the detail slot.
- Fix WCAG scrollable-region-focusable: listbox gets tabIndex=0 when no
  item is selected; data-model-id attribute enables arrow-key selection.
- Update tests/features.spec.ts test 20 for new layout (HF zone removed).
- Update tests/a11y.spec.ts A46-A50 for master-detail interaction model.
- Add A91-A105 covering: layout, search, funnel filter, listbox/option ARIA,
  keyboard nav, tablist structure, Presets tab, custom-model buttons, axe scan.
- Update ACCESSIBILITY.md: Group E, bump test count to 121/7-skipped/0-failed.

Deferred to follow-ups: Files tab, full preset-card polish, sorting, #2356.

Part of #2355

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* feat(GUI3): reconcile Slice 1 with fl0rianr clarifications (#2355)

A: README checkpoint derivation — deriveHFRepo() tries model.checkpoint,
   model.checkpoints.main, then first checkpoints value; strips :variant
   suffix; validates with /^[\w.-]+\/[\w.-]+\$/ before fetching. HF link
   also uses deriveHFRepo. ModelReadmeTab now takes model prop.

B: Sort controls — labeled <select id='model-list-sort'> in ModelListPanel
   with Name (A-Z, default), Size, Last used, Download count. Graceful
   fallback to name when last_used/downloads data absent.

C: Responsive list-first — at max-width:700px, manager--detail-mobile-open
   class toggles list/detail panels exclusively (not stacked). Back button
   (model-detail-panel__back-btn) with aria-label injected via onBack prop.
   Focus: detail heading on open, selected list item on Back.

D: Presets tab Change — Change button (aria-haspopup=dialog, aria-expanded)
   on linked preset card opens inline role=dialog chooser listing compatible
   presets excluding current. Attach buttons in compatible list relabelled
   to Switch when a non-default preset is already linked.

Tests: A106-A115 (10 new); full suite 131 passed / 7 skipped / 0 failed.
ACCESSIBILITY.md: Group F added, test count updated.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(GUI3): render raw HTML in model README (html:true + frontmatter strip) (#2355)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* feat(GUI3): restore client-local pin/favorite to model rail (#2355)

fl0rianr follow-up: the master-detail ModelListPanel dropped the original
rail's pin/favorite affordance. Re-wire the existing client-local pin store
(scoped `pinned_models` localStorage, no lemond) into ModelListPanel so pinned
models float to the top.

A11y: the per-row pin is a non-button span (avoids axe nested-interactive
inside role="option"); keyboard/AT users toggle via aria-keyshortcuts="P" on
the focusable selected row; pinned state is exposed in the row aria-label.

Tests A118-A123 added; full suite 139 passed / 7 skipped / 0 failed.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* feat(gui3): add left navigation rail for three-pane model view (#2355)

Builds the missing LEFT navigation rail (ModelNavRail) to match fl0rianr's canonical 3-pane target: left nav rail + existing ModelListPanel (middle) + ModelDetailPanel (right).

Left rail (client-side derived, no lemond):
- Primary nav: All Models / Downloaded / My Models / Favorites with counts + aria-current; filters the middle list.
- Collapsible Categories (All/LLM/Omni/Image/Audio/TTS/Embed) reusing the existing filterTab state.
- Backends select (distinct recipes + counts) filters by recipe.
- Collapsible Tags (model-family + size chips) with aria-pressed; filters the list.
- Storage meter as role=progressbar; used derived from downloaded sizes, total is MOCK (no client disk-usage source, lemond off-limits).

Also per fl0rianr:
- Status stays a simple dot (no separate Downloaded badge).
- Moved + Custom model / + Omni collection buttons from the bottom footer to a grounded button group with background at the TOP of the list area.
- Presets remain in the model-detail Presets tab ('2) a'); global Presets page untouched.

Shared filter predicates exported from ModelListPanel are the single source of truth for both the middle-list filtering and the rail counts. Responsive: rail collapses behind a keyboard-reachable Filters toggle at <=700px.

Tests: A124-A136 (13 new) in tests/a11y.spec.ts. Full suite 152 passed / 7 skipped / 0 failed. ACCESSIBILITY.md Group G added.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* feat(gui3): restyle model-detail Presets tab as neat compact card grid (#2424)

fl0rianr feedback: the model-detail Presets tab rendered linked + compatible presets as full-width stacked rows. Restyle so the linked preset is a single highlighted card and recommended presets render as a neat responsive grid of compact cards (icon + name, short description, compact temp/ctx/tools meta, Attach/Switch action) consistent with the global preset (.recipe-card) style but smaller and focused.

a11y: grid is a ul role=list of li cards (not role=listbox/option) to avoid nested-interactive with per-card Attach buttons; linked/active state via aria-current + text badges (not color only); each Attach/Switch button names its preset + model; inline Change dialog preserved; Browse/Manage Presets deep-link via client-local lemonade:navigate event. Tests A137-A141; 157 passed / 7 skipped / 0 failed.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* GUI3 #2424: address fl0rianr review — favorites star, rail cleanup, filter & scroll fixes

1. Add a favorite star (aria-pressed) toggle to ModelDetailPanel that reuses the existing client-local pin store; Favorites nav count reflects it.
2. Remove the 'Search presets' box and '+ New' button from ModelNavRail (model search lives in the middle pane).
3. Hide 'Back to models' at desktop widths; keep it for narrow/mobile viewports.
4. Scope the funnel filter to capabilities only and give the popover a solid opaque background (was undefined --surface-overlay -> transparent).
5. Fix the nav-rail layout clipping: .manager--detail inherited grid-template-rows auto 1fr from .manager, so panes grew past the viewport; bound the row with minmax(0,1fr) so the rail scrolls independently and the Storage meter stays reachable.

Tests A142-A148 added; A136 updated. 164 passed / 7 skipped / 0 failed.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Address fl0rianr's 5 merge-blocking items on PR #2424 (GUI3 model view)

1. Favorites is now a DISTINCT client-local store (favorite_models) from
   Pinned (pinned_models). Detail star toggles favorite; left-rail Favorites
   uses a star icon and counts favorites. Pinned still floats rows to the top.
2. Move the 'no model selected' empty state out of the middle list into the
   right ModelDetailPanel (noModelsAvailable prop); the middle list only shows
   a search-no-match message.
3. Funnel filters by functional capability tags (multi-select) derived from the
   model data; middle-panel rows render multiple capability icons each with an
   accessible label. Popover stays opaque (--surface-3).
4. Storage meter consumes real disk stats via api.getStorageInfo() when lemond
   provides them, else a graceful derived estimate (no 32/512 literal). POC has
   no real disk source yet — surfaced to fl0rianr on the PR.
5. A 'Hugging Face' nav entry appears in the left rail below My Models/Favorites
   when a model search yields HF matches, shows the live count, and filters the
   middle list to the HF results; it clears when the search is cleared.

Tests: A149-A153 added; A143/A145/A146 updated. 168 passed / 7 skipped / 0 failed.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

app enhancement New feature or request squad:mattingly Assigned to ⚛️ Mattingly (UI / Frontend)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants