From 9e1468942655ef9f71c425d9d1a303e7db0b912f Mon Sep 17 00:00:00 2001 From: Harlan Wilton Date: Tue, 6 Oct 2026 13:04:52 +1100 Subject: [PATCH 1/4] feat(skills): render Skill embeds through one SkillCard Skills rendered through eight markups: SkillCard, SkillTable, the repository card, and hand-written rows on curator profiles, /me, collections, the reviewed list and the trending archive. Names, bylines and controls drifted apart. SkillCard now takes the context as props: a card, row or compact layout, how much byline the page already carries, one metric, and opt-in controls. A pure view builder resolves the facts once, so every layout shows the same ones. The design merges the takes Harlan picked from the lab: one identity unit, at most two lines of description, and a dashed run pill. Hearts and the SKILL.md icon are opt-in now. --- DESIGN.md | 8 + app/components/SkillCard.vue | 819 +++++++++++------- app/components/SkillTable.vue | 306 ------- .../skill-card/_SkillCardIdentity.vue | 175 ++++ .../skill-card/_SkillCardMetric.vue | 40 + app/components/skill-card/_SkillCardRun.vue | 148 ++++ app/pages/@[login]/[slug].vue | 95 +- app/pages/@[login]/index.vue | 85 +- app/pages/@[login]/liked.vue | 11 +- app/pages/_playground/skill-cards.vue | 283 ++++++ app/types/skill-card.ts | 100 +++ app/utils/skill-card-view.ts | 116 +++ layers/identity/app/pages/me/index.vue | 56 +- .../app/components/AgentSkillsPage.vue | 2 +- .../components/FrameworkSkillsDirectory.vue | 2 +- .../content/ComparisonSkillCard.vue | 18 +- layers/marketing/app/pages/skills/best.vue | 61 +- layers/marketing/app/pages/skills/index.vue | 34 +- .../marketing/app/pages/skills/tag/[slug].vue | 7 +- .../marketing/app/pages/skills/trending.vue | 65 +- .../gh/[owner]/[repo]/_RepoSkillCard.vue | 125 --- .../[owner]/[repo]/_RepoSkillDependencies.vue | 38 + .../app/pages/gh/[owner]/[repo]/index.vue | 26 +- .../registry/app/pages/gh/[owner]/index.vue | 2 +- test/a11y.spec.ts | 23 +- test/unit/install-copy.test.ts | 7 +- .../unit/repo-skill-dependencies-card.test.ts | 121 --- test/unit/repo-skill-dependencies.test.ts | 59 ++ test/unit/skill-card-condensed.test.ts | 139 --- test/unit/skill-card-view.test.ts | 105 +++ test/unit/skill-table-ranking.test.ts | 28 - .../skills-registry-loading-state.test.ts | 2 +- 32 files changed, 1734 insertions(+), 1372 deletions(-) delete mode 100644 app/components/SkillTable.vue create mode 100644 app/components/skill-card/_SkillCardIdentity.vue create mode 100644 app/components/skill-card/_SkillCardMetric.vue create mode 100644 app/components/skill-card/_SkillCardRun.vue create mode 100644 app/pages/_playground/skill-cards.vue create mode 100644 app/types/skill-card.ts create mode 100644 app/utils/skill-card-view.ts delete mode 100644 layers/registry/app/pages/gh/[owner]/[repo]/_RepoSkillCard.vue create mode 100644 layers/registry/app/pages/gh/[owner]/[repo]/_RepoSkillDependencies.vue delete mode 100644 test/unit/repo-skill-dependencies-card.test.ts create mode 100644 test/unit/repo-skill-dependencies.test.ts delete mode 100644 test/unit/skill-card-condensed.test.ts create mode 100644 test/unit/skill-card-view.test.ts delete mode 100644 test/unit/skill-table-ranking.test.ts diff --git a/DESIGN.md b/DESIGN.md index 1b3b194d..ddeb2af2 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -183,6 +183,13 @@ Use `UiTooltip` (not `UTooltip`) for any tooltip that needs more than a plain st - **Data labels**: top-level metrics are singular and labeled ("12 skills", "3 curators"); expanded views show the full breakdown ### Bound Components +- **Skill embeds**: every Skill shown as an item uses ``. Do not hand-write Skill markup. Props fit it to the context: + - `layout`: `card` for a grid tile or an article embed, `row` for a ledger line, `compact` for a sidebar or an index. + - `byline`: `full` by default, `repo` where the page names the owner, `none` where it names the Repository. + - `metric`: one of `stars`, `likes` or `updated`, or `none`. + - `actions`: `run` by default. `like` and `source` are opt-in. + - `rank`, `note` and `trending`, plus the `meta`, `footer` and `actions` slots for facts only one context has. + `/_playground/skill-cards` renders every context in development. - **Skill source lists**: use `SkillSourceList` when a shortlist needs visible author, handle, repository, and source-linked skill name. `stream` is a vertically scrollable discovery preview; `grid` is for bounded related-skill groups. Idle auto-scroll pauses for hover, focus, touch, and reduced motion. Mixed 20-item discovery streams should represent at least 10 people and cap each person at 2 skills. - **Install commands**: never render one as raw text. Use `` so `npx skilld add` stays quiet and the target carries the accent. Long commands wrap (`wrap`); they never get a horizontal scrollbar inside a card. @@ -460,3 +467,4 @@ These are editorial sets, not necessarily database collections yet. If a set has - **Trending shares the /skills shell**: `/skills/trending` uses the full-width layout from `/skills`. Its heading replaces `CompactPageHeader` and lines up with the results column. A sticky 14rem sidebar holds the range links, then the weekly CTA and the skilld install. Nothing sits right of the board, so the board takes every column the sidebar leaves. Rows switch on their own width, and the post carousel shows two cards where it has room. On narrow screens the ranges are a compact row above the board, and the CTA stays after the fifth row. Harlan asked for this on 2026-10-06. It supersedes the 2026-10-01 call that dropped the 9/3 sidebar for taking a quarter of the board's width: that sidebar sat right of a 64rem board, and this one sits left of a board that runs the full page width. - **Trending rows scan in one pass**: the rank is a plain mono number, and the first three are a step darker. Each metadata line runs source, stars, then the signal that ranked the row, and an empty slot renders nothing. Descriptions stop at two lines, the run chip is as wide as its command, and the post carousel shows whole cards only. The sidebar invitation drops the email preview and the card. Its button is the rail's one solid rose element, so the install chip beside it inks its dot. Harlan asked for this polish on 2026-10-06. - **A six-step lifecycle band replaces the four tiles**: under the hero, one band maps the whole product in its real order: find, run, install or fork, keep current, make, build on. The site and the CLI share one line. Each step is one link: a mono `01` marker and step name, then one sans line that opens with its pitch line where COPY.md has one. A line of stone dots joins the steps. Run, the default, carries the band's one rose dot, so no step lights rose on hover. Six columns from 1024px; below that a vertical list with the dots down the left. Keep the band no taller than the tiles it replaced: the hero and the band must fit a 1440×900 viewport. Harlan approved it on 2026-10-06. +- **One Skill card**: `SkillCard` renders every Skill embed, in three layouts. Each opens with one identity unit: the avatar beside the mono name, over a byline of author and Repository. Then come at most two lines of description and one line of facts with a dashed `run` pill. The pill is the run chip's material without `npx skilld`. Its click copies the full command, and its dot stays stone until hover, focus or copy. Rows read in three zones, who, what and the facts, and stack like a borderless card under 40rem. Compact entries drop the description. Cards show no SKILL.md icon and no heart by default: with few accounts yet, a row of zero hearts reads as an empty room. Harlan chose this from five takes, one by Fable, on 2026-10-06. It supersedes the `/skills` table and its "What it does" column. diff --git a/app/components/SkillCard.vue b/app/components/SkillCard.vue index 0afedbbb..8a05089c 100644 --- a/app/components/SkillCard.vue +++ b/app/components/SkillCard.vue @@ -1,353 +1,524 @@ + + diff --git a/app/components/SkillTable.vue b/app/components/SkillTable.vue deleted file mode 100644 index 72dd41e9..00000000 --- a/app/components/SkillTable.vue +++ /dev/null @@ -1,306 +0,0 @@ - - - - - diff --git a/app/components/skill-card/_SkillCardIdentity.vue b/app/components/skill-card/_SkillCardIdentity.vue new file mode 100644 index 00000000..5baa933b --- /dev/null +++ b/app/components/skill-card/_SkillCardIdentity.vue @@ -0,0 +1,175 @@ + + + + + diff --git a/app/components/skill-card/_SkillCardMetric.vue b/app/components/skill-card/_SkillCardMetric.vue new file mode 100644 index 00000000..3d893cc6 --- /dev/null +++ b/app/components/skill-card/_SkillCardMetric.vue @@ -0,0 +1,40 @@ + + + + + diff --git a/app/components/skill-card/_SkillCardRun.vue b/app/components/skill-card/_SkillCardRun.vue new file mode 100644 index 00000000..e267a3ca --- /dev/null +++ b/app/components/skill-card/_SkillCardRun.vue @@ -0,0 +1,148 @@ + + + + + diff --git a/app/pages/@[login]/[slug].vue b/app/pages/@[login]/[slug].vue index b546c9c4..fe46c260 100644 --- a/app/pages/@[login]/[slug].vue +++ b/app/pages/@[login]/[slug].vue @@ -61,23 +61,6 @@ defineOgImage('Collection.takumi', { }, { alt: () => `${collectionOgProps.value?.name ?? 'Collection'} by @${login.value} on skilld`, }) - -function collectionSkillPath(skill: { registryPath: string }) { - return skill.registryPath -} - -function collectionSkillLabel(skill: { repo: string, name?: string | null }) { - return skill.name ? `/${skill.name}` : skill.repo -} - -function collectionSkillTitle(skill: { repo: string, name?: string | null, displayName?: string | null }) { - return skill.displayName || collectionSkillLabel(skill) -} - -function collectionSkillMeta(skill: { owner: string, repo: string, name?: string | null }) { - const source = `${skill.owner}/${skill.repo}` - return skill.name ? `${collectionSkillLabel(skill)} · ${source}` : source -}