This commit is contained in:
197
docs/ux-refactor/_phase1-contract.md
Normal file
197
docs/ux-refactor/_phase1-contract.md
Normal file
@@ -0,0 +1,197 @@
|
||||
# UX-Refactor Phase 1 — Shared CONTRACT (binding)
|
||||
|
||||
> Authored by the Phase-1 contract agent (the ONLY agent that edits
|
||||
> `packages/shared/src/types.ts` + `apps/web/src/lib/adapter.ts` +
|
||||
> `apps/web/src/lib/types.ts` this run). Leaf builders (S01/S02/S03 FE + the
|
||||
> sidecar route leaves) and the integrator MUST align to the names + shapes below.
|
||||
>
|
||||
> Grounded in: `shared-types-delta.md §9`, `backend-api-delta.md` Phase-1 group,
|
||||
> gap cards `S01 §6`, `S02 §6`, `S03 §6`. Mockups are directional; PRD acceptance wins.
|
||||
>
|
||||
> **Verification:** `npx tsc --build packages/shared` → exit 0;
|
||||
> `npx tsc --noEmit --project apps/web/tsconfig.json` → exit 0 (adapter + FE types
|
||||
> resolve against the rebuilt shared dist). The `/api/*` routes do NOT exist yet —
|
||||
> the adapter methods compile as fetch wrappers; the route leaves build the servers.
|
||||
|
||||
---
|
||||
|
||||
## 1. New shared types — `packages/shared/src/types.ts`
|
||||
|
||||
Added as a `// === UX-Refactor Command vocabulary (PRD §12.3 / shared-types-delta §9) ===`
|
||||
block. Exported via the barrel (`packages/shared/src/index.ts` `export * from './types.js'`),
|
||||
so `import { … } from '@waggle/shared'` resolves them. **String-literal unions, all exported.**
|
||||
|
||||
> Phase-0 already shipped the §15.2 unions (`WorkspaceType`, `Scope`, `Confidence`,
|
||||
> `MemoryKind`, `ArtifactKind`, `AgentType`, `AutonomyLevel`, `ExtensionType`) and
|
||||
> `WorkspaceConfigV2` in this same file. This run adds ONLY the Command vocabulary.
|
||||
|
||||
```ts
|
||||
export type CommandCategory =
|
||||
| 'search' | 'launch' | 'create' | 'run' | 'navigate' | 'extend';
|
||||
|
||||
export type CommandResultType =
|
||||
| 'workspace' | 'memory' | 'artifact' | 'session' | 'person'
|
||||
| 'agent' | 'skill' | 'command' | 'connector' | 'mcp' | 'automation';
|
||||
|
||||
export interface CommandAction {
|
||||
route?: string;
|
||||
endpoint?: string;
|
||||
payload?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export interface CommandResult {
|
||||
id: string;
|
||||
type: CommandResultType;
|
||||
title: string;
|
||||
subtitle?: string;
|
||||
category: CommandCategory;
|
||||
icon?: string;
|
||||
requiresApproval?: boolean; // §12.3 permission-gated → approval prompt
|
||||
action?: CommandAction;
|
||||
}
|
||||
|
||||
export interface Command { // execute request body
|
||||
id?: string; // structured command resolves via id
|
||||
input?: string; // OR natural-language command string
|
||||
category?: CommandCategory;
|
||||
type?: CommandResultType;
|
||||
workspaceId?: string;
|
||||
payload?: Record<string, unknown>;
|
||||
}
|
||||
```
|
||||
|
||||
> **Field-name note (binding):** `shared-types-delta §9` is the source of truth, so
|
||||
> `CommandResult` uses `category: CommandCategory` + `type: CommandResultType`.
|
||||
> Gap card S03 §6 sketched alternate field names (`kind`/`objectType`) — those are
|
||||
> SUPERSEDED. Leaves must use `category`/`type`. S03's local `SearchResult`
|
||||
> interface (`GlobalSearch.tsx:17-25`) is replaced by this shared `CommandResult`.
|
||||
|
||||
---
|
||||
|
||||
## 2. New FE view-model types — `apps/web/src/lib/types.ts`
|
||||
|
||||
Added in a `// ── UX-Refactor Phase 1 view-models ──` block. The top of the file
|
||||
now re-exports the shared Command vocabulary so palette FE can import the whole
|
||||
contract from `lib/types`:
|
||||
|
||||
```ts
|
||||
import type {
|
||||
WorkspaceType, CommandCategory, CommandResultType, CommandResult, CommandAction,
|
||||
} from '@waggle/shared';
|
||||
export type { CommandCategory, CommandResultType, CommandResult, CommandAction };
|
||||
```
|
||||
|
||||
### S01 — Home Cockpit
|
||||
```ts
|
||||
export interface RecentWorkspaceCard {
|
||||
id: string; name: string; group: string;
|
||||
summary?: string; lastActive: string;
|
||||
pendingCount: number; continueSessionId?: string;
|
||||
}
|
||||
export interface SuggestedAction {
|
||||
label: string; workspaceId: string; sessionId?: string; kind: string;
|
||||
}
|
||||
export interface UpNextItem {
|
||||
id: string; label: string; workspaceId?: string; at?: string;
|
||||
kind: 'event' | 'task' | 'schedule';
|
||||
}
|
||||
export interface HomeBriefing {
|
||||
greeting: string; userName?: string; date: string;
|
||||
recentWorkspaces: RecentWorkspaceCard[];
|
||||
suggestedActions: SuggestedAction[];
|
||||
upNext: UpNextItem[];
|
||||
activeModels?: string[];
|
||||
isFirstRun: boolean;
|
||||
/** P2 amendment — J08 (D6): personal-mind memories with status 'unreviewed'.
|
||||
* The FE types this optional for back-compat with pre-P2 sidecars. */
|
||||
needsReviewCount: number;
|
||||
}
|
||||
export interface OvernightFailure {
|
||||
id: string; label: string; automationId?: string; error: string; at: string;
|
||||
}
|
||||
export interface OvernightSummary {
|
||||
consolidated: number; artifactsCreated: number; automationsCompleted: number;
|
||||
failures: OvernightFailure[];
|
||||
window?: { from: string; to: string };
|
||||
}
|
||||
export interface QuickCaptureInput {
|
||||
kind: 'note' | 'task' | 'link' | 'file'; content: string; workspaceId?: string;
|
||||
}
|
||||
```
|
||||
|
||||
### S02 — Workspace Desktop
|
||||
```ts
|
||||
export interface WorkspaceStateItem {
|
||||
id: string; content: string; date?: string;
|
||||
freshness?: 'fresh' | 'aging' | 'stale';
|
||||
}
|
||||
export interface WorkspaceStateView { // FE mirror of server WorkspaceState
|
||||
active: WorkspaceStateItem[];
|
||||
openQuestions: WorkspaceStateItem[];
|
||||
pending: WorkspaceStateItem[]; // pending + blocked seed the Tasks tab
|
||||
blocked: WorkspaceStateItem[];
|
||||
completed: WorkspaceStateItem[];
|
||||
stale: WorkspaceStateItem[];
|
||||
recentDecisions: WorkspaceStateItem[];
|
||||
nextActions: SuggestedAction[];
|
||||
}
|
||||
export interface WorkspaceActivityEvent {
|
||||
id: string | number; ts: string; type: string; actor?: string; summary: string;
|
||||
}
|
||||
```
|
||||
|
||||
> The FE `Workspace` interface already carries the Phase-0 optional V2 fields
|
||||
> (`type`, `status`, `description`, `agentIds`, `connectorIds`, `mcpIds`,
|
||||
> `updatedAt`) — leaves consume those; do not re-add them.
|
||||
|
||||
---
|
||||
|
||||
## 3. New adapter methods — `apps/web/src/lib/adapter.ts`
|
||||
|
||||
Pattern matched to existing siblings: thin `await this.fetch(path[, init])` →
|
||||
`res.json()`. Base URL + bearer-token + 403-tier handling all come from the
|
||||
shared `this.fetch()` wrapper. All exposed on the `LocalAdapter` instance
|
||||
exported as `adapter` (and the default export). Routes are net-new (do not exist
|
||||
yet) — these compile and run as fetch wrappers; calls will 404 until the route
|
||||
leaves land.
|
||||
|
||||
| Method (signature) | HTTP | Endpoint | Screen |
|
||||
|---|---|---|---|
|
||||
| `getHomeBriefing(): Promise<HomeBriefing>` | GET | `/api/home/briefing` | S01 |
|
||||
| `getHomeOvernight(since?: string): Promise<OvernightSummary>` | GET | `/api/home/overnight[?since=<iso>]` | S01 |
|
||||
| `quickCapture(input: QuickCaptureInput): Promise<{ frameId: string }>` | POST | `/api/quick-capture` | S01 |
|
||||
| `getWorkspaceState(id: string): Promise<WorkspaceStateView>` | GET | `/api/workspaces/:id/state` | S01, S02 |
|
||||
| `getWorkspaceActivity(id: string, limit = 50): Promise<{ events: WorkspaceActivityEvent[] }>` | GET | `/api/workspaces/:id/activity?limit=` | S02 |
|
||||
| `commandSearch(q: string, scope?: string): Promise<{ results: CommandResult[] }>` | GET | `/api/command/search?q=[&scope=]` | S00, S03 |
|
||||
| `commandRecent(): Promise<{ recent: CommandResult[] }>` | GET | `/api/command/recent` | S03 |
|
||||
| `commandSuggestions(): Promise<{ suggestions: CommandResult[] }>` | GET | `/api/command/suggestions` | S03 |
|
||||
| `commandExecute(payload: Command): Promise<{ ok: boolean; result?: unknown }>` | POST | `/api/command/execute` | S00, S03 |
|
||||
|
||||
### Binding notes for the route + FE leaves
|
||||
- **Response envelopes are exact.** `commandSearch` returns `{ results: CommandResult[] }`,
|
||||
`commandRecent` returns `{ recent: CommandResult[] }`, `commandSuggestions` returns
|
||||
`{ suggestions: CommandResult[] }` (distinct keys — do not collapse). `getWorkspaceActivity`
|
||||
returns `{ events: [...] }`. `getHomeBriefing` / `getHomeOvernight` / `getWorkspaceState`
|
||||
return the bare object (no envelope).
|
||||
- **B4 (founder-ratified):** `commandExecute` posts to the **singular** `/api/command/execute`,
|
||||
which the route leaf ALIASES onto the existing handler. The existing plural
|
||||
`/api/commands/execute` (`adapter.executeCommand`) is NOT renamed and stays as-is.
|
||||
- **C9:** the Command Center permission prompt (`CommandResult.requiresApproval`) reuses the
|
||||
chat approvals pipeline (`useChat().pendingApproval` / `ApprovalRequest`), not a new mechanism.
|
||||
- **C5/C7 do not change this contract** but constrain consumers: Overview chat is a read-only
|
||||
preview deep-linking to the Chat tab; Tasks tab seeds from `WorkspaceStateView.pending` +
|
||||
`.blocked`.
|
||||
- **Artifact/agent facets** in `CommandResultType` are intentionally present-but-gated — S05/S09
|
||||
substrate is not built yet; command search ships with the available facets only.
|
||||
|
||||
---
|
||||
|
||||
## 4. Files touched this run (scope lock)
|
||||
|
||||
- `packages/shared/src/types.ts` — added Command vocabulary block.
|
||||
- `apps/web/src/lib/types.ts` — added Phase-1 FE view-models + shared re-export.
|
||||
- `apps/web/src/lib/adapter.ts` — added 9 Phase-1 methods + 2 import lines.
|
||||
- `docs/ux-refactor/_phase1-contract.md` — this note.
|
||||
|
||||
NOT touched (by contract): `Desktop.tsx`, `lib/dock-tiers.ts`, any `index.ts` route
|
||||
registrar, any `routes/*.ts`, any component. Those belong to the leaf builders.
|
||||
Reference in New Issue
Block a user