diff --git a/packages/ui/components/ui/list-grid.tsx b/packages/ui/components/ui/list-grid.tsx index 20349e9da..cd494fa81 100644 --- a/packages/ui/components/ui/list-grid.tsx +++ b/packages/ui/components/ui/list-grid.tsx @@ -22,7 +22,10 @@ import { cn } from "../../lib/utils"; // `hidden @:flex`; display:none cells drop out of subgrid // auto-placement so the remaining cells fill the right tracks. // - Columns drop by priority as the container narrows (most expendable -// first); never fall back to horizontal scrolling. +// first); never fall back to horizontal scrolling. Corollary: every +// tier's track sum (incl. column gaps) MUST fit inside that tier's +// trigger width — overflow is clipped unreachably, so document the +// arithmetic next to the template and re-check it on every width change. // - Keep the class a literal string in the page source so Tailwind sees it. export type ListGridSortDirection = "asc" | "desc"; diff --git a/packages/views/skills/components/skills-page.tsx b/packages/views/skills/components/skills-page.tsx index 7f84fe904..8cd6ebf38 100644 --- a/packages/views/skills/components/skills-page.tsx +++ b/packages/views/skills/components/skills-page.tsx @@ -70,15 +70,15 @@ import { useT, useTimeAgo } from "../../i18n"; // Column template — single source of truth for header, rows, and skeletons. // Tracks: [edge 0.75rem] [checkbox 1rem] [name, only fr track] -// [usedBy] [source, @4xl+] [creator, @4xl+] [updated/created, @2xl+] +// [usedBy] [source, @5xl+] [creator, @5xl+] [updated/created, @2xl+] // [kebab 1.75rem] [edge 0.75rem]. // Content cells carry a default px-2 from list-grid.tsx // (structural columns opt out with px-0), so the narrow edge tracks plus // cell padding land content 20px from the container edge. Hidden cells carry -// the matching `hidden @2xl:flex` / `hidden @4xl:flex` classes. +// the matching `hidden @2xl:flex` / `hidden @5xl:flex` classes. // Responsiveness is CONTAINER-driven, not viewport-driven: the page wrapper // is the `@container`, so an open sidebar or split pane narrows the list and -// columns drop by priority (source/creator below @4xl, updated/created +// columns drop by priority (source/creator below @5xl, updated/created // below @2xl; name/usedBy never drop) instead of forcing a horizontal // scrollbar. A // user-enabled column therefore means "show when space allows" — it comes @@ -90,10 +90,20 @@ import { useT, useTimeAgo } from "../../i18n"; // A user-hidden column zeroes its var (columnTrackVars), collapsing the // track exactly like the old max-content placeholder did; the empty // placeholder cell stays rendered to keep subgrid auto-placement intact. +// +// INVARIANT — every tier's track sum (incl. the n-1 gap-x-3 gaps) must fit +// inside that tier's trigger width, because there is no horizontal-scroll +// fallback: anything wider is clipped unreachably. Current arithmetic with +// all columns enabled: +// base (≥0): 12+16+96(name min)+144+28+12 + 5×12 = 368px +// @2xl (≥672px): 12+16+140+144+104+104+28+12 + 7×12 = 644px ≤ 672 ✓ +// @5xl (≥1024px): 12+16+180+144+152+144+104+104+28+12 + 9×12 = 1004px ≤ 1024 ✓ +// Touch a track width or add a column → redo this math and bump the tier +// breakpoint if it no longer fits. const GRID_COLS = - "grid-cols-[0.75rem_1rem_minmax(140px,1fr)_var(--lgc-usedby)_1.75rem_0.75rem] " + + "grid-cols-[0.75rem_1rem_minmax(96px,1fr)_var(--lgc-usedby)_1.75rem_0.75rem] " + "@2xl:grid-cols-[0.75rem_1rem_minmax(140px,1fr)_var(--lgc-usedby)_var(--lgc-updated)_var(--lgc-created)_1.75rem_0.75rem] " + - "@4xl:grid-cols-[0.75rem_1rem_minmax(200px,1fr)_var(--lgc-usedby)_var(--lgc-source)_var(--lgc-creator)_var(--lgc-updated)_var(--lgc-created)_1.75rem_0.75rem]"; + "@5xl:grid-cols-[0.75rem_1rem_minmax(180px,1fr)_var(--lgc-usedby)_var(--lgc-source)_var(--lgc-creator)_var(--lgc-updated)_var(--lgc-created)_1.75rem_0.75rem]"; // h-12 rows. The virtualizer's fixed-size contract: every row renders at // exactly this height, which is what lets it skip per-row measurement. @@ -103,9 +113,9 @@ function columnTrackVars( isVisible: (key: SkillColumnKey) => boolean, ): React.CSSProperties { return { - "--lgc-usedby": isVisible("usedBy") ? "10rem" : "0px", - "--lgc-source": isVisible("source") ? "11rem" : "0px", - "--lgc-creator": isVisible("creator") ? "10rem" : "0px", + "--lgc-usedby": isVisible("usedBy") ? "9rem" : "0px", + "--lgc-source": isVisible("source") ? "9.5rem" : "0px", + "--lgc-creator": isVisible("creator") ? "9rem" : "0px", "--lgc-updated": isVisible("updated") ? "6.5rem" : "0px", "--lgc-created": isVisible("created") ? "6.5rem" : "0px", } as React.CSSProperties; @@ -325,7 +335,7 @@ function SourceCell({ } return ( - + {icon} {label} @@ -334,7 +344,7 @@ function SourceCell({ function CreatorCell({ creator }: { creator: MemberWithUser | null }) { return ( - + {creator && ( <> )} {isColVisible("source") ? ( - + {t(($) => $.table.source)} ) : ( - + )} {isColVisible("creator") ? ( - + {t(($) => $.table.created_by)} ) : ( - + )} {isColVisible("updated") ? (