From 3b082eddd2f3af9505b85c9e6622964911986972 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 10 Aug 2026 16:04:37 +0000 Subject: [PATCH 1/2] docs: cover keyboard-only, onboarding tour, and failed-job self-heal Bring discoverability and acceptance docs in line with the recent web shortcut/onboarding work and Sync's durable failed-job auto-clear path. Co-authored-by: Tyler Dane --- docs/README.md | 2 + docs/acceptance/shortcuts.md | 54 ++++++++++++++++++----- docs/development/common-change-recipes.md | 30 +++++++++++++ docs/development/feature-file-map.md | 43 ++++++++++++++++-- docs/features/google-sync-and-sse-flow.md | 23 ++++++++++ docs/frontend/frontend-runtime-flow.md | 34 ++++++++++++++ docs/self-hosting/monitoring.md | 1 + 7 files changed, 173 insertions(+), 14 deletions(-) diff --git a/docs/README.md b/docs/README.md index b05c354d02..5c65122e63 100644 --- a/docs/README.md +++ b/docs/README.md @@ -15,6 +15,8 @@ Internal documentation for engineers and agents working in the Compass repo. - Event shape or recurrence behavior: [Event Domain Model](./architecture/event-domain-model.md), [Common Change Recipes](./development/common-change-recipes.md#change-recurring-event-behavior) - Event caching, reads, or optimistic writes: [Event Caching](./frontend/event-caching.md) - Dragging/resizing events on the week grid: [Week Drag Interaction](./frontend/week-drag-interaction.md) +- Keyboard shortcuts, event jump, or keyboard-only mode: [Shortcuts](./acceptance/shortcuts.md), [Feature File Map](./development/feature-file-map.md#keyboard-shortcuts) +- Onboarding tour after Start Now: [Feature File Map](./development/feature-file-map.md#onboarding-tour), [Frontend Runtime Flow](./frontend/frontend-runtime-flow.md#welcome-and-onboarding-tour) - Breakpoints, sidebar collapse, or layout at different viewport sizes: [Responsive Layout](./frontend/responsive-layout.md) - Local-first or storage behavior: [Offline Storage And Migrations](./features/offline-storage-and-migrations.md) - Backend routes and API behavior: [Backend Route Map](./backend/README.md), [Backend Request Flow](./backend/backend-request-flow.md), [Backend Error Handling](./backend/backend-error-handling.md) diff --git a/docs/acceptance/shortcuts.md b/docs/acceptance/shortcuts.md index c751167733..88fb45770c 100644 --- a/docs/acceptance/shortcuts.md +++ b/docs/acceptance/shortcuts.md @@ -13,11 +13,13 @@ Use this guide to validate: - navigating between views with the keyboard (D, W) - navigating between days in Day view (J, K, T) - navigating between weeks in Week view (J, K, T) -- opening and using the command palette (Cmd+K) +- opening and using the command palette (Cmd+K), including undo/redo rows - creating events with keyboard shortcuts (C, A in both Day and Week view) - editing events with the same keys in Day and Week (Delete, Shift+arrows, draft arrows) +- spatially focusing events with arrow keys +- toggling event-jump chips (Shift tap) and keyboard-only mode (Shift-Shift) - toggling the sidebar (]) -- undoing with the keyboard (Cmd+Z / Ctrl+Z) +- undoing / redoing with the keyboard (Cmd+Z / Cmd+Shift+Z) - confirming that shortcuts do not fire while typing in inputs Do not use this guide to validate: @@ -47,18 +49,21 @@ Helpful notes: | `W` | Global | Navigate to Week view | | `Cmd+K` / `Ctrl+K` | Global | Open command palette | | `]` | Global | Toggle sidebar | +| `?` | Global | Toggle shortcuts legend | | `Cmd+Z` / `Ctrl+Z` | Global | Undo last event action | | `Cmd+Shift+Z` / `Ctrl+Shift+Z` | Global | Redo last undone event action | +| `Shift` `Shift` | Global | Toggle keyboard-only mode | | `J` | Day view | Previous day | | `K` | Day view | Next day | | `T` | Day view | Go to today | | `I` | Day view | Focus sidebar | | `U` | Day view | Focus first calendar event | -| `Shift` | Day view | Toggle event jump keys | +| `Shift` | Day view | Toggle event jump keys | | `C` | Day view | Create timed event | | `A` | Day view | Create all-day event | | `Delete` | Day view | Delete focused event | -| `ArrowUp` / `ArrowDown` | Day view | Focus previous/next event | +| `ArrowUp` / `ArrowDown` | Day view | Focus previous/next event spatially | +| `ArrowLeft` / `ArrowRight` | Day / Week | Focus nearest event on previous/next column/day (skip empty) | | `Arrow keys` | Day view | Move open draft event | | `Enter` | Day view | Open focused event | | `E` then `T` | Day view | Edit focused event title | @@ -78,9 +83,9 @@ Helpful notes: | `A` | Week view | Create all-day event | | `I` | Week view | Focus sidebar | | `U` | Week view | Focus first calendar event | -| `Shift` | Week view | Toggle event jump keys | +| `Shift` | Week view | Toggle event jump keys | | `Delete` | Week view | Delete focused event | -| `ArrowUp` / `ArrowDown` | Week view | Focus previous/next event | +| `ArrowUp` / `ArrowDown` | Week view | Focus previous/next event on the same day | | `Arrow keys` | Week view | Move open draft event | | `Enter` | Week view | Open focused event | | `E` then `T` | Week view | Edit focused event title | @@ -179,10 +184,11 @@ Pressing Cmd+K opens the command palette from any view, including while a text i ### Expected Results - The command palette opens immediately. -- Items include: Create Event, Create All-Day Event, Go to Today, Log Out. +- Items include: Create event, Create all-day event, Go to Today, Undo last change, Redo last change, Restart onboarding tour, Log Out. +- Undo / Redo rows show their keycaps and stay disabled when there is no history. - Google Calendar connection status and actions appear in the sidebar, not the command palette. - Typing filters the list. -- Selecting "Create Event" opens the event creation form. +- Selecting "Create event" opens the event creation form. - Pressing Escape closes the palette without taking action. - Cmd+K works even when a text input elsewhere has focus. @@ -351,7 +357,32 @@ Tapping `Shift` toggles event-jump mode (activation waits briefly so Shift-Shift --- -## Scenario 13: Shortcuts Do Not Fire While Typing In Inputs +## Scenario 13: Shift-Shift Enters Keyboard-Only Mode + +### UX + +Two quick `Shift` taps toggle keyboard-only mode. While active, pointer clicks are blocked (scroll and hover still work) so the user practices keyboard navigation. A persistent indicator shows how to exit. Mode is not persisted across refresh. + +### Steps + +1. Navigate to `/week` with at least one event visible. +2. Tap `Shift` twice quickly (not a held chord). +3. Try clicking an event with the mouse. +4. Use `U` / arrows / `Enter` to open an event with the keyboard. +5. Press `Esc` (with no modal/form open) or tap `Shift` `Shift` again. + +### Expected Results + +- A “Keyboard only” indicator appears after the double tap. +- Clicks do not open events or focus controls; the indicator may pulse on a blocked click. +- Keyboard shortcuts continue to work. +- If a modal, floating layer, or event form is open, `Esc` dismisses that owner first; a later `Esc` exits keyboard-only mode. +- Exiting clears the indicator. Reloading the page also clears the mode. +- A single Shift tap still toggles event-jump chips; a following quick second Shift cancels jump mode and enters keyboard-only instead. + +--- + +## Scenario 14: Shortcuts Do Not Fire While Typing In Inputs ### UX @@ -380,7 +411,7 @@ If time is limited, run these checks before shipping shortcut-related changes: 1. `D`, `W` navigate to the correct views from any starting view. 2. `J` and `K` navigate days in Day view and weeks in Week view. 3. `T` returns to today from any offset in both Day and Week view. -4. Cmd+K opens the command palette; Escape closes it without action. +4. Cmd+K opens the command palette; Escape closes it without action; Undo/Redo rows are present. 5. `C` opens a timed event form and `A` an all-day event form, in both Day and Week view. 6. `]` toggles the sidebar in both Week and Day view. 7. Delete removes a focused event in Day and Week view and shows an undo toast. @@ -388,7 +419,8 @@ If time is limited, run these checks before shipping shortcut-related changes: 9. No shortcuts fire inside a focused text input except Cmd+K. 10. Shift+ArrowLeft/Right move a focused event by one day in both Day and Week view. 11. Arrow keys reposition an open draft in both Day and Week view. -12. With a focused event and no draft open, ArrowUp/ArrowDown move focus to the previous/next event chronologically. +12. With a focused event and no draft open, ArrowUp/ArrowDown stay on the same day; ArrowLeft/Right jump to the time-nearest event on the previous/next non-empty day. 13. Cmd+D / Ctrl+D duplicates a focused event in Day and Week view. 14. With a focused event, `E` then `T` opens the form with the title focused; bare `E` alone does nothing. 15. Tapping Shift toggles event jump chips; a day letter + digit focuses that event; fast Shift+J does not toggle the mode. +16. Shift-Shift enters keyboard-only mode (clicks blocked, indicator visible); Esc or another Shift-Shift exits. diff --git a/docs/development/common-change-recipes.md b/docs/development/common-change-recipes.md index 50b3b1b367..176dab698f 100644 --- a/docs/development/common-change-recipes.md +++ b/docs/development/common-change-recipes.md @@ -69,6 +69,36 @@ consistent. 2. Verify auth-state implications in `packages/web/src/auth/compass/session/SessionProvider.tsx` and auth-state helpers. 3. Test both never-authenticated and previously-authenticated behavior. +## Add Or Change A Keyboard Shortcut + +1. Add or update the entry in `packages/web/src/shortcuts/shortcuts.registry.ts` + (id, keys, label, section, optional `when`). The `?` legend and overlay + sections read from this registry — do not hard-code a second label list. +2. Register the key in the owning hook: + - global / shell: `packages/web/src/shortcuts/useGlobalShortcuts.ts` + - week: `packages/web/src/views/Week/hooks/shortcuts/useWeekViewShortcuts.ts` + (+ behavior in `useWeekShortcutOwner.ts`) + - day: `packages/web/src/views/Day/hooks/shortcuts/useDayViewShortcuts.ts` + - shared grid edit/focus: `packages/web/src/grid/shortcuts/` +3. If the shortcut shares Shift with event-jump or keyboard-only mode, go through + `packages/web/src/shortcuts/shift-tap-gesture.ts` instead of a private + keydown listener — single-tap vs Shift-Shift coexistence depends on one bus. +4. Respect `app-lock`, `escape-ownership`, and “do not fire while typing in + inputs” (use the existing `useAppShortcut` helpers; do not attach bare + `window` listeners for new app shortcuts). +5. Add or update registry/data tests and the owning hook tests. Update + [Shortcuts acceptance](../acceptance/shortcuts.md) when user-visible + behavior changes. + +Common pitfalls: + +- **Legend-only change** — updating the registry without a handler (or the + reverse) ships a lie in the `?` overlay. +- **Private Shift listeners** — bypassing `shift-tap-gesture` reintroduces + jump-mode flashes on Shift-Shift or blocks keyboard-only entry. +- **App-lock blind spots** — shortcuts that fire while Auth/Settings/Welcome + own the UI feel broken; follow the helpers that already gate on lock state. + ## Change A Shared Hotkey Dialog (Day + Week) Use this for overlays mounted in both `WeekView` and `DayViewContent` (for example Dedication). diff --git a/docs/development/feature-file-map.md b/docs/development/feature-file-map.md index c2c1c51e27..b8dbecb46d 100644 --- a/docs/development/feature-file-map.md +++ b/docs/development/feature-file-map.md @@ -38,8 +38,12 @@ Use this document to find the first files to inspect for common Compass changes. - Day view route and content: `packages/web/src/views/Day/view` - Day view header (includes sidebar toggle control): `packages/web/src/views/Day/components/Header/Header.tsx` -- Day keyboard shortcuts: `packages/web/src/views/Day/hooks/shortcuts/useDayViewShortcuts.ts` -- Sidebar toggle (`]`): `packages/web/src/views/Week/hooks/shortcuts/useGlobalShortcuts.ts` +- Day keyboard shortcuts (thin key registration): `packages/web/src/views/Day/hooks/shortcuts/useDayViewShortcuts.ts` +- Week keyboard shortcuts (thin key registration): `packages/web/src/views/Week/hooks/shortcuts/useWeekViewShortcuts.ts` +- Week shortcut owner (draft create/nav/focus + bus): `packages/web/src/views/Week/hooks/shortcuts/useWeekShortcutOwner.ts` +- Shared grid edit/focus shortcuts: `packages/web/src/grid/shortcuts/useGridEventEditShortcuts.ts`, `focus-adjacent-grid-event.ts` +- Day column set when Google is connected (hides local Compass column): `packages/web/src/views/Day/components/Calendar/dayCalendarColumns.util.ts` +- All-day event color wash on day columns: `packages/web/src/grid/utils/allDayColumnTint.util.ts` - Day view hooks: `packages/web/src/views/Day/hooks` - Week view: `packages/web/src/views/Week` - Responsive layout controller (auto-collapse on breakpoint crossings): `packages/web/src/components/AuthenticatedLayout/useResponsiveLayout.ts` @@ -48,11 +52,42 @@ Use this document to find the first files to inspect for common Compass changes. - week view: `packages/web/src/views/Week/WeekView.tsx` - day view: `packages/web/src/views/Day/view/DayViewContent.tsx` +## Keyboard Shortcuts + +Authoritative legend data and shared gesture behavior live under +`packages/web/src/shortcuts`. View owners register keys; do not duplicate +labels outside the registry. + +- Registry (source of truth for `?` legend): `packages/web/src/shortcuts/shortcuts.registry.ts` +- Overlay sections derived from the registry: `packages/web/src/shortcuts/data/shortcuts.data.ts` +- Global shell shortcuts (sidebar `]`, palette, settings, navigation): `packages/web/src/shortcuts/useGlobalShortcuts.ts` +- Shared Shift-tap gesture (single tap vs Shift-Shift): `packages/web/src/shortcuts/shift-tap-gesture.ts` +- Event-jump chips (Shift tap): `packages/web/src/shortcuts/shift-hint/` +- Keyboard-only mode (Shift-Shift; clicks inert until Esc / Shift-Shift): `packages/web/src/shortcuts/keyboard-only/` +- Escape ownership (modals/form before lower handlers): `packages/web/src/shortcuts/escape-ownership.ts` +- App lock (suppress shortcuts while a modal owns the UI): `packages/web/src/shortcuts/app-lock.ts` +- Mount point for global + keyboard-only hooks: `packages/web/src/components/RootShell/RootShell.tsx` +- Acceptance runbook: [Shortcuts](../acceptance/shortcuts.md) + +## Onboarding Tour + +Interactive coachmarks after Welcome **Start Now**. Not an app-lock modal — +calendar shortcuts stay live so each step can advance by doing the action. + +- Steps (order + copy): `packages/web/src/components/OnboardingTour/onboarding.tour.steps.ts` +- Store + start/skip/finish: `packages/web/src/components/OnboardingTour/onboarding.tour.store.ts` +- Seen flag (browser storage): `packages/web/src/components/OnboardingTour/onboarding.tour.storage.ts` +- Progress listeners (advance on C / save / palette / `?`): `packages/web/src/components/OnboardingTour/useOnboardingTourProgress.ts` +- UI: `packages/web/src/components/OnboardingTour/OnboardingTour.tsx` +- Started from Welcome **Start Now**: `packages/web/src/components/WelcomeModal/WelcomeModal.tsx` +- Restart from command palette (“Restart onboarding tour”): `packages/web/src/components/CommandPalette/navigation.cmd.constants.ts` + ## Sidebar - Shared sidebar shell: `packages/web/src/components/Sidebar/Sidebar.tsx` - Month picker: `packages/web/src/components/Sidebar/MonthPicker/MonthPicker.tsx` -- Account identity/sync indicator: `packages/web/src/components/Sidebar/CalendarList/CalendarListHeader.tsx` +- Shared account sync-status + CTA labels: `packages/web/src/components/Sidebar/CalendarList/useAccountHeaderStatus.ts` +- Account identity/sync indicator: `packages/web/src/components/Sidebar/CalendarList/CalendarListHeader.tsx`, `AccountSectionHeader.tsx` - Sidebar actions and shortcuts overlay: `packages/web/src/components/Sidebar/SidebarActions/SidebarActions.tsx`, `packages/web/src/components/Sidebar/ShortcutsOverlay/ShortcutsOverlay.tsx` - Week mount point: `packages/web/src/views/Week/WeekView.tsx` - Day mount point: `packages/web/src/views/Day/view/DayViewContent.tsx` @@ -88,6 +123,8 @@ see [Google Sync And SSE Flow](../features/google-sync-and-sse-flow.md) for the full picture. - Service entrypoint + internal routes: `packages/sync/src/app.ts`, `packages/sync/src/server/` +- Failed-job self-heal sweep: `packages/sync/src/domain/failed-job-requeue.service.ts` (wired from `packages/sync/src/app.ts`) +- Operator CLI for exhausted jobs: [CLI](./cli.md#manage-exhausted-sync-jobs) - Diagnostics / retention / principal purge: `packages/sync/src/server/diagnostic.routes.ts`, `packages/sync/src/domain/connection-retention.service.ts`, `packages/sync/src/domain/principal-purge.service.ts` - Sync DB backup/restore CLI: `packages/scripts/src/commands/sync-backup.ts`, `sync-restore.ts` diff --git a/docs/features/google-sync-and-sse-flow.md b/docs/features/google-sync-and-sse-flow.md index 6158e00cc4..9d54fafed2 100644 --- a/docs/features/google-sync-and-sse-flow.md +++ b/docs/features/google-sync-and-sse-flow.md @@ -200,6 +200,29 @@ remember to click **Refresh calendar**. 4. User initiates re-consent via the OAuth flow (proxied to Sync). 5. Sync completes the OAuth exchange; the backend's next metadata fetch/change-feed poll picks up the reconnected state. +## Failed Job Self-Heal (Sync Operator Path) + +Sync workers mark a job `failed` after its per-attempt retry ladder is spent. +Nothing else requeues that row unless the **failed-job self-heal** sweep runs +(`failedJobRequeue` in `packages/sync/src/app.ts`, logic in +`packages/sync/src/domain/failed-job-requeue.service.ts`): + +1. After a ~30 minute cooldown, the sweep requeues cooled-down failed jobs with + a fresh attempt budget (up to `FAILED_JOB_MAX_REQUEUES`, currently 3). +2. Jobs that keep failing past that budget are **exhausted** and need an + operator — see [manage-failed-jobs](../development/cli.md#manage-exhausted-sync-jobs). +3. Exhausted jobs whose connection already has a durable provider read-failure + marker (`lastReadFailureAt`, for example Google `notACalendarUser`) are + **auto-cleared** so their coalescing key no longer blocks rediscovery / + reconnect enqueue. Health already surfaces those provider errors; keeping + the failed row only adds log noise. + +Watch sync logs for: + +- `Sync self-heal sweep requeued N failed job(s)` +- `Sync self-heal sweep cleared N exhausted job(s) blocked by durable provider read failure` +- `Sync self-heal sweep: N failed job(s) exhausted their requeue budget and need operator attention` + ## Rules Of Thumb For Changes - New realtime behavior usually needs changes in `core` diff --git a/docs/frontend/frontend-runtime-flow.md b/docs/frontend/frontend-runtime-flow.md index 4f9961954c..1059db7c83 100644 --- a/docs/frontend/frontend-runtime-flow.md +++ b/docs/frontend/frontend-runtime-flow.md @@ -52,6 +52,40 @@ Important behavior: This is the shell for the main desktop app experience. +## Welcome And Onboarding Tour + +Files: + +- `packages/web/src/components/RootShell/RootShell.tsx` +- `packages/web/src/components/WelcomeModal/WelcomeModal.tsx` +- `packages/web/src/components/OnboardingTour/` + +`RootShell` mounts the welcome modal, onboarding tour, global navigation / +calendar-shell shortcuts, and keyboard-only mode. + +Welcome → tour contract: + +- **Start Now** starts the interactive tour when + `compass.onboarding.has-seen-onboarding-tour` is not yet set +- backdrop dismiss / Escape / Log In / Sign Up mark the tour skipped without + starting it (same seen flag) so the coachmarks do not appear later by surprise +- the tour is **not** an app-lock modal: shortcuts underneath stay live so each + step advances when the user performs the hinted action (`C`, save, Cmd+K / + Escape, `?`) +- step order and copy live in `onboarding.tour.steps.ts` (single source of truth) +- users can reopen it anytime from the command palette (“Restart onboarding tour”) + +Keyboard-only mode (`Shift` `Shift`, also mounted from `RootShell`): + +- blocks pointer clicks while active; scroll and hover remain +- exits via Escape when nothing higher owns Escape, another Shift-Shift, or refresh +- shares the Shift-tap gesture bus with event-jump chips so a second quick Shift + cancels jump mode and enters keyboard-only instead + +See [Shortcuts](../acceptance/shortcuts.md) for acceptance coverage and +[Feature File Map](../development/feature-file-map.md#keyboard-shortcuts) for +file pointers. + ## Session Runtime File: diff --git a/docs/self-hosting/monitoring.md b/docs/self-hosting/monitoring.md index 31b788ce0a..39da650f19 100644 --- a/docs/self-hosting/monitoring.md +++ b/docs/self-hosting/monitoring.md @@ -40,6 +40,7 @@ What to watch, in sync container logs (`./compass logs sync`): - **`Sync scheduler draining, reconciling, renewing channels, retaining, and reporting health`** — logged once at startup when active; confirms the job worker, the reconcile sweep, and subscription renewal are all running. If you only see `Sync retention + health snapshot started (passive / unconfigured)` instead, Sync isn't doing calendar work — check `google.clientId`/`google.clientSecret` are set and `sync.execution: active`. - **`Sync reconcile sweep enqueued N pull(s)`** — logs roughly every 10 minutes when there's stale work to catch up on. This is the fallback for missed push notifications; every connected calendar converges through it even if Google's webhook never arrives. It's normal for this to log `0` most of the time on a healthy install (nothing missed). - **`Sync job {kind} ({id}) dropped: {reason}`** (warn level) — a job settled without completing (e.g. the connection's Google access was revoked). The affected user needs to reconnect; Compass surfaces this in the UI as a "Reconnect Google Calendar" prompt automatically. +- **`Sync self-heal sweep … exhausted … need operator attention`** (error level) — a job burned its retry ladder and the automatic requeue budget. Inventory and clear/requeue with `bun run cli manage-failed-jobs` ([CLI](../development/cli.md#manage-exhausted-sync-jobs)). Durable provider refusals (connection already stamped with a read-failure marker) are auto-cleared by the same sweep so they do not page forever. Unlike the older backend-only sync engine, Sync manages its own push-notification channel renewal internally — there's no separate cron job or maintenance endpoint to wire up. From af5c688489e09540dff1750670cb7f4a4a861582 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 10 Aug 2026 16:05:04 +0000 Subject: [PATCH 2/2] docs(acceptance): match command palette create-event label casing Co-authored-by: Tyler Dane --- docs/acceptance/shortcuts.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/acceptance/shortcuts.md b/docs/acceptance/shortcuts.md index 88fb45770c..7459dec87b 100644 --- a/docs/acceptance/shortcuts.md +++ b/docs/acceptance/shortcuts.md @@ -178,7 +178,7 @@ Pressing Cmd+K opens the command palette from any view, including while a text i 2. Press Cmd+K (or Ctrl+K on Windows). 3. Observe the palette contents. 4. Use the search/filter to type "event". -5. Select "Create Event" from the palette. +5. Select "Create event" from the palette. 6. Press Cmd+K again and then Escape. ### Expected Results