Files
fusion/packages/dashboard/app/components/SkillsView.css
gsxdsm ea3cfeeac4 FN-7027: constrain skill list row text
Keep discovered skill rows within the left pane by truncating long text values.

- Wrap skill names in a truncatable text span while keeping chevrons fixed-size.
- Add ellipsis rules and compact font sizing for skill names, paths, and sources.
- Cover long, short, and empty metadata skill rows with a dashboard test.
- Add a patch changeset for the published CLI package.

Files changed:
 .changeset/fn-7027-skill-list-truncation.md        |  7 +++
 packages/dashboard/app/components/SkillsView.css   | 28 ++++++++++
 packages/dashboard/app/components/SkillsView.tsx   |  2 +-
 .../app/components/__tests__/SkillsView.test.tsx   | 64 ++++++++++++++++++++++
 4 files changed, 100 insertions(+), 1 deletion(-)

Fusion-Task-Id: FN-7027

Fusion-Task-Lineage: ae50f1aa-e58b-4374-abae-38ca28e4073a
2026-06-25 19:35:47 -07:00

662 lines
16 KiB
CSS

/* === Skills View === */
/*
FNXC:SkillsView 2026-06-22-16:15:
Skills mounts as a flex child of the flex-row .project-content. A flex item with no flex-grow collapses to its intrinsic content width, so the Skills content rendered narrow instead of spanning the main panel. Grow into available space, zero the min-width floor, and pin width:100% — mirroring the GoalsView/SecretsView (FN-6446/FN-6789) fix — so the view fills the full panel width.
*/
.skills-view {
display: flex;
flex: 1 1 auto;
flex-direction: column;
height: 100%;
min-width: 0;
width: 100%;
overflow: hidden;
/*
FNXC:Skills 2026-06-23-01:45:
Establish the query container so the master/detail body can switch between the single-panel stack (narrow) and the two-pane split (wide) based on the view's OWN width — not the global viewport. Modeled on DockFilesView (container-name: dock-files). SkillsView always fills the full main panel, so the inline-size query fires reliably (unlike the right-dock pop-out, which needed DockFilesView's deterministic fallback).
*/
container-type: inline-size;
container-name: skills-view;
}
/*
FNXC:Skills 2026-06-23-01:45:
Master/detail body below the shared ViewHeader. Holds BOTH always-rendered panes.
- NARROW default: single column. The list (.skills-view__list) fills the body; the detail (.skills-view__detail) is hidden until a skill is selected, then it covers the stack (BACK returns to the list).
- WIDE (@container >=640px below): flex-row two-pane — list pinned left (clamped, scrolls), detail flex:1 right (scrolls).
*/
.skills-view-body {
display: flex;
flex-direction: column;
flex: 1 1 auto;
min-height: 0;
min-width: 0;
overflow: hidden;
}
/* FNXC:Skills 2026-06-23-01:45: NARROW default — master list fills the body as the single panel. */
.skills-view__list {
display: flex;
flex-direction: column;
flex: 1 1 auto;
min-height: 0;
min-width: 0;
overflow: hidden;
}
/*
FNXC:Skills 2026-06-23-01:45: NARROW default — detail is the stacked second panel.
Hidden until a skill is selected; when selected ([data-selected="true"]) it overlays the list as the single visible panel.
*/
.skills-view__detail {
display: none;
flex-direction: column;
flex: 1 1 auto;
min-height: 0;
min-width: 0;
overflow: hidden;
}
.skills-view[data-selected="true"] .skills-view__list {
display: none;
}
.skills-view[data-selected="true"] .skills-view__detail {
display: flex;
}
/* FNXC:Skills 2026-06-23-01:45: detail content scrolls inside the pane body so the SKILL.md pre + file badges never overflow the pane. */
.skills-view-detail-body {
flex: 1 1 auto;
min-height: 0;
overflow-y: auto;
padding: var(--space-lg);
display: flex;
flex-direction: column;
gap: var(--space-md);
}
/* FNXC:Skills 2026-06-23-01:45: empty-state placeholder shown in the wide right pane until a skill is selected. */
.skills-view-detail-placeholder {
display: flex;
align-items: center;
justify-content: center;
flex: 1 1 auto;
text-align: center;
color: var(--text-muted);
}
.skills-view-detail-back {
flex-shrink: 0;
}
/*
FNXC:Skills 2026-06-23-01:45:
WIDE container (>=640px): two-pane side-by-side master/detail. Both panes always visible (data-selected no longer toggles visibility here), so the BACK button is hidden — the list never disappears. Mirrors DockFilesView's @container rule.
*/
@container skills-view (min-width: 640px) {
.skills-view-body {
flex-direction: row;
}
/* List pinned LEFT: clamped, scrolls independently, divider against the detail pane. */
.skills-view__list {
display: flex;
flex: 0 0 clamp(280px, 38%, 460px);
min-width: 0;
overflow: hidden;
border-right: 1px solid var(--border);
}
/* Detail fills the remaining width; always visible (empty-state until a skill is selected). */
.skills-view__detail,
.skills-view[data-selected="true"] .skills-view__detail {
display: flex;
flex: 1 1 auto;
min-width: 0;
overflow: hidden;
}
.skills-view[data-selected="true"] .skills-view__list {
display: flex;
}
/* BACK is meaningless when the list is always visible. */
.skills-view__detail .skills-view-detail-back {
display: none;
}
}
.skills-view-count {
color: var(--text-muted);
}
/*
FNXC:Navigation 2026-06-22-02:00:
The shared ViewHeader already supplies the top + side --space-lg padding, so the content area must NOT repeat the top padding (that doubled the gap under the header). Side + bottom padding only, aligned with the header.
FNXC:Skills 2026-06-22-14:23:
The search controls at the top of the Skills list need breathing room below the shared header. Restore a single --space-lg top inset on the scrollable content so the search box does not visually bump into the header.
*/
.skills-view-content {
flex: 1;
overflow-y: auto;
padding: var(--space-lg) var(--space-lg) var(--space-lg);
}
.skills-view-section {
margin-bottom: var(--space-xl);
}
.skills-view-section-title {
font-weight: 600;
color: var(--text);
margin-bottom: var(--space-md);
padding-bottom: var(--space-sm);
border-bottom: 1px solid var(--border);
display: flex;
align-items: center;
gap: var(--space-sm);
}
/* Discovered skills list */
.skills-view-list {
display: flex;
flex-direction: column;
gap: var(--space-sm);
}
.skills-view-item {
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--space-md);
padding: var(--space-sm) var(--space-md);
background: var(--card);
border: 1px solid var(--border);
border-radius: var(--radius-md);
cursor: pointer;
transition: background var(--transition-fast), border-color var(--transition-fast);
}
.skills-view-item:hover {
background: var(--card-hover);
border-color: var(--text-muted);
}
.skills-view-item-info {
display: flex;
flex-direction: column;
gap: var(--space-xs);
min-width: 0;
flex: 1;
}
/*
FNXC:Skills 2026-06-25-00:00:
Left Discovered Skills rows must keep compact, intentional text sizing and truncate every text line with ellipsis so long names, paths, or sources never force horizontal scroll in wide clamped, narrow dock, or mobile list layouts. The chevron remains fixed-size while only the skill-name text truncates.
*/
.skills-view-item-name {
display: flex;
align-items: center;
gap: var(--space-xs);
max-width: 100%;
min-width: 0;
font-size: 0.9em;
font-weight: 500;
color: var(--text);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.skills-view-item-name svg {
flex-shrink: 0;
}
.skills-view-item-name-text {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.skills-view-item-path {
max-width: 100%;
font-size: 0.85em;
color: var(--text-muted);
font-family: var(--font-mono);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.skills-view-item-source {
max-width: 100%;
font-size: 0.85em;
color: var(--text-dim);
text-transform: uppercase;
letter-spacing: calc(var(--space-xs) / 8);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* Toggle switch */
.skills-view-item-toggle {
position: relative;
display: flex;
align-items: center;
cursor: pointer;
user-select: none;
flex-shrink: 0;
}
.skills-view-item-toggle input {
position: absolute;
width: calc(var(--space-xs) / 4);
height: calc(var(--space-xs) / 4);
padding: 0;
margin: calc(var(--space-xs) / -4);
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
.skills-view-toggle-slider {
position: relative;
width: calc(var(--space-xl) + var(--space-lg));
height: calc(var(--space-xl) - (var(--space-xs) / 2));
background: var(--border);
border-radius: var(--radius-md);
transition: background var(--transition-normal);
flex-shrink: 0;
}
.skills-view-toggle-slider::after {
content: "";
position: absolute;
top: calc(var(--space-xs) / 2);
left: calc(var(--space-xs) / 2);
width: calc(var(--space-lg) + (var(--space-xs) / 2));
height: calc(var(--space-lg) + (var(--space-xs) / 2));
background: var(--card);
border-radius: 50%;
transition: transform var(--transition-normal);
}
.skills-view-item-toggle input:checked + .skills-view-toggle-slider {
background: var(--color-success);
}
.skills-view-item-toggle input:checked + .skills-view-toggle-slider::after {
transform: translateX(calc(var(--space-lg) + (var(--space-xs) / 2)));
}
/* Skills search — at top */
.skills-view-search {
margin-bottom: var(--space-lg);
}
.skills-view-search .form-input {
width: 100%;
max-width: calc(var(--space-2xl) * 12 + var(--space-md));
}
/* Catalog grid */
.skills-view-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(calc(var(--space-2xl) * 8 + var(--space-lg)), 1fr));
gap: var(--space-md);
}
.skills-view-card {
background: var(--card);
border: 1px solid var(--border);
border-radius: var(--radius-md);
padding: var(--space-md);
min-height: calc(var(--space-2xl) * 3 + var(--space-xl));
display: flex;
flex-direction: column;
gap: var(--space-sm);
transition: background var(--transition-fast), border-color var(--transition-fast);
}
.skills-view-card:hover {
background: var(--card-hover);
border-color: var(--text-muted);
}
.skills-view-card-header {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: var(--space-sm);
}
.skills-view-card-install {
align-self: flex-start;
flex-shrink: 0;
}
.skills-view-card-title {
font-weight: 600;
color: var(--text);
margin: 0;
}
.skills-view-card-description {
color: var(--text-muted);
line-height: 1.45;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
margin: 0;
}
.skills-view-card-tags {
display: flex;
flex-wrap: wrap;
gap: var(--space-xs);
}
.skills-view-card-installs {
color: var(--text-dim);
margin-top: auto;
}
/* Empty / error / loading states */
.skills-view-empty {
display: flex;
flex-direction: column;
align-items: center;
gap: var(--space-md);
padding: var(--space-2xl);
border: 1px dashed var(--border);
border-radius: var(--radius-md);
text-align: center;
color: var(--text-muted);
}
.skills-view-error {
display: flex;
flex-direction: column;
align-items: center;
gap: var(--space-sm);
padding: var(--space-lg);
color: var(--color-error);
background: color-mix(in srgb, var(--color-error) 8%, transparent);
border: 1px solid color-mix(in srgb, var(--color-error) 25%, transparent);
border-radius: var(--radius-md);
text-align: center;
}
.skills-view-loading {
display: flex;
align-items: center;
justify-content: center;
gap: var(--space-sm);
padding: var(--space-xl);
color: var(--text-muted);
}
/* Close button */
.skills-view-close {
flex-shrink: 0;
}
/* === Skill Detail Panel === */
.skills-view-item--selected {
border-color: var(--todo);
background: var(--card-hover);
}
.skills-view-detail {
background: var(--surface);
border: 1px solid var(--border);
border-left: calc(var(--space-md) / 4) solid var(--todo);
border-radius: var(--radius-md);
margin: var(--space-sm) 0 var(--space-md) 0;
padding: var(--space-lg);
}
/*
FNXC:Skills 2026-06-23-01:45:
Detail-pane header bar: BACK (narrow only) on the left, truncating skill name in the middle, Close on the right. Mirrors DockFilesView's viewer header. Now a flex:0 bar inside the detail pane (the pane itself carries no padding; the header + body each supply their own).
*/
.skills-view-detail-header {
display: flex;
align-items: center;
gap: var(--space-sm);
flex: 0 0 auto;
padding: var(--space-sm) var(--space-lg);
border-bottom: 1px solid var(--border);
}
.skills-view-detail-title {
flex: 1 1 auto;
min-width: 0;
font-weight: 600;
color: var(--text);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.skills-view-detail-close {
flex-shrink: 0;
}
.skills-view-detail-content {
font-family: var(--font-mono);
line-height: 1.5;
overflow-x: auto;
white-space: pre-wrap;
word-break: break-word;
color: var(--text);
background: var(--bg);
padding: var(--space-md);
border-radius: var(--radius-sm);
max-height: calc(var(--space-2xl) * 12 + var(--space-md));
overflow-y: auto;
}
/*
FNXC:Skills 2026-06-23-04:15:
Compact referenced-files strip. Smaller gap/padding and a smaller font so the section no longer dominates the detail pane. Holds ALL referenced files (no cap). File entries are clickable buttons; directories are static badges.
*/
.skills-view-detail-files {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: calc(var(--space-xs) / 2);
margin-top: var(--space-sm);
padding-top: var(--space-xs);
border-top: 1px solid var(--border);
font-size: 0.85em;
}
.skills-view-detail-files-label {
color: var(--text-muted);
}
/* FNXC:Skills 2026-06-23-04:15: clickable file badge in the compact strip. Reset native button chrome so the .badge styling reads as a chip; pointer + hover signal it loads the file. */
.skills-view-detail-file {
appearance: none;
border: 1px solid var(--border);
background: var(--card);
color: var(--text);
cursor: pointer;
font: inherit;
line-height: 1.2;
transition: background var(--transition-fast), border-color var(--transition-fast);
}
.skills-view-detail-file:hover {
background: var(--card-hover);
border-color: var(--text-muted);
}
.skills-view-detail-file--active {
border-color: var(--todo);
background: var(--card-hover);
}
/* FNXC:Skills 2026-06-23-04:15: directories are not previewable -> static, dimmed badge. */
.skills-view-detail-file--dir {
color: var(--text-dim);
}
/*
FNXC:Skills 2026-06-23-04:15:
File viewer column inside the detail body. Header bar carries the "← Back to SKILL.md" affordance + the open file's relative path; the content (markdown or <pre>) and the compact files strip follow.
*/
.skills-view-file-viewer {
display: flex;
flex-direction: column;
gap: var(--space-sm);
min-width: 0;
}
.skills-view-file-viewer-bar {
display: flex;
align-items: center;
gap: var(--space-sm);
min-width: 0;
}
.skills-view-file-back {
flex-shrink: 0;
}
.skills-view-file-viewer-name {
flex: 1 1 auto;
min-width: 0;
color: var(--text-muted);
font-family: var(--font-mono);
font-size: 0.9em;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* FNXC:Skills 2026-06-23-04:15: markdown render wrapper (SKILL.md + markdown files) reuses the shared .mailbox-markdown styling from MailboxMessageContent; this just bounds its width inside the pane. */
.skills-view-detail-markdown {
min-width: 0;
word-break: break-word;
}
.skills-view-detail-loading {
display: flex;
align-items: center;
gap: var(--space-sm);
padding: var(--space-md);
color: var(--text-muted);
}
.skills-view-detail-error {
display: flex;
flex-direction: column;
align-items: center;
gap: var(--space-sm);
padding: var(--space-md);
color: var(--color-error);
background: color-mix(in srgb, var(--color-error) 10%, transparent);
border-radius: var(--radius-sm);
text-align: center;
}
.skills-view-detail-empty {
padding: var(--space-md);
color: var(--text-muted);
font-style: italic;
}
@media (max-width: 768px) {
.skills-view-content {
padding: var(--space-md);
}
.skills-view-section {
margin-bottom: var(--space-md);
}
.skills-view-section-title {
}
.skills-view-card {
padding: var(--space-sm);
}
.skills-view-card-title {
}
.skills-view-card-description {
}
.skills-view-empty {
padding: var(--space-lg);
}
.skills-view-error {
padding: var(--space-lg);
}
.skills-view-loading {
padding: var(--space-md);
}
.skills-view-search .form-input {
width: 100%;
max-width: none;
}
.skills-view-grid {
grid-template-columns: 1fr;
}
.skills-view-card-header {
flex-wrap: wrap;
}
.skills-view-item {
padding: var(--space-md);
min-height: calc(var(--space-lg) + var(--space-md) + var(--space-xs));
flex-wrap: nowrap;
}
.skills-view-item-info {
flex: 1 1 auto;
width: auto;
}
.skills-view-item-toggle {
padding: var(--space-sm);
}
.skills-view-item--selected {
border-color: var(--todo);
}
.skills-view-toggle-slider {
min-width: calc(var(--space-xl) + var(--space-lg));
min-height: calc(var(--space-xl) - (var(--space-xs) / 2));
}
.skills-view-detail {
padding: var(--space-md);
}
.skills-view-detail-content {
max-height: calc(60dvh - (var(--space-2xl) * 6 + var(--space-xs)));
-webkit-overflow-scrolling: touch;
}
.skills-view-detail-close {
min-width: calc(var(--space-lg) + var(--space-md) + var(--space-xs));
min-height: calc(var(--space-lg) + var(--space-md) + var(--space-xs));
}
}