From 059d2613558c97c64d5ac2a7c7df0bdecb580f04 Mon Sep 17 00:00:00 2001 From: Naiyuan Qing <145280634+NevilleQingNY@users.noreply.github.com> Date: Fri, 12 Jun 2026 14:25:25 +0800 Subject: [PATCH] fix(skills): list tiers must fit their container trigger width The @4xl tier's track sum (~1080px with gaps) exceeded its 896px trigger; with the horizontal-scroll fallback gone, the right-side columns were clipped unreachably between 896-1080px. Move tier 3 to @5xl (1024px), trim usedBy/source/creator tracks, and document the fit invariant with its arithmetic next to the template and in the ListGrid conventions. Co-Authored-By: Claude Fable 5 --- packages/ui/components/ui/list-grid.tsx | 5 +- .../views/skills/components/skills-page.tsx | 50 +++++++++++-------- 2 files changed, 34 insertions(+), 21 deletions(-) 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") ? (