---
title: Weekly Summary (Monday-morning recap, folded into Daily Digest) (part 2)
---

# Weekly Summary (Monday-morning recap, folded into Daily Digest) (part 2)

## What it is

This is part 2 of the [Weekly Summary (Monday-morning recap, folded into Daily Digest)](weekly-summary.md) page. It covers what the recap is allowed to recommend, the buttons a suggestion can carry, the first-launch backfill and its freshness gate, the nominal AI usage figure, the phrase exclusions behind the quick-reply suggestion, the deterministic commit tally, the cost cap, the Workflow Coach retirement, and the stored tables, settings keys and renderer surfaces behind all of it.

## Where to find it

The same surface as the [full page](weekly-summary.md): the **Briefings** entry in the Omniscio sidebar, the two **Briefings** rows in the **Inbox**, and **Settings → Email & Summaries → Daily digest → Weekly Summary** for the master toggle, the generation hour, the cost cap and **Run now**.

## How it behaves

### How it works

#### Feature recommendations (2026-08-31)

Reduce may emit **at most ONE** suggestion per week that recommends an Omniscio feature the user is not yet using — "you run a lot of sessions and have never used Auto-Run (Away Mode)". It closes a gap that had been open since the detectors shipped: `featureGaps` / `featureUsage` could already tell the model WHICH features were untouched, but both are keyed on the telemetry `FEATURE_REGISTRY`, whose `description` fields describe what is *counted* ("counts each session created, launched, renamed…"), not what the feature is *for*. The model could see a gap and had nothing to pitch into it.

- **The menu** is `featureCatalog` on the envelope: `Array<{ title, pitch, goals }>` produced by [`buildWeeklyFeatureCatalog()`](/src/shared/feature-inventory/weekly-feature-catalog.ts), a pure projection of the generated [`RECOMMENDATION_INDEX`](/src/shared/feature-inventory/feature-recommendation-index.generated.ts) — **the same artifact Ask Omniscio reads** for "what should I use for X?" (see [ask-amc.md](ask-amc.md)). Written once, surfaced in both places; there is deliberately no second hand-authored list. Entries with an empty title or pitch are dropped — an entry with no pitch can only produce an invented one. `goals` is **comma-joined, not an array**: the envelope is serialized with 2-space indentation, so 160 arrays of one or two short strings spend ~1.4 KB on brackets and newlines for a shape the model reads identically either way.
- **The rules**, all pinned in `REDUCE_SYSTEM_PROMPT` and each locked by a named test in [weekly-summary-prompt-feature-recommendation.test.ts](/tests/unit/services/weekly-summary-prompt-feature-recommendation.test.ts): at most one per week and only when the week's evidence genuinely fits (this is a recap, not a feature tour); recommend ONLY a `title` present in `featureCatalog` (anything absent may be unreleased or unavailable to this user); **cite a concrete usage signal** proving non-use, and emit nothing without one; never recommend the weekly summary, the suggestions card, or the daily digest; write the pitch in the same language as `summaryMd`; put both the catalog entry and the usage signal in `groundednessCheck.signalsUsed`.
- **Why the cite-or-abstain rule is load-bearing:** most of the 160 catalog features have **no telemetry counterpart at all**, so absence from `featureUsage` is silence, not evidence. Without that rule the model would confidently pitch a feature the user runs daily. The prompt says so explicitly.
- **No `action` kind.** Every indexed feature currently has an empty `docLink` (0 carry `publicHelpPage`, 0 carry `llmDocsPage`), so there is nothing for a button to open. The universal **Start session** button already on every SuggestionCard is the call to action.
- **Fail-safe.** `buildFeatureCatalog()` wraps the projection in try/catch returning `[]`, mirroring `buildPriorDismissals()` beside it: a catalog that cannot be built costs the recommendation, never the week's summary. The prompt handles the empty case by emitting no feature suggestion.
- **Budget.** The serialized block is ~31 KB / ~7.8k tokens, paid on all three phases: ≈ **$0.042 per weekly run** (Map $0.0016 + Reduce $0.0388 + Verify $0.0016), ≈ $2.18/user/year. Pinned by a test — re-adding a heavy field (`triggerText` alone is most of the 184 KB index) fails loudly instead of silently multiplying the weekly prompt cost.
- **`weekly-feature-catalog.ts` is MAIN-PROCESS ONLY.** It statically imports the 184 KB generated index; a renderer import would drag that into a bundle with a real Brotli entry budget. Guarded by [weekly-feature-catalog-main-only.test.ts](/tests/unit/lint/weekly-feature-catalog-main-only.test.ts).

#### Build-it-now actions

A suggestion can carry an optional `action: { kind, payload }` — when present the renderer shows a primary button whose **label and icon match the action** (resolved by [`resolveSuggestionActionPresentation()`](/src/renderer/src/features/weekly-summary/suggestion-action-presentation.ts); an unknown kind falls back to a neutral _Take action_). Clicking it routes to a renderer-side primitive via [`dispatchSuggestionAction()`](/src/renderer/src/features/weekly-summary/suggestion-action-dispatcher.ts). The valid `kind` values are pinned in the Reduce system prompt (anything else toasts a warning and no-ops). **`save-quick-reply` was retired 2026-07-06** — repeated phrases now get their own standalone [inbox nudge](quick-reply-nudge.md), so the digest no longer emits it (the dispatcher still handles it, labeled _Add quick reply_, for any already-stored summaries):

| `action.kind`           | Button label      | What clicking does                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| ----------------------- | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `create-automation`     | Create automation | `navigateToSettingsProject({ section: 'automations', automationConfig })` so the Automation Builder opens pre-filled. Accepts either `{ config: {...} }` (nested sub-object) or the whole payload as the config (fallback).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| `open-keybindings`      | Change shortcut   | Sets `pendingSettingId = keybinding-<actionId>` (actionId comes from the `hotkeyAdoption` detector — any bound shortcut the user clicks past, ranked by time lost), then `navigateToSettingsProject({ section: 'keybindings' })`. The panel selects the action's group and `useScrollToSetting` scrolls to + flashes the exact `data-setting-id="keybinding-<actionId>"` row — the button lands on the specific shortcut, not just the section.                                                                                                                                                                                                                                                                                                                                                                 |
| `archive-idle-sessions` | Review & archive  | The model sends `{ projectName }` — NEVER session ids (they never reach the LLM). So the dispatcher **resolves the currently-idle sessions itself** from a fresh, complete `SESSION_LIST` fetch (the renderer store omits `ready`/`terminating` idle sessions at boot, so filtering it would under-count), scoped to the named project (or all idle when `projectName` is omitted) via the shared idle predicate ([`isSessionIdle`](/src/shared/idle-sessions.ts)). Then `useConfirmDialog` (warning variant) shows the TRUE current count, `mergeSessions` makes any non-resident rows resident, and `bulkArchiveSessions(ids, 'user-context-menu')` archives (terminate-before-archive + undo). Explicit `sessionIds` still win (back-compat). |
| `explore-feature`       | Take a look       | Resolves a Settings section from `section` (used as-is) else `featureId` (`cron*` → `cronjobs`; ids that ARE section ids like `mempalace`/`contextdock` pass through; unknown → `null` = Settings home). If `settingId` is in the payload, sets `pendingSettingId` so the panel highlights the target on open. Then `navigateToSettingsProject({ section })`.                                                                                                                                                                                                                                                                                                                                                                                    |

Hard rules baked into the dispatcher:

- `payload` is coerced to `{}` if `null` or non-object — a malformed Reduce output can't crash the click.
- `archive-idle-sessions` with no explicit `sessionIds` resolves the idle set from `projectName` (fresh `SESSION_LIST`); an unfound project toasts a `'warning'`, and a genuinely-empty idle set toasts a neutral `'info'` — never the old always-fires "No idle sessions to archive."
- An unknown `kind` toasts a neutral `'This action isn't available.'` and `console.error`s the raw kind (humanization — the discriminant never reaches the toast).
- The Reduce prompt pins the EXACT `action.payload` shape per kind (`archive-idle-sessions → { projectName? }`, `explore-feature → { section }`, `open-keybindings → { settingId }`, `create-automation → { config }`) so the producer and this consumer can't drift. That drift — the model emitting keys the dispatcher never read (`projectName`/`featureId`) — is exactly what left these buttons dead until 2026-07-13; see [postmortem](/.claude/memory/postmortems/weekly-summary-suggestions-mining-postmortem.md).
- The action payload never contains raw user text (the system prompt's `"action.payload" stays small (a few keys)` rule is reinforced by the privacy canary on the envelope side).

The dispatcher takes `deps: { toast, confirm }` so it stays a pure module — `SuggestionCard.tsx` injects `useToast()` + `useConfirmDialog().confirm` at the call site.

#### First-launch backfill + the freshness gate (2026-08-03)

`backfillRecentWeeks()` is a **private** method called once from `start()`'s IIFE. The first time the service boots after upgrade (or after a fresh install), `weeklySummaryBackfillCompleted` is `false`. It builds the 4 most-recently-completed weeks, then a **freshness gate** filters them, so on any real gap it generates **at most the single most-recent completed week** — never a backlog:

1. Returns early if `weeklySummaryBackfillCompleted` is already `true`.
2. Builds the 4 most-recent completed weeks (the just-completed week + the 3 before it), oldest-first for chronological insert order.
3. **Freshness filter (`isWeekReportPastDue`).** A weekly recap is "due" the Monday after its week ends and becomes **past-due** once a full grace-week (`WEEKLY_REPORT_PAST_DUE_GRACE_DAYS = 7`) has elapsed past that delivery Monday — i.e. once the *next* week's recap has itself come due. Past-due weeks are dropped up front, so only the most-recent completed week survives (the older 3 are always past-due). This is the owner rule (2026-08-03) that stops a stranded/returning user getting a burst of weeks-old recaps. `GRACE = 7` is a documented **floor** — the current week's own margin shrinks to ~1 day on a Sunday, so a smaller grace would start suppressing the *current* week. Local-time, DST-safe math in [/src/main/services/weekly-summary-week-bounds.ts](/src/main/services/weekly-summary-week-bounds.ts).
4. Pre-flights the daily cap before each surviving week (`getTodayCostByLabel('weekly-summary') >= cap`); `generate()` re-checks it too. A **past-due skip is NOT a cap-halt** — it never sets `capHalted`.
5. In `finally`, flips `weeklySummaryBackfillCompleted: true` unless halted by a **real** cap break — a transient API error still completes it (so it doesn't re-loop), a genuine cap break leaves it unset to retry next boot (F069), and a fully-past-due backfill (nothing to generate) still completes. Internal-only, not in the renderer Zod schema.
6. Skips weeks that already have a row (via `hasWeeklySummaryForWeek`) — re-runs after a settings change are safe.

**`catchUp()` is gated the same way.** It builds a window of candidate weeks and applies `isWeekReportPastDue`, so after a >1-week outage it fills **only** the most-recent completed week rather than every missed week — this intentionally supersedes the old multi-week catch-up (F117). The two **automatic** paths (catch-up + backfill) are the only ones gated; **`periodicCheck()` (the current week — never past-due) and the manual _Run now_ (`generate()`) are never gated**, so a user can always regenerate any week on demand. Guard tests: [/tests/unit/services/weekly-summary-week-bounds.test.ts](/tests/unit/services/weekly-summary-week-bounds.test.ts) (the past-due math incl. the Sunday floor sweep) and the backfill/catch-up suites in [/tests/unit/services/weekly-summary-service.test.ts](/tests/unit/services/weekly-summary-service.test.ts).

#### AI usage is a nominal, pay-as-you-go value (2026-08-03)

The week's AI figure (`stats.aiCostUsd` = `SUM(sessions.cost_usd)`) is **metered at standard API list prices regardless of the account's plan** — so for a user on a Claude subscription (e.g. Max) it is the *pay-as-you-go equivalent*, NOT what they actually paid (their real out-of-pocket is typically far lower). Two surfaces keep that honest:

- The **stat tile** is labeled **"AI usage"** (was "AI cost") with an info tooltip: _"Pay-as-you-go value: what this week's AI usage would cost at standard pay-as-you-go API rates. On a Claude subscription (like Max), your actual cost is usually far lower."_ — [StatsCard.tsx](/src/renderer/src/features/weekly-summary/StatsCard.tsx), i18n key `statsCard.aiUsageTooltip`.
- The **REDUCE system prompt** instructs the recap writer to frame the figure as pay-as-you-go usage value (`"$X in AI usage at pay-as-you-go rates"`), never "you spent $X". The tile tooltip is the hard guarantee; the LLM framing is best-effort. Guard: [/tests/unit/services/weekly-summary-prompt-nominal-spend.test.ts](/tests/unit/services/weekly-summary-prompt-nominal-spend.test.ts).

#### Button-phrase exclusion in the "save as a quick reply" suggestion

`runAllDetectors()` accepts an optional `excludePhrases: string[]` on every call. The Weekly Summary passes `[settings.quickReplyText, NUDGE_PROMPT, ...every saved quick reply's text]` so that Detector A (`detectRepeatedOutgoingText`) and Detector K (`detectTopOperatorPhrases`) never flag a phrase the user has ALREADY saved as a one-click button — the configured zap "proceed" phrase, the "Please continue" nudge (from [src/shared/nudge-prompt.ts](/src/shared/nudge-prompt.ts)), AND every row in the `quick_replies` table. The saved texts come from the resilient `getSavedQuickReplyTexts()` helper (`listQuickReplies()` wrapped in try/catch → `[]` on a failed read, mirroring `buildPriorDismissals` — a broken quick-reply read costs only the exclusion, never the whole summary). Exclusion works on the **bucket-insensitive** `signatureWords()` form (the first 8 normalized words, no length bucket), so punctuation/casing variants match (a user-typed "Please continue." is caught by the canonical "Please continue") AND a saved reply whose text is the sent phrase PLUS a trailing sentence still matches — the real "Git Prep" reply normalizes to the `long` bucket while the bare sentence the user sends is `medium`, so the old full-`signaturePhrase()` match missed it and re-suggested it weekly (fixed 2026-06-22). Divider/folder rows carry empty text and are dropped by each detector's own `.filter(p => p.trim().length > 0)`. It is an **additional skip** on top of the existing `is_auto_response = 0` filter — it does not relax that filter. (Known limit: a phrase buried in the MIDDLE/END of a longer saved reply, or a prepend/append-composed send whose leading words differ, can still surface — out of scope.) Contract invariant **`button-bound-phrases-excluded-on-request`**: [workflow-coach-phrase-detection-contract.md](/.claude/memory/contracts/workflow-coach-phrase-detection-contract.md).

#### Deterministic commit tally

`WeeklySummaryStats.commits` (`{ total, projectCount }`, optional) is a deterministic count gathered before any LLM call — **never** model-generated. `gatherWeekCommits(db, bounds)` (calling `gatherCommitActivity()` in [src/main/services/commit-activity.ts](/src/main/services/commit-activity.ts)) counts commits via `git rev-list --count --branches` across **all local branches**, not just `HEAD`, because Omniscio's worktree-per-session model lands commits on per-session branches the main checkout can't reach. The query scopes to every normal project that had ≥1 session in the Mon→Sun window; virtual-project sentinels (`__claude__`, `__nothari__`, `__plugin_*__`, …) are excluded in SQL via `NOT GLOB '__*'`. Rendered in `StatsCard` as "N commits across M projects this week" (hidden when zero).

#### Cost cap

The default cap is **$0.10/day**. The cap is the _cumulative_ spend for the `'weekly-summary'` source-label on the calendar day, queried via `getTodayCostByLabel('weekly-summary')` against `api_cost_log`. Backfill, scheduled runs, and **Run now** all gate against the same number — so a backfill that drains the cap will also block the manual button until the next day. The cap can be raised or lowered in Settings (range `$0.05` – `$5.00`).

### Subsume Workflow Coach — migration details

The retirement of the Workflow Coach feature happened on **2026-05-14** in the same PR as Weekly Summary's first ship:

- The **Suggestions** sidebar entry, its `SuggestionsView` / `SuggestionRow` / `suggestions-store` modules, and the standalone weekly inbox alert were deleted.
- The **six detectors** in [/src/main/services/workflow-coach/detectors.ts](/src/main/services/workflow-coach/detectors.ts) live on — Weekly Summary calls `runAllDetectors()` as a sub-step of every generation.
- The **privacy canary test** at [/tests/unit/services/workflow-coach-privacy.test.ts](/tests/unit/services/workflow-coach-privacy.test.ts) is unchanged and continues to pin the no-raw-text contract.
- Settings → Experimental → Workflow Coach is gone. The replacement panel is Settings → Email & Summaries → Daily digest → Weekly Summary.
- Existing users get a **one-time soft migration** on first launch after the upgrade: `softMigrateFromWorkflowCoachOnce()` reads the old `workflowCoachEnabled` / `workflowCoachDailyCostCapUsd` keys and copies them onto the new ones, then sets `weeklyCoachMigrationDone: true`. If you had Workflow Coach on, you get Weekly Summary on with your old cap.
- The legacy `__suggestions__` virtual-project row is soft-deleted by migration v172 — it stays in `projects` with `is_deleted = 1` so a user who had pinned or customized it has a recoverable record, but it stops rendering.

## For agents

### Privacy contract (carried over from Workflow Coach)

The same canary test ([/tests/unit/services/workflow-coach-privacy.test.ts](/tests/unit/services/workflow-coach-privacy.test.ts)) that pinned Workflow Coach's privacy guarantee continues to pin the detector output. Raw operator text never enters `AggregatedSignals` — only hashes from [`workflow-coach/hash.ts`](/src/main/services/workflow-coach/hash.ts), counts, and a single PII-scrubbed `sampleText` ≤200 chars per detector. The Map-phase prompt is built from `RawStats` (factual aggregates) + `AggregatedSignals` (hashes/counts) — nothing else. If the canary string appears in the prompt, the test fails CI.

### Storage

Migration v171 creates `weekly_summaries` (extended by v195 with `model_chain_json`):

```
id TEXT PRIMARY KEY
week_start_iso TEXT NOT NULL UNIQUE  -- 'YYYY-MM-DD' local Monday
week_end_iso TEXT NOT NULL           -- ISO datetime, local Sunday 23:59:59.999
generated_at TEXT NOT NULL
stats_json TEXT NOT NULL             -- JSON WeeklySummaryStats
summary_md TEXT NOT NULL
suggestions_json TEXT NOT NULL       -- JSON Array<WeeklySummarySuggestion>
detector_signals_json TEXT NOT NULL  -- JSON AggregatedSignals (debugging only)
input_tokens INTEGER NOT NULL DEFAULT 0
output_tokens INTEGER NOT NULL DEFAULT 0
cost_usd REAL NOT NULL DEFAULT 0
model_chain_json TEXT NOT NULL DEFAULT '[]'  -- v195: JSON ModelChainEntry[]
is_read INTEGER NOT NULL DEFAULT 0
is_archived INTEGER NOT NULL DEFAULT 0
snoozed_until TEXT NULL              -- ISO datetime, null = not snoozed
```

Migration v208 creates `weekly_summary_dismissals`:

```
id TEXT PRIMARY KEY
weekly_summary_id TEXT NOT NULL      -- FK → weekly_summaries(id) ON DELETE CASCADE
suggestion_index INTEGER NOT NULL    -- 0-based index into the summary's suggestions[]
suggestion_title TEXT NOT NULL       -- snapshot at dismissal time
suggestion_body TEXT NOT NULL        -- snapshot at dismissal time
reason TEXT NOT NULL                 -- 'already-doing' | 'not-useful' | 'not-relevant' | 'wrong' | 'other'
dismissed_at TEXT NOT NULL
UNIQUE(weekly_summary_id, suggestion_index)  -- one dismissal per slot, last-write-wins via upsert
```

Indexes: `idx_wsd_dismissed_at` (drives the 90-day envelope read), `idx_wsd_weekly_summary_id` (drives the per-summary `dismissals` slice population on GET).

Migration v172 soft-deletes the legacy `__suggestions__` virtual-project row that Workflow Coach used to seed; the row stays in `projects` (because `is_deleted = 1`) for any user who had pinned/customized it, but it no longer renders in the sidebar.

Migration v179 (2026-05-14) soft-deletes the `__weekly_summary__` virtual-project row when the merge into Daily Digest landed. Same pattern — the row stays in `projects` with `is_deleted = 1` so prior customizations are recoverable, but the standalone sidebar entry stops rendering.

Queries: [/src/main/db/queries-weekly-summary.ts](/src/main/db/queries-weekly-summary.ts) — `insertWeeklySummary`, `getWeeklySummary`, `listWeeklySummaries`, `markRead`, `archive`, `snooze`, plus the four dismissal queries `upsertWeeklySummaryDismissal`, `deleteWeeklySummaryDismissal`, `listDismissalsForSummary`, `getRecentWeeklySummaryDismissals(daysBack, limit = 100)`. Tests at [/tests/unit/db/queries/queries-weekly-summary-dismissals.test.ts](/tests/unit/db/queries/queries-weekly-summary-dismissals.test.ts) pin upsert idempotency, FK cascade, 90-day cutoff math, default limit of 100, modelChain JSON round-trip, and the GET-time dismissals slice population.

IPC: [/src/main/ipc/weekly-summary-handlers.ts](/src/main/ipc/weekly-summary-handlers.ts) — `list`, `get`, `mark-read`, `archive`, `snooze`, `generate-now`, `unread-count`, `dismiss-suggestion`, `undismiss-suggestion`. Push events: `WEEKLY_SUMMARY_GENERATED`, `WEEKLY_SUMMARY_UPDATED` (also fires after dismiss/undismiss so other open viewers refresh), `WEEKLY_SUMMARY_UNREAD_CHANGED`.

### Renderer surface

- Settings: [/src/renderer/src/features/settings/WeeklySummarySettings.tsx](/src/renderer/src/features/settings/WeeklySummarySettings.tsx) — rendered at the bottom of [DailyDigestSettings.tsx](/src/renderer/src/features/settings/DailyDigestSettings.tsx) (Settings → Email & Summaries → Daily digest).
- Briefing card + viewer: weekly summaries reuse the same row + viewer components as daily digests inside the Briefings sidebar — see [daily-digest.md](daily-digest.md) for the canonical surface.
- Store: [/src/renderer/src/stores/weekly-summary-store.ts](/src/renderer/src/stores/weekly-summary-store.ts) with the **recap** inbox slice in [/src/renderer/src/stores/weekly-summary-inbox-items.ts](/src/renderer/src/stores/weekly-summary-inbox-items.ts). The `activeInboxSummary` slot is one of the inbox mutex slices — picking a Weekly Summary clears the others, and every bulk action's snapshot/fallback/rollback restores `activeInboxSummary` along with the rest.
- **Two inbox cards (2026-07-20):** the recap stays on the reserved `weekly-summary` integration above; the AI's suggestions surface as a SEPARATE derived `weekly-suggestions` source ([/src/renderer/src/stores/weekly-suggestions-inbox-items.ts](/src/renderer/src/stores/weekly-suggestions-inbox-items.ts)) that reads the SAME store, **surfaces the LATEST week only** (newest by `generatedAt` — never walks back through older summaries; the 2026-07-20 backlog fix), opens a dedicated in-pane detail ([WeeklySuggestionsViewer.tsx](/src/renderer/src/features/weekly-summary/WeeklySuggestionsViewer.tsx)) via the generic `activeInboxDetail` slice, and gates/labels/self-clears on the LIST-only `openSuggestionCount` (total − dismissals, computed in [queries-weekly-summary.ts](/src/main/db/queries-weekly-summary.ts)). The recap's inbox pane hides suggestions (`hideSuggestions` on `WeeklySummaryViewer`); the dedicated sidebar viewer keeps the full recap + suggestions, both via the shared [WeeklySuggestionsContent.tsx](/src/renderer/src/features/weekly-summary/WeeklySuggestionsContent.tsx). The recap inbox card also offers the shared **Start session** button ([components/inbox/StartSessionDialog.tsx](/src/renderer/src/components/inbox/StartSessionDialog.tsx), source `weekly-summary-start-session`), gated by `showStartSession` so it appears on the inbox recap ONLY — the sidebar review viewer omits it, and the suggestions card carries its own per-suggestion Start session. Invariants: [weekly-suggestions-card-contract.md](/.claude/memory/contracts/weekly-suggestions-card-contract.md).

### Settings deep-link compatibility

`'weekly-summary'` stays in the `SettingsSection` union and in `VALID_SECTIONS` so old URLs (e.g. `?section=weekly-summary`) still type-check. The `LEGACY_FEATURE_SECTIONS` map in [Settings-types.ts](/src/renderer/src/features/settings/Settings-types.ts) single-hops `'weekly-summary'` to `'email'` (the Email & Summaries panel that hosts the merged Daily Digest card), so the deep link lands on the right panel automatically. The search index in [settings-search-index.ts](/src/renderer/src/features/settings/settings-search-index.ts) lists each `weekly-summary-*` entry with `section: 'daily-digest'`, so search hits (e.g. typing "weekly", "monday", "haiku", "briefing") scroll the user to the Weekly Summary sub-section inside the Daily Digest panel.

## Related

This is part 2 of the [Weekly Summary (Monday-morning recap, folded into Daily Digest)](weekly-summary.md) page — the overview, the day-to-day usage and the first half of the behaviour live there. Its closest sibling page is [Daily Digest](daily-digest.md), which describes the shared Briefings surface both cadences use.
