Packet 0.1 · Phase 0 — Restore the safety net
Packet 0.1 — Rewrite stale funnel e2e specs
Make the Playwright suite describe the current free funnel so it can gate every later packet.
Exact implementation
- Rewrite e2e/access-flow.spec.ts to assert the current free funnel.
- Quick-audit other specs for stale copy assertions from the old funnel.
Acceptance criteria
- Suite asserts: landing nav has "Start planning free" → /planner/.
- Login nav link → /planner/sign-in.
- Planner loads ungated with Life Inbox visible.
- No assertions remain on removed copy.
Verification
- npx playwright test green
- preflight green including e2e
Dependencies: None · Rollback: Revert; no product surface touched (this packet only changes test files).
Packet 1.1 · Phase 1 — Vocabulary & copy sweep
Packet 1.1 — Canonical vocabulary sweep (app)
Apply the §7.2 table rows for all src/ surfaces (not landing).
Exact implementation
- Apply every §7.2 table row across the 9 named components (OnboardingTour, PlannerSetupPanel, ToSchedulePanel, AddToPlannerModal, BlockEditor, AppShell, WeekGrid, PlannerHeader, Sidebar).
- Resolve the Google Calendar copy contradiction: Settings → Sources states sync truthfully.
- Update any e2e copy assertions the sweep touches, in the same commit.
Acceptance criteria
- The §7.3 grep checklist passes (zero forbidden terms rendered).
- Tour, modal, editor, inbox and settings read in canonical vocabulary.
- Google Calendar copy contradiction resolved to the true statement.
Preserve
- data-tour anchors unchanged
- Code identifiers (§7.4) unchanged — only user-facing copy moves
Verification
- grep checklist over src/ + landing/ for forbidden terms
- manual read-through
Dependencies: None · Rollback: Revert the commit. Packets are single-commit sized on purpose so rollback is always "revert the commit" (implementation-spec-2026-07-13.md §1, risk minimisation point 4).
Packet 1.2 · Phase 1 — Vocabulary & copy sweep
Packet 1.2 — Canonical vocabulary sweep (landing)
Apply the §7.2 landing rows: nav "Log in" (title keeps save framing), footer Refunds removal.
Exact implementation
- Landing nav: relabel "Save your planner" to "Log in" (title/aria keeps the save framing).
- Remove the footer "Refunds" link until something is purchasable.
- Apply the same nav block change to satellite pages sharing the nav.
Acceptance criteria
- All landing pages show "Log in" in nav.
- No "Refunds" footer link remains live.
- Existing src= params preserved.
Preserve
- bppTrack event names
- waitlist/founder JS (dormant by design, untouched)
- existing src= tracking params
Verification
- grep across landing/*.html
- preflight green (build assembles landing into dist)
Dependencies: None · Rollback: Revert the commit. Packets are single-commit sized on purpose so rollback is always "revert the commit" (implementation-spec-2026-07-13.md §1, risk minimisation point 4).
Packet 2.1 · Phase 2 — Identity & state cleanup
Packet 2.1 — Remove founder-beta identity from free users
Free/anonymous users see no founder/beta/trial identity; quarantine premium screens.
Exact implementation
- Delete the badge in PlannerHeader/component.tsx.
- Replace TrialBanner copy with a neutral "Early access — your feedback shapes what's next", shown only for trial|comped.
- Move SignInScreen, CodeEntryScreen, TrialExpiredScreen into src/components/AccessGate/premiumGating.tsx, imported only under PREMIUM_GATING.
Acceptance criteria
- Anonymous header shows no badge.
- grep "Founder Beta" in rendered free-path code → 0.
- Unit test matrix: each AccessStatus → expected banners.
Preserve
- PREMIUM_GATING flag and behaviour
- RefundWindowBanner (applies to genuinely paid legacy users)
Verification
- screenshots signed-out header
- new unit test file passes
Dependencies: None · Rollback: Revert the commit. Packets are single-commit sized on purpose so rollback is always "revert the commit" (implementation-spec-2026-07-13.md §1, risk minimisation point 4).
Packet 3.1 · Phase 3 — Forgiveness: delete joins undo
Packet 3.1 — Deletes join the undo system
Every delete shows an undo toast; Cmd+Z restores deletes.
Exact implementation
- blockHistory.ts: snapshot gains deletedAt; recordMovement handles delete/restore entries.
- plannerActions.ts: deleteBlock records history; add restoreBlock.
- AppShell/component.tsx: a DeleteToast reusing the DropConfirmationToast pattern; wire all delete callsites through one handler.
- Route ToSchedulePanel, ScheduledBlock and BlockEditor deletes through the shared handler.
Acceptance criteria
- Delete via hover ×, Backspace, action-menu, editor → toast with working Undo.
- Cmd+Z after delete restores the block to its exact prior state (inbox or scheduled slot).
- Redo re-deletes; extended undo-redo.spec.ts passes.
Preserve
- GCal cache blocks keep hard-delete semantics
Verification
- run extended undo-redo.spec.ts
- manual signed-in delete→undo→sync-state check
- grep deleteBlock( callsites to confirm none bypass the handler
Dependencies: None · Rollback: Revert; soft-delete semantics unchanged underneath.
Packet 4.1 · Phase 4 — Enabling refactors
Packet 4a.1 — Extract WeekToolbar (pure refactor)
Move the toolbar JSX and its controls from WeekGrid into src/components/WeekToolbar/component.tsx with identical rendering; delete the hidden ZoomSelect.
Exact implementation
- Extract the toolbar JSX into src/components/WeekToolbar/component.tsx.
- Communicate via props only; persisted-setting hooks stay in WeekGrid and pass down.
- Delete the hidden ZoomSelect duplicate (WeekGrid:483-494).
Acceptance criteria
- Pixel-identical toolbar at 375/768/1280/1536 (manual screenshot diff).
- All view controls function.
- ZoomSelect is gone.
Preserve
- Zero visual/behavioural change except the invisible dead select's removal
Verification
- screenshots before/after
- suite green
Dependencies: None · Rollback: Each Phase 4 packet reverts independently.
Packet 4.2 · Phase 4 — Enabling refactors
Packet 4b.1 — Remove nth-of-type responsive coupling
Replace .week-grid-controls > div:nth-of-type(…) rules with explicit classes (.toolbar-density, .toolbar-hours, …) or useIsMobile() conditional rendering; delete the "must be a span" workarounds.
Exact implementation
- Replace the nth-of-type rules in index.css (412-440, 803-840 region) with explicit classes.
- Update WeekToolbar and WeekGrid to render those explicit classes / use useIsMobile() conditionals.
- Delete the DOM-order-dependent comments/workarounds.
Acceptance criteria
- grep nth-of-type in index.css scoped to toolbar → 0.
- Breakpoint screenshots identical to before.
- Month view still hides density/hours as before.
Preserve
- Month-view hide rules, via the new classes
Verification
- grep nth-of-type scoped check
- breakpoint screenshot sweep
Dependencies: 4.1 · Rollback: Each Phase 4 packet reverts independently.
Packet 4.3 · Phase 4 — Enabling refactors
Packet 4c.1 — Overlay/back handling hook
Browser Back closes the top overlay instead of leaving the app.
Exact implementation
- New src/hooks/useOverlayHistory.ts using history.pushState/popstate with a state marker.
- Wire into AddToPlannerModal, BlockEditor, PlannerSetupPanel, SyncStatusPanel popover; design the hook for N consumers (the future quick-create popover included).
Acceptance criteria
- Open editor → Back → editor closes, planner state intact, URL clean.
- Back again → leaves the app.
- Same behaviour for modal & setup panel.
- iOS swipe-back manually verified.
Preserve
- Escape behaviour unchanged
- deep-link restore (?p=) unaffected
Verification
- overlay-back.spec.ts green
- hook unit tests
- manual iOS swipe-back check
Dependencies: None · Rollback: Each Phase 4 packet reverts independently.
Packet 4.4 · Phase 4 — Enabling refactors
Packet 4d.1 — Tour engine rebuild (same content)
Eliminate first-click swallowing and clickable-dimmed-UI; keep step order/copy identical.
Exact implementation
- Replace the per-frame rAF re-measure loop with ResizeObserver + scroll/orientation listeners + a 250ms settle debounce.
- One full-screen overlay using an SVG mask (cut-out = target); pointer-events auto everywhere except the cut-out; block outside interaction on every step.
- Replace 150ms DOM polling with existing Dexie live counts plus a MutationObserver for modal-open detection.
- If a step's target vanishes, regress to the previous step rather than stalling.
- Keep ?tour=1, existing keys, and the planner:start-tour event.
Acceptance criteria
- Single-click test: each spotlit target responds to exactly one click, 20/20 repetitions in Playwright.
- Dimmed-area clicks do nothing on every step.
- No continuous rAF work while idle (performance trace).
- Existing tour.spec.ts still passes.
Preserve
- Step order and copy identical (content changes are Phase 5)
Verification
- tour-reliability.spec.ts (20-click loop)
- DevTools performance trace showing no idle rAF work
Dependencies: None · Rollback: Each Phase 4 packet reverts independently.
Packet 5.1 · Phase 5 — One onboarding
Packet 5.1 — Onboarding single-owner + tour v3 content
Implement §8 fully: exactly one onboarding surface visible at any moment.
Exact implementation
- OnboardingTour: intro card, step copy in canonical vocabulary, key bump to v3 with v2-read migration, mention the P-key placement shortcut.
- WeekGrid: EmptyWeekPrompt → the single ambient card (§8.4), gated on tour state.
- ToSchedulePanel: delete the TRY THIS card.
- AccessGate: suppress the save banner while the tour is active (full retirement is Phase 8).
- analytics.ts: add tour + suppression events.
Acceptance criteria
- At no point during the tour are EmptyWeekPrompt, TRY THIS, or the save banner visible.
- Skipping the tour reveals exactly one ambient empty-state.
- Tour completion sets the same suppression state as skip.
- Playwright asserts only-one-surface across fresh, tour-active, skipped, completed and has-data states.
Verification
- manual first-run on desktop + iPhone viewport
- Playwright: tour completes end-to-end by real interactions
- analytics events verified in local console provider
Dependencies: 1.1, 4.4 · Rollback: Revert to v2 tour (kept intact until Phase 9 cleanup).
Packet 6.1 · Phase 6 — One interaction grammar
Packet 6a.1 — Selection + BlockActionBar + unified click grammar
Click = select, double-click = edit, everywhere; one action bar replaces hover buttons and MobileSelectedBlockControls.
Exact implementation
- New BlockActionBar/component.tsx: Edit · Duplicate · Delete · (Place | To Inbox contextual).
- ScheduledBlock: remove the hover action cluster, keep the selection ring.
- ToSchedulePanel: card click → select; remove the hover ×/⧉ cluster.
- WeekGrid: unify month-chip and all-day-chip click handlers.
- AppShell: render the bar for selectedBlockId (desktop anchored / mobile bottom sheet); delete MobileSelectedBlockControls.
- Apply user-select: none to grid chrome during any active drag (desktop and touch).
Acceptance criteria
- The interaction-grammar table holds for inbox card, scheduled block, all-day chip and month chip.
- No hover-only actions remain (grep group-hover:flex action clusters → 0).
- Touch: tap selects and shows the sheet.
- scheduled-block, life-inbox and month-view specs updated & green.
Preserve
- Existing wasDragging click-suppression pattern
Verification
- manual mouse/touch matrix
- scheduled-block.spec.ts, life-inbox.spec.ts, month-view.spec.ts green
Dependencies: 4.3, 4.4 · Rollback: Revert the commit. If activation metrics dip after this lands, the source is explicit that the mitigation is action-bar prominence, not reverting the grammar itself.
Packet 6.2 · Phase 6 — One interaction grammar
Packet 6b.1 — Edge-drag resize on scheduled blocks
Top/bottom resize handles per the interaction grammar table.
Exact implementation
- ScheduledBlock: add top/bottom drag handles with their own pointer logic and pointer capture.
- plannerActions.ts: resizeBlockDuration already exists — add a start-edge variant that shifts startTime and duration together.
- Overnight head/tail: disable the head bottom handle at the midnight boundary (existing crossesMidnight semantics); tail is bottom-only.
Acceptance criteria
- Drag bottom edge 30m→60m updates endTime/duration.
- Drag top edge moves start, keeps end.
- Undo restores the prior duration; keyboard +/- unchanged.
- New resize.spec.ts green.
- No accidental block-move when grabbing a handle (20-repetition test).
Preserve
- Existing keyboard +/- resize path
Verification
- resize.spec.ts
- 20-repetition handle-grab test
Dependencies: 6.1 · Rollback: Revert the commit. Packets are single-commit sized on purpose so rollback is always "revert the commit" (implementation-spec-2026-07-13.md §1, risk minimisation point 4).
Packet 6.3 · Phase 6 — One interaction grammar
Packet 6c.1 — Slot quick-create popover + keyboard place
The §6.2 popover replaces full-drawer-on-slot-click; P places the selected inbox card.
Exact implementation
- New QuickCreatePopover/component.tsx: title input (autofocused), duration chips (15/30/60/custom), Add, "More options →" to the full editor prefilled.
- DayColumn: route slot click to the popover.
- AppShell: P-key handler using the existing findPastePlacement engine with the slot/selection as preferred slot; keep handleSlotClick fallback to the full editor via "More options".
- analytics.ts: add quick_create_used, task_placed.method.
Acceptance criteria
- Clicking Wed 10:00 at every zoom/density/text-scale combination prefills Wed 10:00 (parameterised Playwright test).
- Escape dismisses without creating anything.
- "More options" opens the full editor with the same prefill.
- P with an inbox item selected places it at the next free slot and fires the drop toast.
Verification
- parameterised Playwright slot-prefill test matrix
Dependencies: 4.3, 6.1 · Rollback: Revert the commit. Packets are single-commit sized on purpose so rollback is always "revert the commit" (implementation-spec-2026-07-13.md §1, risk minimisation point 4).
Packet 7.1 · Phase 7 — One toolbar, one size system per platform
Packet 7.1 — Toolbar consolidation + View popover
Implement the §5.1/§9 final toolbar.
Exact implementation
- Rebuild WeekToolbar contents: Day/Week/Month stays visible; move week-span, density and hours into a single View ▾ popover.
- Normalise selected-state colour to the standard accent tint (remove the green fill on span pills).
- "N tasks this week" and the colour legend move into the popover footer.
- Delete the overflow-scroll band styles in index.css.
Acceptance criteria
- No horizontal scrolling or truncation at 320-1536px (Playwright viewport assertions).
- Every previous setting reachable in ≤2 clicks.
- Settings survive reload.
- Month view shows only Day|Week|Month + View ▾.
Preserve
- Persisted setting keys and values
Verification
- breakpoint sweep
- settings persistence test
Dependencies: 4.1, 4.2 · Rollback: Revert; persisted keys untouched.
Packet 7.2 · Phase 7 — One toolbar, one size system per platform
Packet 7.2 — Text-size relocation + header cleanup + drag-only edge rails
Remove the header −%+ control; Appearance section gains "Planner text size"; edge rails render only during drag.
Exact implementation
- PlannerHeader: remove TextZoomControls.
- PlannerSetupPanel: make the Appearance section real — "Planner text size" (A− / 100% / A+, live preview, same localStorage key planner.textScale, same clamp 0.9-1.3).
- AppShell: change the edge-rail render condition to isDraggingBlock only; text-scale state stays in AppShell.
Acceptance criteria
- Header has no %-control.
- Settings slider/steppers adjust scale live and persist.
- Rails are absent at rest, present during drag with working drop/week-switch.
- Keyboard week nav unaffected.
Preserve
- Same localStorage key and clamp for text scale
- Existing keyboard week navigation (header arrows)
Verification
- manual verification: header has no control; Settings adjusts and persists; rails behave correctly during/after drag
Dependencies: 7.1 · Rollback: Revert the commit. Packets are single-commit sized on purpose so rollback is always "revert the commit" (implementation-spec-2026-07-13.md §1, risk minimisation point 4).
Packet 8.1 · Phase 8 — Anonymous persistence & sign-in funnel
Packet 8.1 — Storage chip + milestone nudges + banner retirement
Replace the one-shot save banner with a truthful persistent indicator plus milestone nudges.
Exact implementation
- AccessGate: delete SavePlannerBanner.
- SyncStatusPanel: chip states — "On this device" (anonymous) / sync labels (signed-in); update the panel headline copy.
- Nudge component (toast-style, reusing existing toast primitives) fires at ≥5 placed tasks or a second distinct day of use.
- analytics.ts: add nudge events.
Acceptance criteria
- The §8.6 state matrix holds (at most one onboarding/persistence surface visible at any time).
- Banner code is deleted.
- Nudge fires exactly once at ≥5 placed tasks (Playwright seeds data).
Verification
- state matrix Playwright coverage
- Playwright: chip reflects task count, nudge appears once at threshold
Dependencies: 5.1 · Rollback: Revert the commit. Packets are single-commit sized on purpose so rollback is always "revert the commit" (implementation-spec-2026-07-13.md §1, risk minimisation point 4).
Packet 9.1 · Phase 9 — IA & accessibility polish
Packet 9.1 — Filters redesign
Replace All/Imported/Planner-created/Manual/Travel with: Created here / Imported (provenance, multi-select) + Show travel time (display toggle) + implicit All when none active.
Exact implementation
- plannerFilters.ts: rewrite the predicates around one provenance axis.
- AppShell PlannerSidePanel: update the filter UI.
- PlannerSetupPanel: make the Filters section copy truthful.
Acceptance criteria
- No two filters have overlapping predicates.
- Unit tests enumerate block fixtures × filters.
- Side-panel count badge stays accurate.
Verification
- unit tests over block fixtures × filters
Dependencies: None · Rollback: Revert the commit. Packets are single-commit sized on purpose so rollback is always "revert the commit" (implementation-spec-2026-07-13.md §1, risk minimisation point 4).
Packet 9.2 · Phase 9 — IA & accessibility polish
Packet 9.2 — Settings cleanup + focus traps + contrast/type floor
Delete placeholder Settings sections; apply a shared focus trap to modal/editor/setup/popovers; raise the minimum rendered font size and re-measure contrast.
Exact implementation
- PlannerSetupPanel: delete the Filters/Advanced placeholder cards; Appearance is now real (from 7.2); Sources becomes one truthful paragraph inside General.
- New src/hooks/useFocusTrap.ts, applied to modal/editor/setup/popovers.
- Raise the minimum rendered font size to an 11px-equivalent floor; re-measure text-muted contrast to ≥4.5:1, adjusting the token rather than per-use overrides.
Acceptance criteria
- Settings has no "Prepared for later" cards.
- Tab cannot escape an open overlay.
- axe-core Playwright pass on the planner and each overlay with zero serious/critical issues.
- No rendered text below 11px at scale 1.0.
Verification
- axe-core Playwright pass
- manual Tab-cannot-escape check on every overlay
Dependencies: 7.2 · Rollback: Revert the commit. Packets are single-commit sized on purpose so rollback is always "revert the commit" (implementation-spec-2026-07-13.md §1, risk minimisation point 4).
Packet 9.3 · Phase 9 — IA & accessibility polish
Packet 9.3 — Add-flow consolidation (§5.7)
The Add modal's menu becomes exactly three options — Quick add · New task with details · Import… — with the two import rows collapsed behind one tabbed view and the disabled Templates row deleted.
Exact implementation
- AddToPlannerModal: render exactly three children in order — the unchanged quick-add form (still autofocused), "New task with details", "Import…".
- Delete the disabled Templates (Coming Soon) button (Templates stay reachable via the sidebar TemplatePanel, unchanged).
- Collapse Import from Source + Import Calendar into one "Import…" row opening the existing view === 'paste' view.
- That view gains a two-tab strip: Paste a list (default, renders today's content unchanged) | Calendar (today's explanatory paragraph + <CalendarImport />).
- Delete the 'importCalendar' view value; do NOT rename the 'paste' view value or the initialView prop type.
- Demote the existing Quick Import | AI Import control visually (32px high, 12px text, no bordered card, label "Paste format") so two tab strips never stack.
- Tabs get role="tablist"/role="tab" + aria-selected and arrow-key roving tabindex, matching WeekToolbar's segmented-control pattern.
Acceptance criteria
- Menu shows exactly three options; grep -r "Templates (Coming Soon)|Import from Source|Import Calendar" src/ → 0.
- "Import…" opens the tabbed view; Paste a list reaches Review with a draft list identical to today's; Calendar reaches the Google connect + .ics upload controls.
- initialView='paste' (the empty-week "Paste a list" CTA) still opens the import view on the Paste tab.
- Back from the import view returns to the menu; Back from Review returns to the import view; Escape closes the modal; Tab cannot escape it.
- Tabs operable by keyboard: arrows move focus, Enter/Space activates, aria-selected correct.
- axe pass on the modal in both tabs: zero serious/critical.
- New e2e/add-flow.spec.ts green; existing suite gains no net-new failures.
Preserve
- Templates remain reachable through the sidebar TemplatePanel
- data-tour anchors untouched
- No analytics event names added or changed
Verification
- e2e/add-flow.spec.ts
- e2e/accessibility.spec.ts (extended)
- manual Back/Escape/Tab walkthrough
Dependencies: 4.3 · Rollback: Revert the commit. Packets are single-commit sized on purpose so rollback is always "revert the commit" (implementation-spec-2026-07-13.md §1, risk minimisation point 4).
Packet 10.1 · Phase 10 — Performance (conditional)
Packet 10.1 — Performance measurement (gate packet)
Produce the profile that decides whether Phase 10 work happens at all.
Exact implementation
- Seed 300 blocks via console/Dexie (scratch scripts only — no product files change).
- Record traces (initial render, week nav, drag) at 4× CPU throttle, desktop + mobile viewport.
Acceptance criteria
- A written note (docs/perf-2026-07.md): frame times, dropped frames during drag, live-query counts.
- An explicit go/no-go recommendation for virtualisation, backed by the evidence gathered.
Verification
- note committed; decision recorded
Dependencies: None · Rollback: Revert the commit. Packets are single-commit sized on purpose so rollback is always "revert the commit" (implementation-spec-2026-07-13.md §1, risk minimisation point 4).
Packet 11.1 · Phase 11 — Responsive integrity (device-parity pass)
Packet 11.1 — Mobile week: readable, snapping day columns
Each mobile day column is readable and tappable; the week scrolls in intentional day steps; the page never scrolls horizontally; the first day is visible at rest. (Defects M1, M3.)
Exact implementation
- index.css: mobile .day-column / .week-day-heading / .week-grid-shell / .week-time-gutter rules.
- Possibly WeekGrid/component.tsx if CSS alone cannot satisfy the acceptance criteria.
Acceptance criteria
- At 320/375/390/430px: every rendered day column ≥96px wide, showing a 2-line (not 1-char) title.
- No day label overlaps another.
- document.documentElement.scrollWidth === clientWidth (page fixed) while .week-grid-shell scrollWidth may exceed its clientWidth.
- The time gutter stays pinned at the left edge after a horizontal scroll.
- The first column's left edge sits at or after the gutter's right edge at rest.
Preserve
- Desktop columns unchanged
Verification
- scripts/responsive-diag.mjs at 320/375/390/430px
- desktop columns re-checked unchanged
Dependencies: 11.2 · Rollback: Revert the commit. Packets are single-commit sized on purpose so rollback is always "revert the commit" (implementation-spec-2026-07-13.md §1, risk minimisation point 4).
Packet 11.2 · Phase 11 — Responsive integrity (device-parity pass)
Packet 11.2 — No header/block overlap; floating controls never cover content
The sticky day-header never overlaps the first block row; the FAB, storage banner and Life Inbox tray never cover events, the time gutter, the now-marker, or each other. (Defects M2, M4, M5.)
Exact implementation
- index.css: .week-days-header, .mobile-add-fab, .mobile-life-inbox-tray, storage-chip/banner rules.
- Possibly AppShell if CSS positioning alone cannot satisfy the acceptance criteria.
Acceptance criteria
- At 320-430px: day-header bottom ≤ first-block top (zero overlap).
- FAB intersects 0 scheduled blocks, 0 now-marker, and does not intersect the storage-banner rect.
- Storage-banner rect does not intersect the tray rect.
Preserve
- Tour dimming behaviour (body[data-tour-step])
Verification
- scripts/responsive-diag.mjs at 320-430px
- manual check that tour dimming still works
Dependencies: None · Rollback: Revert the commit. Packets are single-commit sized on purpose so rollback is always "revert the commit" (implementation-spec-2026-07-13.md §1, risk minimisation point 4).
Packet 11.3 · Phase 11 — Responsive integrity (device-parity pass)
Packet 11.3 — Tablet mid-zone readable
At 768-1000px the grid gets readable column width instead of being squeezed by the 240px Life Inbox sidebar. (Defect T1.)
Exact implementation
- AppShell/component.tsx: change the sidebar default-collapsed logic for the 768-1000px band (user can still reopen it).
- index.css if needed alongside the logic change.
Acceptance criteria
- At 768/834px: the Life Inbox sidebar is collapsed by default, so grid width ≥ viewport − 60px and day columns ≥ ~90px.
- 1024/1280/1440px layouts unchanged from baseline.
Preserve
- Desktop ≥1024px layout and column widths unchanged from baseline
Verification
- scripts/responsive-diag.mjs at 768/834px
- re-check 1024/1280/1440 unchanged
Dependencies: 11.1 · Rollback: Revert the commit. Packets are single-commit sized on purpose so rollback is always "revert the commit" (implementation-spec-2026-07-13.md §1, risk minimisation point 4).