Give Input keycaps, a shortcut search, and the missing pointer basics
Claude-Session: https://claude.ai/code/session_01Ms2FbjQy31TVf3CEvQhGM8
This commit is contained in:
@@ -0,0 +1,147 @@
|
||||
# Notifications & Focus redesign — two tabs, nothing unbounded
|
||||
|
||||
Approved mock: `home-mocks/notifications.html` (scratchpad, :8642). This spec is the
|
||||
implementation contract; where mock and spec disagree, the spec wins.
|
||||
|
||||
## Goals
|
||||
|
||||
1. **Bound the app list.** Recent senders + customized apps render up top; everything else sits
|
||||
behind a collapsed, searchable "All apps" expander. Rows expand in place to their controls.
|
||||
2. **Real per-app rules**: sound on/off, banners-vs-history, urgency override, forget.
|
||||
3. **A real focus-mode editor**: create/rename/delete/reorder (order IS priority), trigger-kind
|
||||
editing for all five kinds, schedules with day pills, chip-based interrupt lists.
|
||||
4. **Two tabs**: Notifications | Focus, as category tabs like Shell.
|
||||
|
||||
Non-goals: lock-screen privacy (hyprlock cannot render notifications; contract-banned),
|
||||
per-app counters (no data), time-based history retention, merging duplicate app identities
|
||||
(show the raw id honestly instead).
|
||||
|
||||
## Rule shape (pinned — service and UI program against this)
|
||||
|
||||
`normalizedAppRule` in `Notifs.qml` grows from `{ enabled }` to:
|
||||
|
||||
| Field | Type | Default | Meaning |
|
||||
|---|---|---|---|
|
||||
| `enabled` | bool | true | Off = rejected before tracking/history/unread/toast (unchanged) |
|
||||
| `sound` | bool | true | false = `playBell` skips this app |
|
||||
| `display` | string | `"banners"` | `"history"` = file in history + unread, no popup, no bell |
|
||||
| `urgency` | string | `"auto"` | `"low"` / `"critical"` override what the app claims |
|
||||
| `lastSeenMs` | int | 0 | stamped in `rememberApplication` on every notification |
|
||||
| `name` | string | "" | display name cached at remember time (resolution stays live-first) |
|
||||
| `icon` | string | "" | icon cached at remember time (DesktopEntries lookup, else `appIcon`) |
|
||||
|
||||
Unknown/stale fields (incl. the old lock-screen pair) keep being dropped on read. Old
|
||||
`{enabled}`-only blobs stay valid — every new field is optional with the defaults above.
|
||||
New API: `forgetApp(appId)` deletes the rule key outright.
|
||||
`effectiveUrgency(notification)` — the app's `urgency` override applied over
|
||||
`notification.urgency`; consumed by `playBell` (low = silent), the popup timeout choice
|
||||
(critical duration), the DND breakthrough gate, and `NotificationCard`'s critical edge.
|
||||
|
||||
**Critical breakthrough**: new schema key `criticalBreaksThrough` (bool, def **false**, group
|
||||
`notifications`, label "Critical alerts break through"). Popup gate becomes: show when
|
||||
`!doNotDisturb || FocusModes.allows(appId) || (Settings.criticalBreaksThrough &&
|
||||
effectiveUrgency(n) === critical)`.
|
||||
|
||||
## FocusModes API additions (pinned)
|
||||
|
||||
- `createMode(name)` → new mode `{ id: unique slug, name, enabled: true, triggers: [{kind:
|
||||
"manual"}], silence: true, keepAwake: false, allow: [] }`, appended (lowest priority).
|
||||
- `removeMode(id)`, `renameMode(id, name)` (non-empty, trimmed).
|
||||
- `moveMode(id, delta)` — reorder; order is priority and the UI says so.
|
||||
- `setTriggerKind(id, kind, fields)` — replaces the mode's `triggers` with one trigger of the
|
||||
new kind. Kind-specific seeds: schedule → `{ start: "22:00", end: "07:00", days: [0..6] }`;
|
||||
workspace → `{ id: 1 }`; fullscreen/game/manual → no fields. (Shipped modes each carry one
|
||||
trigger; a hand-edited multi-trigger mode collapses to one on first kind change — the editor
|
||||
edits `triggers[0]` and that is documented in a comment.)
|
||||
- **Manual-mode semantics**: investigate how a manual-trigger mode activates today and PRESERVE
|
||||
it exactly; if manual modes currently have no activation path besides `enabled`, the header
|
||||
toggle keeps meaning `enabled` and the editor's "Turns on: Manually" detail explains that a
|
||||
manual mode quiets things whenever it is switched on. Do not invent new activation machinery.
|
||||
- Existing `setEnabled`/`update`/`reschedule`-style schedule + day editing semantics stay;
|
||||
`withinWindow`, single-DND-ownership, and the gaming report-don't-silence rule are
|
||||
contract-pinned and untouched.
|
||||
|
||||
## Routing (pinned)
|
||||
|
||||
`SettingsRoutes` category `notifications` gains tabs:
|
||||
`[{ page: "notifications", label: "Notifications" }, { page: "focus", label: "Focus" }]`.
|
||||
New leaf `focus` → new `FocusPage.qml`; `SettingsShell` case + Component. `groupPages`
|
||||
`"focus"` moves `"notifications"` → `"focus"` (the focusModes entry renders there now).
|
||||
`GamingPage`'s "Open Focus" action retargets `openSettings("focus")`.
|
||||
`NotificationCard`'s "Notification settings" jump stays `"notifications"`.
|
||||
|
||||
## Notifications tab (NotificationsPage.qml rebuilt)
|
||||
|
||||
Lede unchanged. Cards:
|
||||
1. **Quiet** — Do Not Disturb toggle; "Critical alerts break through" ToggleRow
|
||||
(`criticalBreaksThrough`); "Quiet hours" ActionRow whose detail states the Sleep mode's
|
||||
live schedule (or "not scheduled" when Sleep lacks/disabled) and whose button opens the
|
||||
Focus tab (`openSettings("focus")`).
|
||||
2. **Banners & history** — the four existing schema sliders (names contract-pinned; critical
|
||||
zero renders "Never"); history count + Clear folded into the history row.
|
||||
3. **Applications** — subtitle per mock. Sections:
|
||||
- *Recent*: apps with `lastSeenMs` within 7 days, newest first.
|
||||
- *Customized*: any app with a non-default field (and not already in Recent).
|
||||
- *All apps (N)*: collapsed expander with an inline search field; alphabetical.
|
||||
Rows: cached icon (fallback letter tile), name, subtitle (relative last-seen when known ·
|
||||
state summary like "sound off"/"History only", else the raw appId), enabled toggle, chevron.
|
||||
Expanded body: Play sound toggle · "Show as" dropdown (Banners & history / History only) ·
|
||||
"Urgency" dropdown (App decides / Treat as low / Treat as critical) · "Forget this app"
|
||||
ActionRow (detail: "Remove its rule; it returns on its next notification").
|
||||
4. **Active-mode banner** at top (ok-tinted) when a focus mode is active: "<name> is quieting
|
||||
notifications · because <reason> · <allow summary>", button "Open Focus".
|
||||
|
||||
## Focus tab (FocusPage.qml, new)
|
||||
|
||||
Lede: "Modes quiet this machine on their own terms — first matching mode wins, and the order
|
||||
below is the priority."
|
||||
1. **Focus modes** card — accordion (one open at a time): drag grip (reorder = priority; also
|
||||
keyboard up/down on the grip), mode glyph, name, summary line ("Turns on <trigger summary> ·
|
||||
silences everything except N apps" / "On now — <reason>"), enabled toggle, chevron.
|
||||
Expanded: "Turns on" dropdown (five kinds) + kind fields (schedule start/end `TimeOfDayRow`-
|
||||
style or validated HH:MM inputs + seven day pills; workspace id picker); "Silence
|
||||
notifications" toggle; "May interrupt" chip row (chips with ×, "+ Add app" opens a searchable
|
||||
picker over known apps — reuse the rules list's app universe); "Keep the screen awake"
|
||||
toggle; Rename + Delete mode buttons. "+ New focus mode" dashed row at the bottom.
|
||||
2. **Focus sessions** card — default duration segmented chips (25/45/60/90 →
|
||||
`focusDurationMinutes`), Caffeine toggle, session status row + Start focus/Show controls
|
||||
(existing behaviors move over unchanged).
|
||||
|
||||
Reuse existing row widgets and the Displays/Sound phase components (OptionPickerRow, etc.)
|
||||
before inventing new ones. No continuously repainting animations. All new components get
|
||||
qmldir lines in the same wave as first reference.
|
||||
|
||||
## Search & docs
|
||||
|
||||
Hand-written entries (page per target): Do Not Disturb, Quiet hours, Critical alerts break
|
||||
through, Application notification rules, Forget an app's notifications, Per-app notification
|
||||
sound → `notifications`; Focus modes detail already schema-indexed (now routes to `focus`),
|
||||
plus Focus session duration → `focus` if not covered by the workspaces group move. Docs and
|
||||
launcher commands regenerate after the schema lands (orchestrator's audit pass).
|
||||
|
||||
## Contracts (write, do NOT run — cite in the backlog for the next sweep)
|
||||
|
||||
- `notification-app-rules-contract`: extend the pinned rule shape to the table above (defaults,
|
||||
optional back-compat, stale-field dropping incl. lock-screen pair), pin `forgetApp`, the
|
||||
display="history" no-popup-no-bell path, sound=false no-bell, `effectiveUrgency` consumers,
|
||||
and the breakthrough gate literal.
|
||||
- `focus-modes-contract`: pin the new CRUD/reorder/trigger APIs, keep every existing pin
|
||||
(conditions-not-alarms, DND ownership, gaming reports, exception list consulted + editable —
|
||||
the editable needle moves to FocusPage).
|
||||
- `settings-pages-contract` (page id list + component list), `settings-jump-contract`
|
||||
(GamingPage → focus), `search-routing-contract` expectations, `settings-window-contract` if
|
||||
it enumerates tabs.
|
||||
- Backlog spec gains a Phase 7 section listing all of it.
|
||||
|
||||
## Agent ownership (parallel)
|
||||
|
||||
- **A — services**: `services/Notifs.qml`, `services/FocusModes.qml`,
|
||||
`config/PreferenceSchema.qml` (one new key; comments above braces).
|
||||
- **B — UI**: `modules/settings/NotificationsPage.qml`, new `modules/settings/FocusPage.qml` +
|
||||
new components + `modules/settings/qmldir`, `services/SettingsRoutes.qml`,
|
||||
`modules/settings/SettingsShell.qml`, `modules/notifications/NotificationCard.qml`
|
||||
(effectiveUrgency), `modules/settings/GamingPage.qml` (Open Focus target).
|
||||
- **C — periphery**: `services/SettingsSearch.qml`, the contracts above + harness fixtures,
|
||||
test-backlog spec, README count line only if the contract count changes.
|
||||
|
||||
B programs against the pinned shapes; A must not change them without updating this spec.
|
||||
Reference in New Issue
Block a user