From 83b4b99c9dd01e3db1f3f3bdbb8b1b7c0cb7492f Mon Sep 17 00:00:00 2001 From: "[._.]/ Adam Eivy" Date: Tue, 28 Jul 2026 18:06:57 -0700 Subject: [PATCH] make the Catalog page usable on a phone MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The toolbar's four controls needed ~350px and ran off the side of a 320px viewport; the three filter dropdowns each claimed a fixed min-width they didn't have room for; and the selection bar's menus opened off-screen when anchored to a ~90px button. - Toolbar wraps to its own rows under `sm`: full-width segmented Grid/Albums toggle, then Sync/Ingest/New splitting the row evenly. - Search moved above the filters so it's reachable without scrolling. - Type chips stay on one swipeable row instead of stacking 3+ rows deep. - Filter dropdowns lay out 2-up; the fixed min-width applies from `sm` up. - Selection bar stacks its count above three equal-width actions. Its menus anchor to the button ROW under `sm` (the per-button wrappers only become `relative` at `sm`) so they span the bar, and cap at 60vh so an upward- opening menu can't run off the top in landscape. - Labels too long for a narrow button hide their tail and keep the full wording in aria-label, so the accessible name never depends on viewport width (WCAG 2.5.3 label-in-name, asserted in the suite). - Card delete confirm moves from a corner overlay to the shared InlineConfirmRow band, which names the ingredient. The overlay had to be paid for with reserved padding on the link, and a single-column phone card has no width to spare — reserving it crushed the name, and stretching the overlay instead covered the name outright, hiding the record being deleted. The band also drops a magic padding constant that was coupled to the confirm row's own height. - Bigger delete/select tap targets; album subtitle drops below the title instead of squeezing it. - Sticky bar drops `backdrop-blur` under `sm`: the fill is already 95% opaque, so the blur only cost a per-frame compositor re-sample. Apart from the delete-confirm band, desktop layout is unchanged. --- .changelog/NEXT.md | 2 + .../src/components/catalog/CatalogAlbum.jsx | 18 +- client/src/components/catalog/CatalogCard.jsx | 72 ++++---- client/src/pages/Catalog.jsx | 157 +++++++++++------- client/src/pages/Catalog.test.jsx | 22 +++ 5 files changed, 168 insertions(+), 103 deletions(-) diff --git a/.changelog/NEXT.md b/.changelog/NEXT.md index e17887055a..30627af5fd 100644 --- a/.changelog/NEXT.md +++ b/.changelog/NEXT.md @@ -38,6 +38,8 @@ - **Branch reconciler prioritizes recognized work branches.** The in-flight set handed to the coordinator agent is now ordered by branch prefix — `claim/`, `cos/`, `next/`, `feature/`, `fix/`, and other conventional prefixes are reconciled ahead of unrecognized/ad-hoc branches — so a bounded run spends its budget on real deliverables first. Both `/` and `-` separators match. +- **The Catalog page is usable on a phone.** The toolbar used to run off the side of the screen and the filter dropdowns each claimed a fixed width they didn't have room for. Now the view toggle spans the width as a segmented Grid/Albums control with the three actions split evenly beneath it, search sits directly under the header so it's reachable without scrolling, the type chips stay on one swipeable row instead of stacking three rows deep, and the Universe/Series/Tag dropdowns lay out two-up. The selection bar stacks its count above three equal-width buttons, and its "Remix into…" / "Add to universe/series…" menus now span the bar instead of opening off-screen. Delete and select targets on each card are bigger, and long album subtitles drop below the title rather than squeezing it. Confirming a card delete now shows a band across the top of the card naming the ingredient — `Delete "Echo Saint"?` — instead of an unlabeled prompt tucked in the corner, which on a phone left barely enough room for the name beside it. That one is visible at every width; the rest of the layout is unchanged on a desktop-width window. + ## AI providers - **[issue-3194] Ask and image captioning now work on a local OpenCode provider.** If you pointed Ask Yourself or vision captioning at an OpenCode provider backed by Ollama, the run failed with the model reported as "not valid" — OpenCode has to be told which local models exist before it will accept one, and only the agent and Run Prompt paths were doing that. Every path that starts a coding CLI now declares them the same way, so the model you picked is accepted wherever you use it. diff --git a/client/src/components/catalog/CatalogAlbum.jsx b/client/src/components/catalog/CatalogAlbum.jsx index 13bf9a5bbb..04f6c5d13f 100644 --- a/client/src/components/catalog/CatalogAlbum.jsx +++ b/client/src/components/catalog/CatalogAlbum.jsx @@ -75,6 +75,7 @@ export default function CatalogAlbum({ }, [expanded, ensureLoaded]); const toggle = () => setExpanded((e) => !e); + const Chevron = expanded ? ChevronDown : ChevronRight; const loadMore = async () => { setLoadingMore(true); @@ -102,21 +103,26 @@ export default function CatalogAlbum({ return (
+ {/* On a phone the subtitle drops to its own line under the title rather + than competing with it for a ~200px slot; the chevron and count stay + pinned so the row reads the same at every width. */} {expanded && ( -
+
{loading ? (
Loading… diff --git a/client/src/components/catalog/CatalogCard.jsx b/client/src/components/catalog/CatalogCard.jsx index feefb12604..aaa8932944 100644 --- a/client/src/components/catalog/CatalogCard.jsx +++ b/client/src/components/catalog/CatalogCard.jsx @@ -11,6 +11,7 @@ import { Link } from 'react-router-dom'; import { Sparkles, Trash2 } from 'lucide-react'; import MediaImage from '../MediaImage'; +import InlineConfirmRow from '../ui/InlineConfirmRow'; import { payloadSnippet } from '../../lib/catalogTypes'; function TypeBadge({ type, getType }) { @@ -60,7 +61,7 @@ export default function CatalogCard({ const snippet = payloadSnippet(it.payload, it.type, 120, getType); return (
  • onToggleSelect(it)} onClick={(e) => e.stopPropagation()} aria-label={`Select ${name}`} - className="w-4 h-4 m-1 accent-port-accent cursor-pointer" + className="w-4 h-4 m-1.5 accent-port-accent cursor-pointer" /> + {/* Armed delete: a full-width band above the card body (the house + inline-confirm pattern) rather than a corner overlay. A corner overlay + has to be paid for with reserved padding on the link, and on a + single-column phone card there isn't enough width to reserve without + crushing the name — the version that stretched the overlay instead + covered the name, hiding the very thing being deleted. The band names + it. `pl-11` clears the absolutely-positioned checkbox. */} + {armed && ( + onConfirmDelete(it)} + onCancel={onCancelArm} + /> + )} @@ -96,39 +115,20 @@ export default function CatalogCard({ ) : null} -
    - {/* The delete control is a sibling of the , not a descendant, so a - click on these buttons never traverses the anchor. */} - {armed ? ( - - Delete? - - - - ) : ( - - )} -
    + {/* The arm button is a sibling of the , not a descendant, so its + click never traverses the anchor. It gives way to the confirm band + above once armed, so the card never shows two delete affordances. */} + {!armed && ( + + )}
  • ); } diff --git a/client/src/pages/Catalog.jsx b/client/src/pages/Catalog.jsx index de16e55620..0c296c074e 100644 --- a/client/src/pages/Catalog.jsx +++ b/client/src/pages/Catalog.jsx @@ -517,69 +517,98 @@ export default function Catalog() { return (
    -
    -
    -