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 (
    -
    -
    -