design phase 7.3 hub ui - sinrimin/harness-nexus GitHub Wiki
Status: implemented in Phase 7.3 (โ ). PRD:
prd-phase-7-skills.mdยง7.3. Prior designs:phase-7.1-plugin-source.md(storage) +phase-7.2-marketplace-fetch.md(browse backend). This is the UX closed loop.
The browse surface for 7.2's marketplace catalog. A user opens /skills/hub,
picks a marketplace, filters by category / free text, sees each plugin's trust
tier, and saves an entry as a plugin-source skill resource (the 7.1 variant)
โ which then appears in /resources and can be referenced from a profile via
skill:<name>.
/skills/hub (SkillHubPage)
โ
โ mount โ api.listMarketplaces() [once, from 7.2 allowlist]
โ selectedMkt/category/q change
โ โ api.listMarketplacePlugins(id, {category, q}) [7.2, server cached]
โผ
<Table>
row: Name + description | Category badge | Source kind (mono) | Trust | [Save as skill]
โ
client-side resolveTrustTier(plugin.source) โ
โผ
<SavePluginDialog>
key/scope/targets
+ warn callout
(community & no pin)
โ
api.createResource({kind:'skill', source, ...})
โผ
/resources row (kind=skill)
No new backend. The page consumes 7.2's two endpoints + 7.1's POST /api/resources.
Mirrors the ResourcesPage / ProfilesPage shape (AppShell wrapper + Card +
filter bar + Table). Three module-scope sub-components (defined at module
scope, never inline โ per the React perf rule in AGENTS.md):
-
SkillHubPageโ owns state:marketplaces,selectedMkt,category,q,items,saving. TwouseEffects: one loads the allowlist on mount (auto-selecting the first), the other refetches plugins when selectedMkt/category/q change (Pattern B from Resources). Filter state isuseState, not URL params (matches every existing page). -
PluginRowโ one catalog row. Trust computed viaresolveTrustTier(plugin.source)(client-side; the server recomputes authoritatively at save time viawithTrustLabels). -
TrustBadgeโ neutral-variant badge (see Decision 1 below). -
SavePluginDialogโ the lightweight save form (see Decision 2).
The Signal design system (AGENTS.md "Web UI design system") reserves --signal
(cyan) for liveness โ "Spend that accent in one place per view; do not sprinkle
it as decoration." A trust tier is not a liveness concept, so the badge uses
the same neutral pattern as the scope badge on every other page:
-
trusted โ
Badge variant="default"(ink/primary) +ShieldCheckIcon -
community โ
Badge variant="secondary"(muted surface) + a small gray dot
This is a deliberate choice over bg-signal text-signal-foreground (which is
technically available). The PRD's earlier "signal accent for trusted" wording
is superseded by this decision โ recorded here so the next contributor doesn't
"fix" it toward cyan.
Each row has a "Save as skill" button that opens a small SavePluginDialog
asking only for key / scope / targets. The source, name,
description come from the marketplace entry (converted via
marketplacePluginToResourceSource). This deliberately does not route
through the ResourceEditor on /resources:
- The
ResourceEditoris built for inline authoring (markdown body, bundle files) โ none of which applies to apluginsource (there's no body). - Routing through it would require extending its
computedSourcewith apluginbranch + accepting prefilled inputs โ larger surface area for a worse fit. - The inline dialog keeps the hub self-contained: browse โ save โ done, without a context switch.
The existing ResourceEditor therefore still emits only inline /
inline-bundle; plugin-source skills are created exclusively via the hub
(data model allows them everywhere; the editor just doesn't produce them).
MarketplacePlugin has no trust field (7.2's catalog schema doesn't carry
it). Rather than add it to the 7.2 endpoint response (which would change a
shipped shape), the page imports resolveTrustTier from @harness-nexus/sdk
(re-exported from @harness-nexus/shared) and computes the tier for display from
the plugin's source. The same function runs server-side in withTrustLabels
at save time, so the displayed badge and the stored labels.trust agree.
packages/shared/src/schemas/marketplace.ts exports
marketplacePluginToResourceSource(plugin, pluginName?):
MarketplacePlugin โ PluginResourceSource (the ResourceSource.plugin
shape, mirrored in shared because shared can't import core โ same layering
pattern as TrustTier). The one wrinkle: the marketplace github source
carries commit AND sha (aliases per research); the storage shape only has
sha, so commit folds into sha when sha is absent. git-subdir / url
map 1:1. npm is in the storage union but never produced here (the live
catalog has no npm entries).
SavePluginDialog shows a text-warn callout at the top when the source is
community trust AND has no pin (sha absent and version absent). This is
the supply-chain drift warning from the PRD โ a floating community reference
tracks upstream HEAD, which is a risk surface. The callout uses
TriangleAlertIcon + border-warn/40 bg-warn/10, matching the warn pattern
in guards.tsx. Pinned entries (sha or version present) and trusted entries
show no callout.
-
Route (
App.tsx):<Route path="/skills/hub" element={<RequireAuth> <SkillHubPage/></RequireAuth>} />โ any authenticated user may browse (the marketplace is a public catalog; saving still respects the normal resource scope rules). -
Nav (
app-shell.tsx): one entry innavItems()after/resources:{ to: '/skills/hub', icon: <StoreIcon/>, label: 'Skill hub' }. The mobile drawer reuses the same array, so one addition covers both surfaces. -
SDK: no new SDK methods (7.2's
listMarketplaces/listMarketplacePlugins+ 7.1'screateResourcecover it). The page importsresolveTrustTier/marketplacePluginToResourceSource/TrustTierre-exported through@harness-nexus/sdk.
7.3 is pure frontend. The backend (7.2 fetch + 7.1 plugin source) is covered by the smoke suite (121 passed, 0 failed โ unchanged in 7.3). For the UI, the manual acceptance flow:
- Register (first user โ admin), land on dashboard.
- Click "Skill hub" in the sidebar โ marketplace loads.
- Verify the 4 object-source plugins show (the 2 string-source entries are filtered by the catalog service).
-
category=securityโ 2 rows;q=artifactโ 1 row (jfrog). - Click "Save as skill" on jfrog โ dialog with
key=skill:jfrog, targets, scope. (No warn callout โ jfrog has a sha.) - Save โ success toast โ the row appears on
/resourcesaskind=skill.
All verified green on a memory + fixture server.
- Multi-source search (skills.sh, direct URL, custom taps alongside the marketplace) โ Phase 7.4. 7.3 browses one marketplace catalog at a time.
-
The
ResourceEditorgaining apluginbranch โ not needed; the hub is the plugin-source entry point. If inline editing of a plugin source later makes sense, extendcomputedSourcethen. - URL-param filter persistence โ no existing page does this; 7.3 matches.
- Content scanning / private marketplace credentials โ Out of Scope.