moving
Some checks failed
Installer Smoke / installer-smoke (push) Has been cancelled

This commit is contained in:
Oleg Maslov
2026-09-02 10:10:29 +02:00
commit 0c3e2ead3b
3841 changed files with 970576 additions and 0 deletions

View File

@@ -0,0 +1,243 @@
# Gap Card — S05 Artifact Center
> Execution model: **in-place incremental refactor** of `apps/web` + targeted backend extensions.
> Mockups are directional; PRD acceptance criteria win over pixels (PRD §24).
> Sources: PRD §12.5 + §16.6, blueprint p.32 (`_blueprint_extracted.txt:306-317, 525, 547`),
> mockup `screen_05_artifact_center.png`, baseline inventories under `docs/ux-refactor/_inventory/`,
> backend-map `sections/04-feature-map.md`.
---
## 1. Screen & purpose
The **Artifact Center** is the **outcome layer**: it organizes *outputs* (documents, presentations,
spreadsheets, dashboards, research, code, media, designs, other) as first-class **relational objects**
not file attachments. PRD §12.5 purpose verbatim: "Organize outcomes, not just attachments." The
headline acceptance criterion (PRD line 532): **searching a topic (e.g. "Germany GTM") returns all
relevant outcome objects plus related memories, sessions, tasks, agents, and people — not just files.**
Mockup (directional): left filter rail (type / status / workspace / tag facets), a center **artifact
table** (icon, title, type, workspace, status badge, updated, owner) with a top search bar + view
toggle + pagination, and a right **detail panel** (preview thumbnail, metadata, related items, actions).
This is a **data-heavy table+detail screen**, the same shape the blueprint flags as acceptable in light
variant (`_blueprint_extracted.txt:484`).
The defining difference from today's Files app: an artifact is an **outcome with relations**
(`relatedMemoryIds / relatedSessionIds / relatedTaskIds / relatedAgentIds`,
`generatedByAgentId`, `status: draft|final|shared|generated`), addressable by a stable `id`, spanning
**all storage backends and all formats**. Today's Files app exposes only a raw filesystem tree scoped to
one workspace + one storage tab.
---
## 2. Required states (PRD / Blueprint)
PRD §12.5 functional requirements:
- Artifact **categories** (= `ArtifactKind`, PRD §15.2 line 950): `document | presentation |
spreadsheet | dashboard | research | code | media | design | other`.
- **Topic search returns artifacts + related memories/sessions/tasks/agents/people** (the §16.6
`GET /api/artifacts/search-related?q=` contract).
- **Detail panel**: preview, metadata, workspace, creator, updated time, access, status, tags, related
items, actions.
- **Actions**: open, share, duplicate, move, delete, relate to workspace/memory/session/task.
State model — PRD §14.1 global states (every major screen): Loading, Empty, Populated, Error,
Offline/local-only, Syncing, Permission denied, Partial data, Approval required.
Plus the **artifact-specific states** (PRD §14 line not enumerated but blueprint `:312-314, 458`):
**Draft, Final, Shared, Generated, External-missing (source unavailable), Permission denied.**
So the concrete states to build:
1. Loading (skeleton table + skeleton detail).
2. Empty ("no artifacts yet" — first-run / no outputs produced).
3. Populated (table + facets + detail).
4. Error (fetch failed).
5. Offline/local-only (sidecar unreachable — mirror FilesApp offline banner pattern).
6. Permission denied (team-scoped artifact the user can't view).
7. Per-row status badges: Draft / Final / Shared / Generated.
8. Source-unavailable (artifact row whose backing file/url is missing — show broken-link affordance).
---
## 3. Current state in repo (disposition: **create-new** for the screen; **keep-promote** the substrates)
**There is NO Artifact entity, type, route, or component anywhere.** Grep-confirmed:
- No `Artifact` type in `apps/web/src/lib/types.ts` (the only `Artifact` hit in `apps/web/src` is
`components/os/apps/memory/EvolutionTab.tsx`, referring to evolution `artifacts_json` — unrelated).
- No `artifacts` table in `packages/hive-mind-core/src/mind/schema.ts`.
- No `/api/artifacts/*` routes (grep over `packages/server/src/local/routes/*.ts`: 0 matches).
- Confirmed by `_inventory/substrate-types.md:259-266` ("**NO backing entity anywhere** … the single
largest entity gap") and `_inventory/backend-routes.md:476-484` (all 6 §16.6 rows PARTIAL/MISSING).
**The closest current surface is the Files app** (the current-component hint), which is **NOT an
artifact center** — disposition for it is **keep-as-is, do not retrofit**:
- `apps/web/src/components/os/apps/FilesAppTabs.tsx` — P16 three-tab (Virtual/Local/Team) wrapper that
remounts `FilesApp` per `storageType`. Storage-location switcher, not an outcome browser.
- `apps/web/src/components/os/apps/FilesApp.tsx` (735 LOC) — full file-manager: tree + list/grid +
preview + upload + rename/move/copy/delete + bulk ops + properties dialog + inline `VersionHistory`.
It is **path/workspace/storage-scoped** (`adapter.listFiles(workspaceId, currentPath)`), has no
cross-workspace aggregation, no type/status/relation model, no facet filtering by outcome kind.
- Sub-components `components/os/files/{FileTree,FilePreview,FileActions,FileUploadZone,SyntaxPreview,
WorkspaceRail}.tsx` — operate on `FileEntry` (`lib/types.ts:42-50`: `name/path/type/size/mimeType/
modifiedAt/createdAt`), a raw FS entry, not an outcome object.
**Three existing backend substrates the new Artifact layer must aggregate over (reuse, do not duplicate):**
1. **Workspace file registry** — `GET /api/workspaces/:id/files` (`workspaces.ts:594-606`) returns
`readFileRegistry(dataDir, id)` of `FileRegistryEntry { name, type, summary, sizeBytes, ingestedAt }`
(`routes/ingest.ts:125-130`). This is an **ingest log**, newest-first — closest thing to a
per-workspace "produced/ingested things" list, but no id, no status, no relations.
2. **Document version registry** — `GET /api/workspaces/:id/documents` +
`/documents/:name/versions` (`routes/documents.ts`, JSON at
`~/.waggle/workspaces/{id}/documents.json`, shapes `TrackedDocument`/`DocumentVersion`). Gives
versioning + size + createdAt keyed by name; already surfaced in FilesApp's `VersionHistory`
(`FilesApp.tsx:27-54`). No type/status/relations.
3. **Workspace storage files** — `GET /api/workspaces/:id/storage/files` + `/storage/read|write|delete`
(`workspaces.ts:885+`) — the actual byte store for virtual/local/team.
**Verdict:** the screen is **create-new** (`ArtifactCenter` is in PRD §20.3 "Create" list, line 1289).
The backend is **a thin net-new aggregation/normalization layer over the three existing stores** — no
new data store required (`_inventory/backend-routes.md:604-607`).
---
## 4. Frontend work
**New top-level app (dock id `artifacts`).** Register in `Desktop.tsx` `appConfig` + `renderAppContent`
switch, add `AppId` `'artifacts'` in `lib/dock-tiers.ts`, and a Work-bucket dock entry (the IA maps
Artifacts to the **Work** layer — `_inventory/frontend.md:360`). Do **not** route — this is a windowed
single-route desktop; opening is by `AppId` via `openApp` (`useWindowManager`).
Components to **create** (keep files small, ~200-400 LOC each per repo file-org rule):
- `components/os/apps/ArtifactCenterApp.tsx` — shell: search bar + view toggle + facet rail + table +
detail panel + pagination. Owns query/filter/selection state. Mirrors the FilesApp three-pane layout
idiom (rail / main / detail) so it feels native.
- `components/os/artifacts/ArtifactTable.tsx` (or `ArtifactRow.tsx` — blueprint names `ArtifactRow`,
`_blueprint_extracted.txt:487`) — list rows with icon/title/type/workspace/status badge/updated/owner.
- `components/os/artifacts/ArtifactFacetRail.tsx` — type/status/workspace/tag facet filters (left rail in mockup).
- `components/os/artifacts/ArtifactDetailPanel.tsx` — preview + metadata + related-items list + actions.
- `components/os/artifacts/ArtifactRelatedList.tsx` — renders related memories/sessions/tasks/agents;
clicking a related item should raise the relevant window via the existing `waggle:open-app`
CustomEvent (and/or `onContextRail` like FilesApp does, `FilesApp.tsx:206-208`).
**Reuse targets (do not rebuild):**
- Status/confidence badges, `Skeleton`, `Table`, view-toggle, `Pagination`, `HoverCard` — all exist in
`components/ui/*` (shadcn set, `_inventory/frontend.md:337-342`).
- Offline banner pattern + retry — copy from `FilesApp.tsx:384-389`.
- File preview for an artifact's backing file — reuse `components/os/files/FilePreview.tsx`.
- Empty-state idiom — `FilesApp.tsx:470-477`.
- Detail-panel metadata layout idiom — FilesApp Properties dialog (`FilesApp.tsx:667-729`).
- ContextRail for "show full context of this artifact" — `overlays/ContextRail.tsx` already exists
(extend `ContextRailTarget` with an `'artifact'` variant).
**New hook + adapter methods:**
- `hooks/useArtifacts.ts` — `{ artifacts, filters, setFilter, selected, select, search, refresh,
create, patch, remove, share }`; reads/writes through the adapter. Follow the `useMemory` shape
(`hooks/useMemory.ts`).
- Extend `lib/adapter.ts` (the single sidecar gateway, ~1930 LOC — new §16 methods land here per
`_inventory/frontend.md:380`) with: `getArtifacts`, `getArtifact`, `createArtifact`,
`patchArtifact`, `deleteArtifact`, `searchRelatedArtifacts`, `shareArtifact`.
**Props/state notes:** `ArtifactCenterApp` takes `{ workspaces?, activeWorkspaceId?, onSelectWorkspace?,
onContextRail? }` (same cross-workspace pattern FilesApp uses). It is **cross-workspace by default**
(the whole point vs FilesApp) — workspace becomes a *facet*, not a hard scope.
---
## 5. Backend work (PRD §16.6)
> No new data store. Every endpoint is a **net-new aggregation/normalization route** over the existing
> file registry + document versions + workspace storage. New file:
> `packages/server/src/local/routes/artifacts.ts`, registered in `local/index.ts`. The Artifact `id`
> can be a stable composite of `workspaceId + source-store + name/path` (or a registry-assigned id if a
> lightweight `artifacts.json` index is added per workspace, mirroring `documents.json`).
| PRD §16.6 endpoint | Status | Plan (EXTEND vs NET-NEW) + substrate |
|---|---|---|
| `GET /api/artifacts` | **PARTIAL → NET-NEW route** | No `/api/artifacts` domain. NET-NEW `GET /api/artifacts` in `artifacts.ts` that **fans out over `WorkspaceManager.list()`** and, per workspace, normalizes (a) file registry `GET /api/workspaces/:id/files` (`workspaces.ts:594`, `FileRegistryEntry`), (b) document versions `GET /api/workspaces/:id/documents` (`documents.ts`), into a unified `Artifact[]`. Supports `?workspaceId=&type=&status=&tag=&q=` facet filters. Cross-workspace = the differentiator. |
| `POST /api/artifacts` | **PARTIAL → NET-NEW route (thin)** | Closest writes that already persist bytes: `POST /api/ingest` (`ingest.ts`), `POST /api/workspaces/:id/files/upload` (`files.ts`), `POST /api/workspaces/:id/documents` (`documents.ts`), `POST /api/workspaces/:id/storage/write` (`workspaces.ts`). NET-NEW `POST /api/artifacts` records artifact metadata (kind/title/status/tags/relations + `generatedByAgentId`) in a per-workspace `artifacts.json` index and (optionally) writes the backing file via the storage route. |
| `GET /api/artifacts/:id` | **MISSING → NET-NEW** | Resolve composite id → normalized `Artifact` with relations + preview metadata. Reuse `documents.ts` version lookup for `relatedVersions`. |
| `PATCH /api/artifacts/:id` | **MISSING → NET-NEW** | Update title/status/tags/relations in the `artifacts.json` index (move = re-point `workspaceId`/`storagePath`; reuse `files/move`). |
| `DELETE /api/artifacts/:id` | **PARTIAL → NET-NEW route** | Closest: `POST /api/workspaces/:id/files/delete` (`files.ts`), `DELETE /api/workspaces/:id/storage/delete` (`workspaces.ts`). NET-NEW `DELETE /api/artifacts/:id` removes the index entry and (optionally) the backing file via those. |
| `GET /api/artifacts/search-related?q=` | **MISSING → NET-NEW (the headline endpoint)** | Federated search: query the normalized artifact index **plus** `GET /api/memory/search` (`memory.ts`), session search `GET /api/workspaces/:wid/sessions/search` (`sessions.ts`), tasks `GET /api/tasks` (`tasks.ts`), and fleet/agents (`GET /api/agents/active`/`/api/fleet`), returning grouped `{ artifacts, memories, sessions, tasks, agents }`. Internally can lean on existing FTS (`memory_frames_fts`, marketplace FTS5, wiki search). Delivers PRD line 532 acceptance. |
| `POST /api/artifacts/:id/share` (blueprint `:525`) | **MISSING → NET-NEW** | Blueprint adds a `/share` action not in PRD §16.6 list. Maps to the missing `POST /api/share` (`_inventory/backend-routes.md:551`) + team scope. Defer to the Team phase (see §7); gate behind TEAMS tier like `/api/team/*`. |
**Substrate touched:** workspace file registry (`ingest.ts` `FileRegistryEntry`), document versions
(`documents.ts` JSON), workspace storage (`workspaces.ts` storage routes), memory FTS
(`memory_frames_fts`), sessions JSONL, tasks store, fleet/orchestrator. **No `.mind` migration
required** for a metadata-first implementation: artifact metadata + relations live in a per-workspace
`artifacts.json` index (same pattern as `documents.json`). If artifacts must later be queryable in SQL
alongside frames, a future additive `artifacts` table in `schema.ts` follows the established
idempotent ADD-pattern (`mind/db.ts:116-124`) — flag, not now.
**Governance note:** if agent-generated artifacts (`generatedByAgentId`) need an audit trail, reuse
`InstallAuditStore`/`emitAuditEvent` (`workspaces.ts:631` already emits `workspace_update`) rather than
a parallel log.
---
## 6. Shared types needed (PRD §15.2 / §15.6 vs `lib/types.ts`)
**Net-new, none exist today** (`_inventory/substrate-types.md:226, 259-266`):
- `ArtifactKind` union (PRD §15.2 line 950) — add to `lib/types.ts` (and `packages/shared/src/types.ts`
if the sidecar route also imports it, to keep one contract).
- `ArtifactStatus = 'draft' | 'final' | 'shared' | 'generated'` (from blueprint states `:312`,
PRD §14 artifact states). Note blueprint also implies `external-missing`/`source-unavailable` —
model as a derived flag, not a status value.
- `Artifact` interface — PRD §15.6 (lines 1039-1056) blueprint `:547`: `id, title, kind, workspaceId,
teamId?, createdBy, generatedByAgentId?, source, status, mimeType?, storagePath?, previewUrl?,
summary?, tags[], relatedMemoryIds[], relatedSessionIds[], relatedTaskIds[], relatedAgentIds[],
createdAt, updatedAt`.
- `RelatedSearchResult` — the grouped `{ artifacts, memories, sessions, tasks, agents }` envelope for
`search-related`.
**Reconcile, don't fork:** define `Artifact`/`ArtifactKind`/`ArtifactStatus` **once** (shared package
preferred) so the sidecar route and the frontend hook share the contract — avoid the existing
FE↔BE `MemoryFrame` drift the inventory flags (`_inventory/substrate-types.md:246-247`). Optionally add
`relatedArtifactIds[]` to the memory side later (PRD §15.4) so the relation is bidirectional.
---
## 7. Dependencies (screens / phases first)
- **PRD Phase 2 — Work layer** (this screen's home; Memory Center is its sibling, Sprint 4). The
`search-related` federated endpoint is the binding dependency on Memory (FTS) + Sessions + Tasks.
- **AppShell / dock IA (Phase 0/1)** must exist first so `artifacts` registers as a Work-bucket dock
entry (consolidate on `AppId`, retire stale `AppView`).
- **Command Center (Ctrl+K) (Phase 1)** should index artifacts (`_blueprint_extracted.txt:515` — command
index unifies artifacts) — soft dependency; Artifact Center can ship before Ctrl+K wires it in.
- **`WorkspaceConfigV2` `type`/`status` fields** (S-workspace cards) help facet labels but are not
blocking.
- **Team Workspace / RBAC (Phase 5)** gates `POST /api/artifacts/:id/share` + permission-denied state.
Ship Artifact Center personal-scoped first; share/team-scope is a follow-on.
- **Agent run → artifact linkage** (PRD Journey 7, §15.6 `generatedByAgentId`) depends on the agent
runtime writing artifact records — a downstream integration, not a blocker for the read/browse screen.
---
## 8. Effort: **L**
Net-new full-stack surface: 5-6 new frontend components + a new hook + 7 new adapter methods, **plus**
a net-new backend aggregation domain (`artifacts.ts`, ~6 routes) that must normalize **three** existing
stores into one entity and a **federated** search across memory/sessions/tasks/agents. No new DB and
heavy component/route reuse keep it out of XL, but the cross-workspace aggregation + the
`search-related` federation + a brand-new shared `Artifact` contract make it clearly more than M.
---
## 9. Open questions
1. **Artifact identity & index.** Synthesize `id` as a composite (`workspaceId:store:name`), or add a
per-workspace `artifacts.json` index (mirroring `documents.json`) that assigns stable ids and holds
status/tags/relations? (PATCH/relations effectively require the latter.) → maps to PRD Open Q6
(storage: workspace FS vs virtual store vs external refs, PRD line 1421).
2. **What counts as an artifact in v1?** Only explicit outputs (generated docs/decks/etc.), or every
ingested file in the registry? The registry mixes ingested inputs with produced outputs — need a
classification rule for `kind`/`status`.
3. **`search-related` scope/cost.** Federating memory FTS + sessions + tasks + agents per query — cap
per-source result counts and run async result groups (PRD §24 perf mitigation), or a single indexed
provider? (Mirrors the Command Center search concern.)
4. **Share semantics (blueprint `/share` vs PRD `/api/share`).** Is `POST /api/artifacts/:id/share`
in-scope for the first Artifact Center cut, or deferred entirely to the Team phase?
5. **Preview generation.** `previewUrl`/thumbnails — generate server-side, reuse `FilePreview`
on-demand client-side, or skip thumbnails in v1 (icon + on-click preview only)?
6. **Bidirectional relations.** Do we add `relatedArtifactIds[]` to memory frames now (PRD §15.4) or
keep relations one-directional (artifact → others) in v1?