Skip to content

Digest UI redesign: two-bucket, source-aware, cited #481

@jayzalowitz

Description

@jayzalowitz

Digest UI redesign: two-bucket, source-aware, cited

Context

The reference product's value is mostly presentation: a scannable layout that opens
with a short "act on this" list, then themed "be aware" clusters, each line citing
its sources. SkyTwin already renders a briefing, but as a flat importance-ordered
list of decisions with no act/FYI split, no topic grouping, no source-type cues, and
no citations. The backend specs (01-07) produce the structured data — to-dos, topic
clusters, deadlines, per-signal source + citation — but none of it reaches the user
without a UI that renders it. This spec is the web + mobile surface for that data, and
it must be source-aware: an item from calendar, a file, or a voice note should
look and cite differently from an email, because SkyTwin is multi-source.

Current State

Verified 2026-06-06.

  • Routes (apps/web/public/js/app.js:33-54): #/renderDashboard,
    #/briefingrenderTwinBriefing, #/approvals, #/decisions. Pages render via
    template literals into a single reused #page-content (app.js:653-680).
  • Dashboard briefing card (apps/web/public/js/pages/dashboard-view.js:142-184):
    shows last scan's headline + "N handled on own, M waiting on you", lists up to 5
    items as a colored bar (green auto / yellow needs-approval) + description +
    reasoning + domain/confidence/urgency; overflow → "+N more on Decisions page".
    No act/FYI split, no topic grouping, no source cue, no citation.
  • Full briefing page (apps/web/public/js/pages/twin-briefing.js:180-264):
    Markdown prose, Daily/Weekly tabs, per-Lifebook collapsible sections, history
    sidebar. Prose is escaped text — no extracted to-dos, no source links.
  • Frontend conventions in force (CLAUDE.md:163-176, confirmed in code): no inline
    onclick; data-action attributes + delegated listener; singleton delegator wired
    once on document, gated by window.location.hash, behind a _listenerWired
    guard. Real examples: approvals.js:21-22,139-177 (_approvalsListenerWired,
    hash-checks #/approvals); decisions.js:11,216-237; twin-briefing.js:22,34-38
    (_briefingListenerWired, isOnBriefingRoute()); dashboard-view.js:581-650
    (initDashboardGlobals()).
  • CSS (apps/web/public/css/styles.css:469-535): .card, .card-header,
    .card-title, .badge/.badge-success|warning|danger|info, table styles, and a
    left-accent-border convention (border-left: 3px solid var(--…)). Enough primitives
    to build the redesign without a new design system.
  • API (apps/api/src/routes/twin-briefings.ts:34-98, briefings.ts):
    /api/twin-briefings/latest returns { briefing: { prose_markdown, generated_at, read_at }, sections: [...] }; legacy /api/briefing/:userId returns
    briefing.items[] = { wouldAutoExecute, actionDescription, reasoning, domain, confidence, urgency }. Neither returns a to-do/FYI flag, topic cluster, source
    type, or citation list yet
    — those come from specs 01/04/07.
  • Mobile (apps/mobile/src/screens/BriefingScreen.tsx:1-150,
    DashboardScreen.tsx): briefing headline card + unread badge + pending-approvals
    link; recent decisions list (limit 5). Same gaps as web.
  • Absent everywhere: to-do vs FYI grouping, topic sub-headings, source-type
    icons/labels, inline citations to originating signals.

Proposed Change

Redesign the briefing/digest surface (web #/briefing and dashboard card; mobile
BriefingScreen) to render the structured payload from specs 01/04/07:

  1. Two buckets, ordered: a To-dos section first (act-required, ≤7, urgency-
    ordered, each with deadline chip when present), then Topics (awareness,
    grouped by cluster from spec 04, each cluster collapsible).
  2. Source-aware rows: every item shows a source-type indicator — icon + label for
    email, calendar, file, voice, app — so the user sees at a glance that a
    to-do came from a voice note vs. an email. Drives home that SkyTwin is multi-source.
  3. Citations: each item renders its signalRefs (from spec 04/05) as small
    "source" chips linking to the originating signal/decision detail. Multiple sources
    → multiple chips (mirrors the reference product's "From: A · B", but every chip is
    a real in-app link, never a raw external URL — security, per spec 06 and safety
    invariant Live notification layer: SSE, approval expiry cron, push alerts #8).
  4. Provenance/urgency affordances: keep the existing auto/needs-approval color
    coding; add a small "untrusted source" marker on untrusted_external items so the
    user knows an item originated from inbound content.

Implementation Details

  1. API — extend /api/twin-briefings/latest (additive) to return the structured
    payload spec 01 persists:
    {
      "briefing": { "prose_markdown": "...", "generated_at": "...", "read_at": null },
      "structured": {
        "todos":  [{ "text","sourceType","signalRefs":[],"deadline":null,"provenance" }],
        "topics": [{ "domain","title","items":[{ "text","sourceType","signalRefs":[] }] }]
      },
      "sections": [ ... ]   // unchanged, for back-compat
    }
    structured is null for old briefings → UI falls back to prose.
  2. Web render — new renderDigest(container, data) (extend twin-briefing.js):
    • To-dos list, then topic clusters (reuse .card, .badge, left-accent-border).
    • Source indicator: a source-chip component keyed on sourceType (small CSS
      additions to styles.css, reuse badge tokens).
    • Citations: data-action="open-signal" + data-signal-ref chips; the delegator
      opens the decision/signal detail.
  3. Event wiring (MUST follow the codebase pattern) — one module-level delegator
    on document, guarded by _digestListenerWired, first line
    if (window.location.hash.split('?')[0] !== '#/briefing') return;. Read
    getCurrentUserId() inside the handler, not closed-over (CLAUDE.md:170-176). No
    inline handlers. This is the exact trap the CLAUDE.md frontend section warns about;
    the spec calls it out so the implementer doesn't stack listeners on re-render.
  4. Empty/degraded states: no to-dos → "Nothing needs you right now." No
    structured payload (old row / LLM down) → render existing prose. Unknown
    sourceType → neutral chip, never a crash.
  5. Mobile — mirror the two-bucket layout in BriefingScreen.tsx: a To-dos
    SectionList section + per-cluster Topic sections, source-type icon per row,
    citation chips navigating to decision detail. Pull-to-refresh stays.
  6. Accessibility — sections are real headings; source indicators have text labels
    (not icon-only); citation chips are buttons with accessible names ("source: email").

Acceptance Criteria

  1. Web #/briefing renders a To-dos section above a Topics section when structured
    is present; To-dos are urgency-ordered and capped at 7 with overflow disclosure.
  2. Each row shows a source-type indicator matching its sourceType
    (email/calendar/file/voice/app); unknown types render a neutral chip without error.
  3. Each row renders one citation chip per signalRef; clicking opens the in-app
    signal/decision detail. No chip ever links to a raw external URL.
  4. untrusted_external items show the untrusted marker.
  5. The digest delegator is wired once, gated on #/briefing; navigating away and
    back, and re-rendering after refresh, do not stack listeners or fire on other
    routes (verified: only one handler invocation per click after N re-renders).
  6. When structured is null, the page falls back to the existing prose render (no
    regression for historical briefings).
  7. Mobile BriefingScreen shows the same two-bucket, source-aware, cited layout.
  8. No inline event-handler attributes are introduced anywhere in the new markup.
  9. Tests written and passing. No degradation of existing functionality.

Testing Plan

Layer What Count
Unit renderDigest markup: section order, cap+overflow, source chip per type +5
Unit Citation chips are in-app links; external URL never rendered as chip +2
Unit Fallback to prose when structured is null +2
Unit (dom) Delegator wired once; hash-gating; no stacking across re-renders +3
Integration API /latest returns structured; UI renders it end-to-end +2
Mobile BriefingScreen two-bucket render + citation navigation +3

Rollback Plan

API field is additive and nullable; UI is gated on structured presence. Roll back
by having the API omit structured (or a DIGEST_UI=off flag) → both surfaces fall
back to today's prose/list render. No data migration. New CSS classes are unused when
the old path renders.

Effort Estimate

  • API structured passthrough: ~2h
  • Web renderDigest + source chips + citations: ~6h
  • Web delegator wiring (pattern-correct): ~2h
  • CSS (source chips, section layout): ~2h
  • Mobile BriefingScreen: ~5h
  • Tests: ~5h

Total: ~3 days (web ~1.5d, mobile ~1d, tests/API ~0.5d).

Files Reference

File Change
apps/web/public/js/pages/twin-briefing.js:180-264 New renderDigest + delegator (_digestListenerWired, hash-gated #/briefing)
apps/web/public/js/pages/dashboard-view.js:142-184 Dashboard card adopts to-do/FYI counts + source cues
apps/web/public/css/styles.css:469-535 Add source-chip, section, citation-chip styles (reuse tokens)
apps/api/src/routes/twin-briefings.ts:34-98 Add nullable structured to /latest
apps/mobile/src/screens/BriefingScreen.tsx Two-bucket source-aware cited layout

Out of Scope

  • Producing the structured data (todos/topics/sourceType/signalRefs) — specs
    01, 04, 05, 07 own that. This spec only renders it.
  • Editing/acting on to-dos inline (mark done, snooze). Future; this is read + navigate.
  • A full design-system overhaul — reuse existing card/badge primitives.

Related

  • Renders the output of specs 01 (to-do/FYI), 04 (clusters), 05 (citations), 07
    (source type). Depends on at least 01 + 07 landing first.
  • Follows the frontend conventions in CLAUDE.md:163-176 (singleton delegator,
    hash-gating, data-action, getCurrentUserId() inside handler).

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions