# PR4 Recon · Slice 1 — Current Marketplace Front-End
> Read-only recon for PR4 (warm-Hive). Maps the **current** Marketplace FE against the
> screen-09 contract (Variation A browse+ask, Variation B inline-in-chat, shared install
> "sync" store). Cites real code; flags the delta; names exact integration points.
> Branch: `feature/warm-hive-pr4`. No source was modified.
---
## 1. What exists today
The Marketplace surface today is the **UX-Refactor Phase-4B "consolidated Extend
surface"** — a faceted *package/extension browser*, NOT the agent-searchable shelf the
design contract describes. There are two real surfaces:
- **A grid browser** — `MarketplaceApp.tsx` (route `/marketplace` → `MarketplaceRoute.tsx` → `SurfaceBoundary`).
- **An inline-in-chat install card** — `CapabilityRequestCard.tsx`, parsed out of agent
text by `capability-request-parser.ts` (`segmentText`) inside `TextBlock`.
These two surfaces are **independent** — they do **not** share an install store, do not
share micro-state vocabulary, and do not cross-reflect (installing in one does not update
the other). That is the single biggest structural gap vs the screen-09 "sync" mandate.
### 1a. The grid browser — `MarketplaceApp.tsx`
Layout (top to bottom), all in one file, 418 LOC:
- **Header** (`apps/web/src/components/os/apps/MarketplaceApp.tsx:273-331`): a `Store` icon
(honey via `var(--honey-500)`), title "Marketplace", and a right-aligned
`"{visible.length} extensions"` count (`:277`). This count is a *render-time array
length*, NOT a per-workspace "N in this workspace" chip bar.
- **Two tabs** — `Browse | Audit` (`:283-297`), `role="tablist"`. Audit renders
`InstallAuditPanel` (the C18 shared install-audit feed). Browse is the grid.
- **B7 facet rail** (`:302-317`): pills for `['all', ...EXTENSION_TYPES]` =
**All · Skills · Agents · Connectors · MCPs · Models · Templates** (7 facets, labels in
`FACET_LABELS` `:37-45`). `EXTENSION_TYPES` is canonical in
`packages/shared/src/types.ts:375-378` (`skill·agent·connector·mcp·model·template`).
- **A keyword search input** (`:319-328`) — a plain `` with a `Search` icon,
placeholder "Search skills, agents, connectors, MCPs, models, templates…". Debounced
300ms (`:202-209`); marketplace facets re-query the server, others client-filter.
- **Body** (`:334-385`): federated-provenance note (`:339-343`), loading / error+Retry /
empty states (honest, not fake-empty), then a vertical list of ``.
Per-card UI — `ExtensionCard.tsx`:
- A generic `Package` icon (NOT a per-kind badge), name, an Installed/Available
`StatusBadge`, optional scan-status badge.
- A row of small text chips: **`ext.type`** (this is the only "kind badge" today — a plain
text pill `:44`), optional category, optional trust, and a muted `ext.source` label.
- A single right-side action that is **lifecycle/kind-aware but NOT type-aware**:
- installable + not installed → **"Install"** (Download icon) — same label for every type.
- installable + installed (kind `package`) → **"Remove"** (Trash icon, destructive).
- federated (connector/agent/mcp-catalog/model/template) → **"Open in "** deep-link
(`ExternalLink`), e.g. "Connector Hub", "Agent Center", "MCP Hub" (`:72-79`).
- There is **no install count** rendered, no progress→done micro-states beyond a single
spinner on the Install button (`installing` → `` `:68`).
### 1b. Data flow — federate-at-read (A5)
The pure merge/normalize layer is `apps/web/src/lib/extension-catalog.ts` (no adapter
import — unit-testable). `loadFacet` (`MarketplaceApp.tsx:116-188`) fans out to per-domain
adapter calls based on the active facet, `Promise.allSettled`-merges, then
`sortExtensions`. The mapping (from the file header `extension-catalog.ts:7-14`):
| facet | adapter call | normalizer | install path | installable |
|-----------|------------------------------------------------|-----------------------|---------------------|-------------|
| skill | `getMarketplace({type:'skill'})` + `getMarketplacePacks()` | `fromMarketplacePackage` / `fromSkillPack` | real (POST /api/marketplace/install) | pkg ✓ / pack ✗ |
| mcp | `getMcps()` + `getMarketplace({type:'mcp'})` | `fromMcpCatalogRow` / `fromMarketplacePackage` | catalog → MCP Hub; registry-pkg → real | catalog ✗ / pkg ✓ |
| agent | `getPersonas()` | `fromPersona` | local (Agent Center) | ✗ |
| connector | `getConnectors()` | `fromConnector` | local (Connector Hub)| ✗ |
| model | `getModels()` | `fromModel` | local (Settings) | ✗ |
| template | `getWorkspaceTemplates()` | `fromTemplate` | local (Home) | ✗ |
Install actions (`MarketplaceApp.tsx`):
- `handleInstall` (`:224-251`) — only `kind==='package'` installs here; goes through the
shared `ApprovalModal` first (`buildInstallRequest` `:76-92`, scan-derived risk via
`installRiskFor`/`classifyInstallRisk`). 403 `TIER_INSUFFICIENT` → dispatches
`waggle:tier-insufficient` (UpgradeModal); 403 SecurityGate block → destructive toast.
On success it **optimistically flips local state** (`:231`) — this is the *only* "sync",
and it is purely local to this component's `extensions` array.
- `handleUninstall` (`:253-262`) — confirmed via a second `ApprovalModal`
(`buildRemoveRequest` `:65-74`).
- `handleOpenIn` (`:264-266`) — dispatches `waggle:open-app` for federated deep-links.
Adapter methods consumed (all in `apps/web/src/lib/adapter.ts`):
`getMarketplace` (`:2082-2092`, `/api/marketplace?type=…`), `getMarketplacePacks`
(`:1309-1313`), `installMarketplacePackage` (`:1338-1343`, `fetchRaw` so status survives),
`uninstallMarketplacePackage` (`:1345-1350`), `searchMarketplace` (`:1334-1336`, used by
the inline card), `getMcps` (`:2004-2011`), `getConnectors` (`:1958-1964`),
`connectConnector` (`:1971-1979`, `/api/connectors/:id/connect`), `installMcp`
(`:2017-2029`, `/api/mcps/install`), `getPersonas`, `getModels`, `getWorkspaceTemplates`,
`getExtendAudit` (`:2095-2103`, C18 audit feed).
### 1c. The inline-in-chat card — `CapabilityRequestCard.tsx`
Parsed from agent text by `capability-request-parser.ts:segmentText` — two patterns: a
structured HTML marker `` (Pattern A) and a legacy
`` `install_capability` with name "X" and source "Y" `` phrasing (Pattern B). Card shows
name, a source pill, optional `reason` ("why"), and Install/Dismiss. Phases:
`pending → installing → installed | declined | failed` (`:17`). For marketplace kind it
**resolves name → packageId via `searchMarketplace`** then `installMarketplacePackage`;
for skill kind it calls `adapter.installPack`. 403 → `waggle:tier-insufficient`.
This card is the seed of screen-09 Variation B but it is **skill/marketplace-only** (no
connector or MCP kind), has **no vault-aware approval** ("token goes to your vault"), no
"connected follow-up", and shares **no state** with the grid.
### 1d. Test coverage — `phase4b-marketplace-extend.test.tsx`
11 tests, all green, pinning the *current* Phase-4B contract: federate-at-read of all six
domains, honest federated notes, browse-only packs (no pack install testid), registry-MCP
packages install through the real route, ApprovalModal-gated install with scan-derived
risk, 403-tier-vs-403-securitygate split, all-backends-down error+Retry, Remove confirm,
failed-uninstall does-not-flip-installed, and the Audit tab type filter. **Any PR4 rework
of MarketplaceApp must update / supersede these** — they assert the current testids
(`extension-install-pkg:7`, `extension-facets`, `federated-note`) and the
`{ type, limit:30 }` call shape.
---
## 2. Gap vs screen-09 Variation A (the delta)
| screen-09 element | current state | delta |
|---|---|---|
| **Centered agent-search bar** ("Describe what you want to do…" → "Ask the agent") | plain keyword `` that filters/queries the registry; no agent round-trip | MISSING — no NL→agent-suggestion call; search is literal substring/registry-`?query=`. |
| **Example chips** | none | MISSING. |
| **Agent-suggestion box** (recommends a connector + skill + tool, each with a "why" + install button) | none in the grid (only the unrelated inline `CapabilityRequestCard` carries a `reason`) | MISSING — no suggestion surface, no "why" reason rendered in the grid, no per-suggestion install. |
| **"N in this workspace" count bar (chips)** | a single render-time `"{visible.length} extensions"` text label `:277` | PARTIAL/WRONG semantics — it counts *visible filtered rows*, not *installed-in-this-workspace* capabilities, and is not a chip bar. |
| **Category filter All/Skills/Connectors/MCP** | 7-facet rail All/Skills/Agents/Connectors/MCPs/Models/Templates | SUPERSET — current has the 3 design facets *plus* Agents/Models/Templates. Design shows 4 (All/Skills/Connectors/MCP). PR4 must decide: collapse to the design's 3-shelf framing or keep the 6-domain superset. |
| **Card grid** (kind badge + name + desc + **install count** + Add button) | vertical *list* of cards; "kind badge" is a plain text `ext.type` pill; **no install count**; single "Install"/"Remove"/"Open in" action | PARTIAL — needs a real grid, a styled kind badge, an install-count field, and **type-aware** primary actions. |
| **Type-aware one-click flows** (skill Add→Adding…→Added; connector Connect→Signing in…→Connected ~1.1s token→vault; MCP Enable→Enabling…→Enabled) | one generic "Install" label + single spinner; connectors/MCPs are *federated deep-links* (Open-in), not in-place actions | MISSING — no per-type verb, no progress→done micro-states, no in-place connect/enable; connector/MCP installs currently *leave* the Marketplace. |
| **Toast + count-bar update per action** | install toasts fire; count bar does not track installed-count | PARTIAL — toasts ✓, count-bar-update ✗. |
| **Shared "sync" store** (one store powers grid + agent-pick + inline card; install anywhere reflects everywhere) | grid keeps a local `extensions` array; inline card keeps its own `phase`; no shared store, no cross-reflect | MISSING — the headline architectural gap. |
### Variation B (inline) delta
- `CapabilityRequestCard` exists but is **skill/marketplace-only** — no connector / MCP
kinds. Needs the same type-aware verbs as the grid (Connect/Enable).
- **No vault-aware approval** copy ("token goes to your vault"). The warm
`InlineApprovalCard` (`components/os/warm/InlineApprovalCard.tsx`) is the obvious
adoption target — it already renders `--honey-wash` + risk vocabulary + Approve/Not-now,
and connectors carry credentials via `connectConnector(id, {token,…})`.
- **No "connected follow-up"** state distinct from generic "Installed".
### Data availability for "install count"
The marketplace DB schema **does** persist a `downloads` (and `stars`) column
(`packages/server/src/local/routes/marketplace.ts:860`, `upsertPackage`), so an install
count is *backed by data*. But it is **not surfaced**: the FE `MarketplacePackageRow`
(`extension-catalog.ts:54-65`) omits `downloads`, `getMarketplace` returns
`packages: unknown[]`, and `Extension` has no `installs` field. PR4 would thread
`downloads` → `Extension.installCount` → `ExtensionCard`.
---
## 3. Warm-Hive tokens / components to adopt
MarketplaceApp/ExtensionCard are still on the **legacy semantic-token** vocabulary
(`bg-secondary/20`, `text-muted-foreground`, `border-border/30`, `var(--honey-500)`,
`bg-primary`). The warm-Hive PR1+ vocabulary they should migrate to:
- **Tokens** (defined `apps/web/src/waggle-theme.css`, used across `warm/*`):
`--honey`, `--honey-wash`, `--honey-line`, `--shadow-honey`, `--surface`, `--line`,
`--line-strong`, `--text`, `--text-2`, `--text-dim`, `--attention`.
- **`AskBar`** (`components/os/warm/AskBar.tsx`) — the warm full-width ask pill (honey "+",
⌘K hint, honey send). This is the direct fit for the screen-09 "centered agent-search
bar" (swap placeholder to "Describe what you want to do…", wire submit → agent
suggestion instead of free chat).
- **`InlineApprovalCard`** (`components/os/warm/InlineApprovalCard.tsx`) — the vault-aware
honey approval card for Variation B's connector token→vault approval.
- **`SectionLabel`**, **`IconTile`/`HexCheckTile`**, **`StatusBadge`**
(`components/ui/status-badge.tsx`, already used) for kind badges / count chips.
- **`StreakChip`/`RunChip`** patterns (`components/os/warm/`) as the visual idiom for the
"N in this workspace" chip bar.
- All warm atoms are barrel-exported from `apps/web/src/components/os/warm/index.ts`.
---
## 4. Exact integration points a PR4 build would touch
- **Grid host**: `apps/web/src/components/os/apps/MarketplaceApp.tsx` — `MarketplaceApp`
component; `loadFacet` (fan-out/merge), `handleInstall`/`handleUninstall`/`handleOpenIn`,
`buildInstallRequest`/`buildRemoveRequest`/`installRiskFor`.
- **Card**: `apps/web/src/components/os/apps/extend/ExtensionCard.tsx` — `ExtensionCard`
(add kind badge styling, install count, type-aware action verbs + micro-states).
- **View-model + normalizers**: `apps/web/src/lib/extension-catalog.ts` — `Extension`
interface (add `installCount`, an `installState` micro-phase, make connector/mcp
`installable`), `fromConnector`/`fromMcpCatalogRow`/`fromMarketplacePackage`,
`filterExtensions`, `sortExtensions`.
- **Inline card**: `apps/web/src/components/os/apps/chat-blocks/CapabilityRequestCard.tsx`
+ `capability-request-parser.ts` (`segmentText`, `CapabilityRequest`) — extend `kind`
to `'connector' | 'mcp'`, wire `connectConnector`/`installMcp`, vault-aware approval.
- **Shared store (NEW)**: nothing exists today. A PR4 "sync" store would be a new module
(e.g. a context/zustand/event-bus in `apps/web/src/lib/` or `providers/`) that both
`MarketplaceApp` and `CapabilityRequestCard` subscribe to; cross-reflect can also reuse
the existing `window.dispatchEvent('waggle:*')` event idiom already used for
`waggle:open-app` and `waggle:tier-insufficient`.
- **Adapter (existing, reuse)**: `apps/web/src/lib/adapter.ts` — `getMarketplace`,
`installMarketplacePackage`, `uninstallMarketplacePackage`, `searchMarketplace`,
`getConnectors`, `connectConnector`, `getMcps`, `installMcp`, `getExtendAudit`.
- **Types**: `packages/shared/src/types.ts` — `EXTENSION_TYPES` / `ExtensionType`
(`:375-378`) is the canonical facet list; decide whether the design's 4-facet shelf
collapses or wraps this 6-domain tuple.
- **Server (read-only context)**: `packages/server/src/local/routes/marketplace.ts`
(`/api/marketplace`, `/install`, `/uninstall`, `downloads` column),
`packages/server/src/local/routes/extend.ts` (C18 audit).
- **Route wrapper**: `apps/web/src/routes/MarketplaceRoute.tsx` (unchanged unless the shell
framing changes).
- **Tests to supersede**: `apps/web/src/test/phase4b-marketplace-extend.test.tsx`.
---
## 5. Risks
- **Test churn**: 11 existing tests pin current testids and the `{type,limit:30}` call
shape; a Variation-A rework rewrites most of them.
- **Facet framing decision**: design says 3 shelves (Skills/Connectors/MCP); code has 6
domains incl. Agents/Models/Templates with honest federated notes. Collapsing loses the
honesty work; keeping diverges from the design's "one simple shelf".
- **In-place connect/enable vs deep-link**: design wants connectors/MCPs installed *in the
Marketplace* (Connect→Connected, Enable→Enabled). Today they are federated deep-links to
Connector Hub / MCP Hub which own the real OAuth/security-scan/approval flows
(`connectConnector`, `installMcp` w/ SecurityGate). Doing it in-place must NOT bypass
those flows — risk of duplicating or weakening security gating.
- **"Sync" consistency**: a shared store must reconcile optimistic flips with server truth
(install can 403/securitygate-block after the optimistic flip) across both surfaces.
- **Install count truthfulness**: `downloads` is local-single-user and often 0; rendering
it as a social "install count" may be misleading (mind-isolation / honest-stats ethos
the repo enforces elsewhere).
---
## 6. Open questions for the lead
1. **Facets**: collapse to the design's All/Skills/Connectors/MCP (3 shelves), or keep the
6-domain superset (Agents/Models/Templates) with federated notes?
2. **In-place vs deep-link** for connector/MCP: do Connect/Enable happen *inside*
Marketplace (new in-place flow that must reuse the Hub security/OAuth paths), or stay
deep-links? The design clearly wants in-place.
3. **Shared "sync" store** mechanism: new context/store module vs reuse the existing
`window` CustomEvent bus? What's the source of truth (server re-read vs optimistic +
reconcile)?
4. **"N in this workspace"**: counts *installed* capabilities (which backend gives the
per-workspace installed set?) — is `getExtendAudit` / a capabilities-status read the
source, or a new `/api/marketplace?installed=true&workspace=…`?
5. **Install count**: surface `downloads` honestly, or omit it given local-single-user
data is ~0 and the repo's honest-stats ethos?
6. **Agent-suggestion box**: which backend produces the connector+skill+tool
recommendation with a "why"? Is there an agent/LLM route to call, or is this a curated
heuristic over the registry?
7. **Inline Variation B**: extend `CapabilityRequestCard` in place, or replace it with a
shared picker component used by both grid and chat?