MalloryEstate Briefing
M·010Approved
← All briefings

Context & Investigations

Investigations View

A unified "Digs | Loops" bottom sheet so Alex can see in-flight, finished, and failed investigations — with reports rendered natively and pushes that deep-link straight to the answer.

Dated 2026-06-10Programme Context & Investigations

Situation

Step 3 of the materialised-context architecture. Step 2 built the investigate data + notification plumbing but explicitly left the UI out of scope; this spec supersedes that placeholder. Goal: give Alex a place to see investigations pending/running, finished (distilled answer + full cited report, rendered natively), and failed — and make the investigation_complete push deep-link to the relevant dig instead of opening /.

Resolved in brainstorming: a unified bottom sheet ("Digs | Loops" segments) opened from a persistent tray handle under the chat input, replacing the existing standalone Loops sheet rather than adding a second surface. Swipe-pane, tab-bar, and route-based variants were all considered and rejected as un-unifying.

Key decisions

Unified tray + sheet

One tray handle shows live counts ("Digs 1 · Loops 3"); opening it reveals a segmented "Digs | Loops" sheet, and the old standalone Loops sheet migrates in wholesale.

D1 as list source

A new SSO-gated read endpoint over the investigations table — deriving from messages type='investigation' was rejected because it can't represent pending/running/failed.

Event-driven freshness only

Fetch on app load, sheet open, window focus, and push-triggered service-worker refresh — deliberately no polling.

Reports render natively

The runtime includes the full report body in its result callback; the Worker stores it in D1 (capped, truncated) as a frozen snapshot, while the vault note remains canonical via an obsidian:// link.

"Intel" naming (UI review amendment)

The segment reads "Intel" in all UI copy while internal identifiers keep digs; a --gold token marks the report/artefact affordance.

Retry (UI review amendment)

Failed digs get a "Retry →" backed by a new SSO-gated POST /investigations/:id/retry, rejecting the runtime CN.

Architecture

An additive migration adds the report column; two new SSO-only endpoints expose the list and detail; the PWA gets a tray/sheet component pair and a sanitized markdown viewer.

migrations/007
ALTER TABLE investigations ADD COLUMN report TEXT — capped at 100KB, truncated server-side with a trailing marker.
GET /investigations, /investigations/:id
SSO-only read endpoints (must reject the runtime CN, mirroring claim/result rejecting SSO); list excludes report bodies for payload size.
web/src/lib/components/TraySheet.svelte
Tray (closed) + segmented sheet (open); chat fades to ~half opacity behind it but stays interactive.
web/src/lib/investigations.svelte.ts
State module (pattern: session.svelte.ts) driving the event-driven refresh triggers.
Report viewer
Full-screen overlay, markdown rendered via marked + sanitized with DOMPurify (reports quote untrusted web content), with an "Open in Obsidian" link.
web/src/service-worker.ts
Sets the push notification target to /?dig=<correlationId> and posts a message to open clients for immediate refresh.

Risks & guardrails

Untrusted report content

Sanitization (DOMPurify) is treated as non-negotiable since reports quote fetched web content verbatim.

Backwards compatibility

Pre-007 digs without a stored report fall back to answer + vault path + the obsidian:// link.

Least privilege both directions

Claim/result endpoints keep rejecting SSO tokens; the new read/retry endpoints keep rejecting the runtime CN.

Retention

Investigations are never deleted; the sheet lists the most recent 50, and reports remain in the vault permanently.

Delivery

Migration + db helpers

report column, capped list query.

SSO-only read endpoints

GET /investigations (list) and /investigations/:id (single, with report).

Result callback + runtime

Store the capped report; investigate.sh reads and includes it after a successful dig.

Web API client + state module

loadInvestigations()/loadInvestigation(id); investigations.svelte.ts.

TraySheet component — UI review gate

Built to a checkpoint requiring Alex's sign-off before proceeding (produced the naming/gold-token/retry amendments).

Push deep-link + report viewer + final PR

Service-worker deep-link wiring, sanitized viewer, then full verification.

Out of scope

Swipe-pane navigationSSE/WebSocket live status (event-driven refresh only)Rendering arbitrary vault notes (only stored investigation reports)Any Hub call from the Worker
spec · docs/superpowers/specs/2026-06-10-mallory-investigations-view-design.md
plan · docs/superpowers/plans/2026-06-10-mallory-investigations-view.md
Mallory · Estate briefing