feat(home): workspace-wide "Catch me up" AI summary on the dashboard (LC-705) #671

Merged
longjacksonle merged 1 commit from feat/LC-705-workspace-catch-up into main 2026-08-13 18:57:17 +02:00

What

Adds a one-click "Catch me up" AI recap of everything the viewer missed across their whole workspace, rendered into a card at the top of the Home dashboard. Unlike the existing per-room "Catch me up" (routes::summary), this reads across every accessible room and DM in a single pass.

Why

The Home dashboard (LC-703) shows what you missed as a card grid, but scanning six cards is still work. This turns "what happened while I was away?" into one button: the AI reads recent unread across every channel and DM and returns a short summary + action items.

How

  • New workspace-level AI gate in ai_gate: allowed_workspace / require_llm_workspace / llm_catch_up_available, reusing the LC-702 flag + audience. "everyone" (default) admits any logged-in user; "staff" narrows to the site-admin role. A cross-room surface has no enclave or per-room override to consult, so "privileged" here reduces to site admin, the same floor privileged_in_room lands on for an enclave-less room.
  • Aggregation reuses db::inbox::list_unread, which returns only accessible rooms, so membership is enforced by the query itself (opening the audience never exposes a non-member's messages). It scans the newest 200 unread, groups per channel, and builds one prompt bounded by MAX_PROMPT_CHARS.
  • No cache, matching the per-room summary: each click regenerates. The result renders through the shared SummaryFragment, so the returned Regenerate button re-runs in place.
  • The entry card renders only when AI is available to the viewer AND there is unread to summarize. The POST re-checks the gate server-side, so the card is a UI affordance, not the access control. If the last unread was read between render and click, the handler returns a friendly caught-up fragment (200), not an error.

Resolved open questions (from the ticket)

  • Cap strategy: newest-200 unread workspace-wide, then trimmed to MAX_PROMPT_CHARS (48k) at prompt-build time.
  • Cache: none. Each click regenerates, same posture as the per-room and transcript summaries.

UI

  • New i18n keys home-catchup-ai-heading / -scope / -caught-up (en + es). Reuses the existing summary-* / ai-* strings for the Generate/Regenerate buttons, disclaimer, and the staged "reading..." pending affordance (driven by the global live.js handler, so it works on the dashboard).

Testing

  • just test: 197 binaries ok, 0 failed, 1664 tests passed.
  • 2 unit tests for the per-channel prompt builder (grouping, chronological ordering, DM labeling, empty-body skipping).
  • 4 route tests for the gate: flag off -> 403 even for an admin; everyone -> a plain member is summarized (200); staff -> member 403 while admin passes; empty workspace -> caught-up fragment (200).
  • cargo clippy clean under standalone and saas; cargo fmt --check clean; i18n en/es parity test passes.

Follow-on to

LC-704 (per-type preview icons) and LC-703 (Home dashboard redesign). This was split out of LC-704 because it is a new cross-room feature rather than a preview tweak.

🤖 Generated with Claude Code

https://claude.ai/code/session_016ufqbuN7mjyt4ms5NEygMy

## What Adds a one-click "Catch me up" AI recap of everything the viewer missed across their whole workspace, rendered into a card at the top of the Home dashboard. Unlike the existing per-room "Catch me up" (`routes::summary`), this reads across every accessible room and DM in a single pass. ## Why The Home dashboard (LC-703) shows *what* you missed as a card grid, but scanning six cards is still work. This turns "what happened while I was away?" into one button: the AI reads recent unread across every channel and DM and returns a short summary + action items. ## How - New workspace-level AI gate in `ai_gate`: `allowed_workspace` / `require_llm_workspace` / `llm_catch_up_available`, reusing the LC-702 flag + audience. `"everyone"` (default) admits any logged-in user; `"staff"` narrows to the site-admin role. A cross-room surface has no enclave or per-room override to consult, so "privileged" here reduces to site admin, the same floor `privileged_in_room` lands on for an enclave-less room. - Aggregation reuses `db::inbox::list_unread`, which returns only accessible rooms, so membership is enforced by the query itself (opening the audience never exposes a non-member's messages). It scans the newest 200 unread, groups per channel, and builds one prompt bounded by `MAX_PROMPT_CHARS`. - No cache, matching the per-room summary: each click regenerates. The result renders through the shared `SummaryFragment`, so the returned Regenerate button re-runs in place. - The entry card renders only when AI is available to the viewer AND there is unread to summarize. The POST re-checks the gate server-side, so the card is a UI affordance, not the access control. If the last unread was read between render and click, the handler returns a friendly caught-up fragment (200), not an error. ## Resolved open questions (from the ticket) - Cap strategy: newest-200 unread workspace-wide, then trimmed to `MAX_PROMPT_CHARS` (48k) at prompt-build time. - Cache: none. Each click regenerates, same posture as the per-room and transcript summaries. ## UI - New i18n keys `home-catchup-ai-heading` / `-scope` / `-caught-up` (en + es). Reuses the existing `summary-*` / `ai-*` strings for the Generate/Regenerate buttons, disclaimer, and the staged "reading..." pending affordance (driven by the global `live.js` handler, so it works on the dashboard). ## Testing - `just test`: 197 binaries ok, 0 failed, 1664 tests passed. - 2 unit tests for the per-channel prompt builder (grouping, chronological ordering, DM labeling, empty-body skipping). - 4 route tests for the gate: flag off -> 403 even for an admin; `everyone` -> a plain member is summarized (200); `staff` -> member 403 while admin passes; empty workspace -> caught-up fragment (200). - `cargo clippy` clean under `standalone` and `saas`; `cargo fmt --check` clean; i18n en/es parity test passes. ## Follow-on to LC-704 (per-type preview icons) and LC-703 (Home dashboard redesign). This was split out of LC-704 because it is a new cross-room feature rather than a preview tweak. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_016ufqbuN7mjyt4ms5NEygMy
feat(home): workspace-wide "Catch me up" AI summary on the dashboard (LC-705)
All checks were successful
check-secrets / Nosey parker (push) Successful in 4s
check-secrets / TruffleHog (push) Successful in 5s
check-secrets / Kingfisher (push) Successful in 7s
check-secrets / Nosey parker (pull_request) Successful in 4s
check-secrets / TruffleHog (pull_request) Successful in 4s
check-secrets / Kingfisher (pull_request) Successful in 12s
Check / clippy + fmt + tests (pull_request) Successful in 5m57s
Create release / Create release from merged PR (pull_request) Has been skipped
752c03fe40
Adds a one-click AI recap of everything the viewer missed across every accessible room and DM, rendered into a card at the top of the Home dashboard. Unlike the existing per-room "Catch me up" (routes::summary), this is cross-room: it gathers recent unread workspace-wide, groups it per channel, and summarizes in a single pass.

Gate: a new workspace-level AI gate (ai_gate::allowed_workspace / require_llm_workspace / llm_catch_up_available) reuses the LC-702 flag + audience. "everyone" (default) admits any logged-in user; "staff" narrows to the site-admin role (a cross-room surface has no enclave or per-room override to consult, so "privileged" reduces to site admin, the same floor privileged_in_room lands on for an enclave-less room). The POST re-checks the gate server-side, so the card is a UI affordance, not the access control.

Aggregation reuses db::inbox::list_unread (accessible rooms only, so membership is enforced by the query), scans the newest 200 unread, and builds one per-channel-grouped prompt bounded by MAX_PROMPT_CHARS. No cache, matching the per-room summary: each click regenerates. The result renders through the shared SummaryFragment, so the returned Regenerate button re-runs in place. The entry card shows only when AI is available to the viewer AND there is unread to summarize; the handler still returns a friendly caught-up fragment (not an error) if the last unread was read between render and click.

New i18n keys home-catchup-ai-heading / -scope / -caught-up in en and es; reuses the existing summary-* / ai-* strings for the button, disclaimer, and pending affordance.

Tests: 2 unit tests for the per-channel prompt builder (grouping, chronological ordering, DM labeling, empty-body skipping) and 4 route tests for the gate (flag off -> 403 even for admin; everyone -> member summarized; staff -> member 403 / admin 200; empty workspace -> caught-up 200).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016ufqbuN7mjyt4ms5NEygMy
longjacksonle scheduled this pull request to auto merge when all checks succeed 2026-08-13 18:52:21 +02:00
longjacksonle deleted branch feat/LC-705-workspace-catch-up 2026-08-13 18:57:18 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
psa-systems/lets-chat!671
No description provided.