feat(journal): right-rail captures panel + manual curator trigger (Phase 1b)

Frontend half of the conversation+curator architecture. Pairs with the
backend in commit a7002a8. With this commit, you can have a journal
conversation (chat model has no tools, doesn't try to capture), then
press a button and see what the curator extracts.

JournalView.vue:
- New "Captures" section in the right rail, above the existing
  "Upcoming" events block. Shows moments from the selected day with
  timestamp, content, and entity/task/note chips.
- "Process captures" button (Sparkles icon). Disabled for non-today
  days because we're not back-running the curator over historical
  conversations. Toast on success/failure with timing + tool-call
  count from the CuratorRunResult.
- Captures auto-load on day change AND immediately after a curator
  run completes — the right rail reflects current state without a
  page reload.
- Bound CSS scoped to the rail: cards with a primary-color left
  border, monospaced timestamps, chips for people/places/tasks/notes.

api/client.ts:
- CuratorRunResult type matching the backend dataclass.
- runJournalCurator(convId) helper.
- Pass empty body to apiPost() to satisfy the 2-arg signature
  (caller-side fix, not a backend change).

What's not in this commit (deferred):
- The captures panel doesn't show captures from days where the curator
  hasn't run yet, even if they would later be captured. Visible only
  AFTER a curator pass. (Phase 2's scheduler closes this gap by
  running automatically.)
- No edit/delete affordances on captures yet — that comes when we
  add the moment-editing UI (out of scope for the conversation+curator
  architecture commit chain).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-22 10:04:56 -04:00
parent a7002a89a0
commit a73dd17a1b
2 changed files with 229 additions and 4 deletions
+22
View File
@@ -401,6 +401,28 @@ export async function triggerJournalPrep(date?: string): Promise<{ ok: boolean;
return apiPost('/api/journal/trigger-prep', date ? { date } : {});
}
export interface CuratorRunResult {
conv_id: number;
user_id: number;
model: string;
messages_examined: number;
tool_calls: Array<{
name: string;
arguments: Record<string, unknown>;
status: 'success' | 'error' | 'pending';
error: string | null;
}>;
tools_attempted: number;
tools_succeeded: number;
summary: string;
duration_ms: number;
error: string | null;
}
export async function runJournalCurator(convId: number): Promise<CuratorRunResult> {
return apiPost<CuratorRunResult>(`/api/journal/curator/run/${convId}`, {});
}
export async function listJournalMoments(params: Record<string, string | number | boolean> = {}): Promise<JournalMoment[]> {
const qs = new URLSearchParams();
for (const [k, v] of Object.entries(params)) qs.set(k, String(v));