Skip to content

fix: quick wins — monthly recurrence, blur, SSR fix#27

Merged
vayungodara merged 27 commits into
mainfrom
test/streak-templates-tests
Mar 22, 2026
Merged

fix: quick wins — monthly recurrence, blur, SSR fix#27
vayungodara merged 27 commits into
mainfrom
test/streak-templates-tests

Conversation

@vayungodara

Copy link
Copy Markdown
Owner

Summary

  • handleMiss double-submit guard in PactCard (prevents rapid-click duplicate DB writes)
  • Monthly recurrence option added to pact creation (UI button + DB constraint updated)
  • Overlay blur — dashboard blurs behind modal when opened
  • SSR fix — guard createPortal with typeof document check
  • useStreakFreeze export alias for DailySummaryCard compatibility

DB changes applied

  • pacts CHECK constraint updated to allow 'monthly' recurrence
  • 15 abandoned focus sessions cleaned up
  • Stale streak reset for user 419f05e6

Test plan

  • Build passes
  • 104/104 unit tests pass
  • Open New Pact modal — overlay blurs dashboard
  • Toggle recurring → Monthly button appears
  • Rapid-click "I missed it" on a pact — only one update fires
  • No SSR errors on page load

🤖 Generated with Claude Code

vayungodara and others added 10 commits March 19, 2026 17:55
Install Vitest + happy-dom + v8 coverage and add 77 unit tests
across 5 test files covering lib/streaks, lib/gamification,
lib/activity, lib/accentColors, and lib/streaks-advanced.

Includes reusable Supabase mock helper (tests/setup/supabase-mock.js)
and npm scripts: test, test:watch, test:coverage.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Two-step pact creation flow: template picker grid with 8 preset
templates, then customizable form. Templates pre-fill title,
description, recurrence, and suggested deadline time.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace unlimited weekly free freezes with pool-based system:
- Users start with 2 freezes, earn more at streak milestones (7/14/30d)
- Max pool: 5 freezes, 3-day cooldown between uses
- UI shows freeze count badge, cooldown timer, tier-based streak icons
- SQL migration adds streak_freezes_remaining + streak_freeze_last_used

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
5-agent parallel review (security, performance, architecture, testing,
accessibility) identified 26 issues. All 26 now resolved:

Accessibility (critical): dialog ARIA semantics, focus trap, Escape key
Accessibility (high): aria-labels, role="alert", aria-pressed states
Accessibility (medium/low): focus-visible outlines, reduced-motion CSS,
  decorative SVG aria-hidden, recurrence button states
Security: maxLength on inputs, generic error messages (no DB leak)
Architecture: extracted resetFormState(), useModalScrollLock hook (shared
  across 5 modals), removed dead code (recurrenceLabel fn, unused CSS)
Performance: memoized filteredTemplates/today, fixed AnimatePresence exit
  animations, replaced transition:all with specific properties
Testing: 25 new unit tests for pactTemplates.js data integrity

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Root cause: lib/pactTemplates.js (20 templates, 5 categories) was not
included in the previous commit, causing CI test failures and empty
template grid on GitHub-deployed version.

Additional fixes from Claude's PR review:
- Error icon SVG now has aria-hidden in CreatePactModal
- resetFormState wrapped in useCallback for exhaustive-deps compliance
- useStreakFreeze returns generic error message (no DB detail leak)
- FREEZE_COOLDOWN_DAYS constant imported in DailySummaryCard (was hardcoded)
- Exported FREEZE_COOLDOWN_DAYS from streaks-advanced.js

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…-tests

# Conflicts:
#	components/DailySummaryCard.js
- PactCard: add isCompletingRef guard to handleMiss (prevents double-submit)
- CreatePactModal: add Monthly recurrence button + hint text
- pactTemplates.test.js: add 'monthly' to valid recurrence types
- streaks-advanced: export useStreakFreeze alias for applyStreakFreeze
- DB: cleaned 15 abandoned focus sessions, reset stale streak,
  updated CHECK constraint to allow 'monthly' recurrence

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Overlay now blurs dashboard behind modal (backdrop-filter: blur(8px))
- Guard createPortal with typeof document check to prevent SSR crash

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@vercel

vercel Bot commented Mar 21, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
lockin Ready Ready Preview, Comment Mar 22, 2026 11:03am

@vayungodara

Copy link
Copy Markdown
Owner Author

@claude review

@claude

claude Bot commented Mar 21, 2026

Copy link
Copy Markdown

Claude finished @vayungodara's task in 3m 0s —— View job


PR #27 Code Review

  • Gather context
  • Read changed files
  • Review: PactCard.js (handleMiss guard)
  • Review: CreatePactModal.js (monthly recurrence)
  • Review: Modal components (overlay blur, SSR fix)
  • Review: lib/streaks-advanced.js
  • Review: lib/pactTemplates.js + tests
  • Review: useModalScrollLock.js
  • Post final review

Overall this is a solid PR with good attention to UX polish and defensive coding. A few real bugs need fixing before merge.

Bugs

🔴 Monthly recurrence causes wrong toast text (PactCard.js:124, PactCard.js:163)

Both handleComplete and handleMiss use a ternary that doesn't account for 'monthly':

// Line 124 (handleComplete) and 163 (handleMiss)
pact.recurrence_type === 'daily' ? 'day' : pact.recurrence_type === 'weekly' ? 'week' : 'weekday'

When recurrence_type === 'monthly', this falls through to 'weekday', so the user sees "It'll reset for the next weekday" instead of "month". Fix this →


🔴 No SQL migration file for monthly constraint (mentioned in PR description only)

The DB constraint in supabase/checkpoint8_complete.sql:23 and supabase/new_features_schema.sql:9 only allows ('daily', 'weekly', 'weekdays'):

CHECK (recurrence_type IS NULL OR recurrence_type IN ('daily', 'weekly', 'weekdays'))

The PR says "DB changes applied" but there's no migration file. A fresh Supabase setup or a new dev environment would reject 'monthly' at the DB level, silently failing with a constraint violation. Add a migration file (e.g., supabase/add_monthly_recurrence.sql) that drops and recreates the constraint. Fix this →


🔴 mark_overdue_pacts.sql doesn't handle monthly recurrence (supabase/mark_overdue_pacts.sql:34-46, 70-81)

The stored procedure's CASE recurrence_type block falls through to else for monthly pacts — which adds 1 day. Monthly pacts would cycle daily instead of monthly. The else branch needs a when 'monthly' case:

when 'monthly' then
  next_dl := next_dl + interval '1 month';

This affects both the completed/missed block and the active-overdue block. Fix this →


Minor Issues

🟡 useStreakFreeze naming (lib/streaks-advanced.js:75)

export const useStreakFreeze = applyStreakFreeze;

The use prefix conventionally signals a React hook. applyStreakFreeze contains no React hooks, so ESLint's react-hooks/rules-of-hooks may warn about calling it conditionally or outside a component. The alias works today but is risky if the rules-of-hooks lint rule is ever enabled. Consider renaming the export to applyStreakFreeze and updating the one call site in DailySummaryCard.js.

🟡 SSR fix applied inconsistently across modals

CreatePactModal.js:183 has the typeof document !== 'undefined' guard, but CreateGroupModal.js:101, CreateTaskModal.js:96, and JoinGroupModal.js:103 use {isOpen && createPortal(...)}. These are safe today because isOpen starts as false and is set only in effects (which don't run during SSR). But the inconsistency could confuse future contributors. One pattern or the other — pick one.

🟡 useModalScrollLock cleanup is unconditional (lib/useModalScrollLock.js:9-12)

return () => {
  document.body.style.overflow = '';
  document.documentElement.removeAttribute('data-modal-open');
};

The cleanup always removes data-modal-open and overflow: hidden, even when the effect fired with isOpen=false (meaning it set nothing). If two modals are ever open at the same time, closing one would re-enable body scroll while the other stays open. Not a bug with the current UI, but fragile. A common fix is a ref-count or moving the cleanup inside the if (isOpen) branch.


Positives

  • handleMiss guard — correct use of isCompletingRef shared across both handleComplete and handleMiss to prevent concurrent double-submits. Clean approach.
  • Accessibilityrole="dialog", aria-modal, aria-labelledby, aria-pressed on recurrence buttons, Escape key, and focus management are all implemented correctly in CreatePactModal.
  • resetFormState extraction — eliminates the 3× duplication noted in the old code, and useCallback prevents unnecessary re-renders.
  • Test coverage — 238-line test file for streaks-advanced, proper Supabase mock setup, realistic edge cases (cooldown, pool cap, milestone boundary). Solid foundation.
  • useModalScrollLock hook — clean extraction, shared correctly across all modal components.

Summary: 3 bugs to fix before merge (monthly toast text, missing migration file, missing monthly case in stored procedure). The rest are minor consistency/style issues that can be addressed in follow-up.

6-agent parallel execution addressing frontend audit (6.3→target 8+)
and morning report bugs.

DESIGN REFRESH:
- Kill gradient monoculture: solid accent on 19 non-CTA elements,
  gradient reserved for primary buttons + hero only
- Replace glass with solid backgrounds on 19 content card selectors,
  glass kept on sidebar/nav/modals only
- Tighten spacing: card padding 20px→16px, vary border-radius by
  context (cards 14px, inputs 10px, modals 22px, pills full)

NEW FEATURES:
- Skeleton loading states: new Skeleton component with shimmer
  animation, replaces spinners on 4 dashboard pages
- Groups page redesign: member avatar stacks, task progress bars,
  deterministic color icons, refined hover effects

BUG FIXES:
- FocusContext: proper async cleanup on unmount, localStorage
  try/catch for corrupted data
- PactCard: isolated streak/XP chain (no cascade failures),
  15s safety timeout on isCompletingRef
- GroupDetailClient: N+1 query → single join (21 calls → 1)
- DailySummaryCard: 4 sequential queries → Promise.all (~300ms saved)
- ActivityItem: wrapped in React.memo (prevents O(n) re-renders)

35 files changed, ~880 insertions, ~400 deletions

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
8-agent parallel execution addressing all remaining frontend audit findings.

GRADIENT: 4-stop rainbow → subtle 2-stop indigo (#4338CA → #5B5EF5)
on all gradient vars including celebration and subtle variants.

DARK MODE: Warmer backgrounds (#08090D → #0C0C0E, #0E1117 → #111113)
shifting from cold blue to warm neutral.

TEXTURE: Added SVG noise overlay at 3% opacity on body::after for
organic depth (Linear/Craft.do pattern).

LANDING PAGE: Removed template icon boxes (52px → 32px inline),
simplified step circles, reduced hero padding, added browser mockup
product showcase section with perspective tilt.

EMPTY STATES: Personality copy — "Suspiciously empty" for pacts,
"No stats yet" for stats, contextual filter-aware messages.

PACTCARD: Deadline urgency colors (green >24h, yellow <24h, red <12h,
pulsing <6h). Refined hover (border-color shift, no translateY).

DASHBOARD: Stat cards with colored left borders by type (streak=indigo,
pacts=green, focus=blue, XP=amber). Animated count-up numbers with
ease-out cubic via requestAnimationFrame. Shadow-only hover.

SOUND: New lib/sounds.js — Web Audio API generated sounds for
completion pop, timer chime, streak milestone, miss tone. Toggleable
via localStorage. No external audio files needed.

FRAMER MOTION: Replaced whileHover spring with CSS transitions on
mundane elements (Sidebar nav items, logo, buttons; Navbar CTA;
FocusTimer buttons). Kept Framer for layoutId, entrance animations,
AnimatePresence, and whileTap. Tuned navPillSpring to smoother values.

ACTIVITY FEED: Added missing activity type icons (streak, freeze,
challenge). Restructured layout with inline timestamps, stronger
actor/action hierarchy, hover backgrounds, and neutral timeline line.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- PactCard: playCompletionSound() on complete, playMissSound() on miss
- FocusTimer: replaced inline sound with richer playTimerChime() from
  lib/sounds.js (3-note C-E-G chime)
- Removed old inline audio code from FocusContext (duplicated by sounds.js)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… cleanup, urgency colors, stat cards, empty states, Cmd+K palette

Addresses remaining items from the March 21 Frontend Audit (6.3→8+ target):

DESIGN:
- Warmer dark mode: shift all blue-tinted grays to warm neutral zinc
- SVG noise texture on body::after at 3% opacity (Linear/Raycast style)
- Landing page: remove floating cards, bold step numbers, asymmetric grid
- Stat cards: streak card with flame glow, colored left borders on others
- Empty states: personality copy + custom SVG illustrations on Pacts/Groups
- PactCard: deadline urgency colors (green >24h, yellow 12-24h, red <12h, pulse <6h)

FEATURES:
- Command Palette (Cmd+K): search, keyboard nav, theme toggle, navigation
- Timezone detection: auto-persist user IANA timezone for streak calculations
- Konami code easter egg with confetti
- Timezone-aware streak risk detection and freeze system

FIXES:
- Streak test dates: add Z suffix for proper UTC interpretation
- Modal UI refreshes (CreateGroup, CreateTask, Onboarding)
- DailySummaryCard enhancements

28 files changed, ~840 insertions, ~390 deletions

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…imezone, parallel queries

Addresses remaining items from the March 21 Morning Report:

BUGS:
- ActivityFeed: fix stale closure in loadMore by using refs for
  hasMore/isLoadingMore inside IntersectionObserver callback
- StatsPageClient: pass user timezone to calculateStreak (was
  defaulting to UTC, mismatching local completions)

SECURITY:
- GroupDetailClient: UUID validation on route param — returns 404
  for malformed IDs before any DB query
- CreatePactModal: deadline capped at 1 year from today (max attr)
- Cron routes: verified case-insensitive (Web Headers API) — no fix needed

PERFORMANCE:
- gamification.js: 6 sequential unlockAchievement calls → Promise.all()
  (~5-6x latency improvement on pact completion path)

104 tests pass, build clean.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- CreatePactModal: fix template grid clipping — modal uses flex column
  with scrollable body sections, header stays pinned
- CommandPalette: "Create New Pact" now opens modal directly via
  onCreatePact callback instead of navigating to /dashboard/pacts
- DashboardLayout: wire CommandPalette → CreatePactModal with state

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- CommandPalette: overlay changed from motion.div to plain div —
  Framer Motion's transform breaks backdrop-filter compositing.
  Added CSS fade-in animation instead. Blur now works correctly.
- CreatePactModal: category pills changed from overflow-x scroll
  to flex-wrap so all 5 pills are visible without clipping

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Removed AnimatePresence wrapper and backdrop-filter from overlay.
Uses same approach as working modals: plain div, rgba(0,0,0,0.4)
background, conditional render with early return.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The blur mechanism uses html[data-modal-open] #dashboard-layout
{ filter: blur(12px) } — all other modals trigger this via the
useModalScrollLock hook. Added the same hook to CommandPalette.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
P0 CRITICAL:
- FocusContext: fix stale closure in timer interval (ref sync in
  render body + optional chaining guard)
- FocusContext: fix race condition in session creation (null check
  on data.id before ref assignment)
- Cron: batch N+1 queries — check-streak-risk 2N→2 queries,
  cleanup N→1 upsert

P1 HIGH:
- Auth callback: fix open redirect with URL origin validation
  (blocks //evil.com, javascript:, data: schemes)
- DashboardClient: throttle mark_overdue_pacts to once per day
- GroupDetailClient: remove duplicate userIds variable
- FocusContext: clarify useFocusSafe contract (JSDoc)

P2 MEDIUM:
- email.js: complete HTML escaping (quot, apos, backtick, null guard)
- next.config.mjs: lock image remote to specific Supabase project
- activity.js: null guard on getBatchReactions return value

DESIGN:
- globals.css: restore styled thin scrollbars (a11y fix)
- globals.css: neutral shadows/borders in light theme (remove AI tint)
- Landing page: particle background → CSS mesh gradient
- FocusTimer: document.title shows countdown during sessions

104 tests pass, build clean.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Restore ParticleBackground component (removed by mistake in
  mesh gradient change — user wants particles)
- Fix scroll: remove min-height: 100dvh from .hero — was making
  hero fill entire viewport, hiding all content below the fold

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Restore 3 floating notification cards (Pact completed, 2 hours left,
  Alex joined) with staggered entrance + ambient bob animations
- Increase hero padding-top to 128px (desktop) / 96px (mobile) to
  clear fixed navbar with breathing room

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The asymmetric first-card span 2 left Streaks & Stats alone on
the last row with a gap. Reverted to equal 3-column grid.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- ActivityFeed: replace useEffect+deps with callback ref pattern
  for IntersectionObserver — observer created once on mount, not
  recreated on every isLoading toggle
- Toast: type-aware progress bar colors, error duration sync (6s),
  info type uses var(--info) instead of accent, 4px left borders

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@vayungodara

Copy link
Copy Markdown
Owner Author

@claude review

@claude

claude Bot commented Mar 22, 2026

Copy link
Copy Markdown

Claude finished @vayungodara's task in 1m 42s —— View job


PR #27 Code Review

  • Read changed files
  • Check if previous bugs were fixed
  • Review new components (CommandPalette, Skeleton, sounds.js)
  • Review SQL/DB changes
  • Review test coverage
  • Post final review

Status: 3 bugs from previous review remain open

The previous Claude review flagged 3 red bugs. None of them have been fixed in the current branch.


Bugs (still open)

🔴 Monthly recurrence toast shows wrong period (components/PactCard.js:159, 213)

Both handleComplete and handleMiss still use the same two-arm ternary:

pact.recurrence_type === 'daily' ? 'day' : pact.recurrence_type === 'weekly' ? 'week' : 'weekday'

monthly falls through to 'weekday' → user sees "It'll reset for the next weekday". Needs a third arm:

pact.recurrence_type === 'daily' ? 'day'
  : pact.recurrence_type === 'weekly' ? 'week'
  : pact.recurrence_type === 'monthly' ? 'month'
  : 'weekday'

Fix this →


🔴 No SQL migration for 'monthly' constraint (supabase/checkpoint8_complete.sql:23, supabase/new_features_schema.sql:9)

Both files still only allow ('daily', 'weekly', 'weekdays'):

CHECK (recurrence_type IS NULL OR recurrence_type IN ('daily', 'weekly', 'weekdays'))

streak_freeze_redesign.sql was added as a migration pattern — same pattern needed for monthly. A fresh Supabase setup will reject monthly pacts at the DB level, silently returning a constraint violation error. Fix this →


🔴 mark_overdue_pacts.sql silently cycles monthly pacts daily (supabase/mark_overdue_pacts.sql:34-46, 70-81)

Both CASE blocks still have no when 'monthly' arm. Monthly pacts fall through to else → next_dl + interval '1 day', so an overdue monthly pact gets reset every day instead of every month. Fix this →


New code review

CommandPalette.js — good overall. Two small notes:

  • ARIA wiring is incomplete: the <input> has no aria-controls pointing to the <ul>, and the <ul role="listbox"> is disconnected from the input. Screen readers won't associate the input with the results. Add aria-controls="cmd-palette-list" on the input and id="cmd-palette-list" on the <ul>.
  • Silent failure when onCreatePact not passed (line 89): ⌘N is listed as a shortcut in the command list, but if onCreatePact is undefined the command silently does nothing. Either omit that command when the prop is absent, or ensure every CommandPalette usage site passes it.

sounds.js — solid. One edge case: browsers sometimes auto-suspend AudioContext after inactivity. Calling osc.start() on a suspended context throws. A ctx.state === 'suspended' && ctx.resume() guard before scheduling nodes would prevent the silent failure. Not blocking.

useStreakFreeze naming (lib/streaks-advanced.js:83): still present. DailySummaryCard.js:170 calls it inside an async handler — fine today, but the use prefix will cause ESLint react-hooks/rules-of-hooks to warn if the rule is ever enabled. Consider renaming to applyStreakFreeze at the call site too.

useModalScrollLock cleanup (lib/useModalScrollLock.js:9-12): still unconditional — closing one modal clears data-modal-open even if another is open. Low risk given current UI (modals don't stack), but fragile.


Positives

  • streak_freeze_redesign.sql is a clean migration pattern — same pattern just needs applying to the monthly constraint.
  • CommandPalette keyboard navigation (arrow keys, Enter, Escape) and scroll-into-view are implemented correctly.
  • sounds.js is SSR-safe and uses no external files — good dependency hygiene.
  • isCompletingRef guard across both handleComplete and handleMiss is correct and blocks concurrent calls regardless of which button is clicked.

Summary: 3 pre-existing bugs need fixes before merge (monthly toast, DB constraint migration, stored procedure case). The new CommandPalette ARIA wiring is a minor gap worth addressing.

MERGE:
- Resolve 8 conflicts (CreatePactModal, DailySummaryCard, streaks-advanced,
  package-lock, 2 test files) — keep branch versions with all improvements

REVIEW FIXES (from 7-agent parallel review):
- DashboardClient: remove unused useCountUp (~48 wasted re-renders),
  fix user object in deps (use user?.id), remove dead handleSignOut,
  add timezone to calculateStreak
- Deduplicate CreatePactModal: single instance in DashboardLayout,
  DashboardClient uses custom events to trigger it
- Consolidate sound settings: merge two localStorage keys (lockin-sound-enabled
  + lockin_sounds) into one (lockin_sounds)
- Heatmap bug: output loop now uses formatDateInTimezone (was formatLocalDate)
- Timezone DB write: cached in localStorage, only writes when changed

104 tests pass, build clean.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Addresses 3 bugs from Claude's GitHub PR review:

- PactCard: monthly recurrence toast now shows "month" instead of
  falling through to "weekday"
- SQL: add migration for monthly constraint on pacts table
- mark_overdue_pacts: monthly pacts now advance by 1 month instead
  of falling through to 1 day

Both SQL changes applied to production Supabase.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@vayungodara

Copy link
Copy Markdown
Owner Author

@claude review

@claude

claude Bot commented Mar 22, 2026

Copy link
Copy Markdown

Claude finished @vayungodara's task in 1m 25s —— View job


PR #27 Code Review

  • Gather context
  • Check if previous bugs were fixed (monthly toast, DB migration, stored proc)
  • Review open minor issues from last review
  • Post final review

All 3 red bugs from previous reviews are fixed ✅

Bug Status
Monthly toast shows "weekday" fallback (PactCard.js) ✅ Fixed — monthly arm added at lines 159 & 213
No SQL migration for 'monthly' constraint ✅ Fixed — supabase/add_monthly_recurrence.sql added
mark_overdue_pacts.sql cycles monthly pacts daily ✅ Fixed — when 'monthly' added to both CASE blocks (lines 44, 82)

Remaining minor issues (carried from previous reviews)

🟡 CommandPalette ARIA wiring incomplete (components/CommandPalette.js:154, 169)

The input has no aria-controls pointing to the list, and the <ul role="listbox"> has no matching id. Screen readers can't associate them:

// input needs:
aria-controls="cmd-palette-list"

// ul needs:
id="cmd-palette-list"

The "Create New Pact" command (line 89) also silently does nothing when onCreatePact is undefined — consider either filtering it out when the prop is absent, or always passing it at every usage site. Fix this →

🟡 useStreakFreeze naming (lib/streaks-advanced.js:83)

The alias export const useStreakFreeze = applyStreakFreeze still exists. The use prefix flags it as a hook under react-hooks/rules-of-hooks. Low risk now, but renaming the export and the call in DailySummaryCard.js:170 to applyStreakFreeze removes the ambiguity entirely.

🟡 useModalScrollLock cleanup is unconditional (lib/useModalScrollLock.js:9-12)

Cleanup always removes data-modal-open and overflow: hidden, even when isOpen was false when the effect ran. Harmless with the current UI (no stacked modals), but worth fixing if that ever changes.

🟡 sounds.js — no AudioContext.resume() guard

Browsers suspend AudioContext after inactivity. Calling osc.start() on a suspended context throws a silent error. Adding if (ctx.state === 'suspended') await ctx.resume() before scheduling nodes in each function would prevent this edge case.


Positives

  • Clean migration patternadd_monthly_recurrence.sql follows the same structure as streak_freeze_redesign.sql. Consistent.
  • mark_overdue_pacts.sql — inner loop correctly advances by full months until the deadline is in the future, same as other recurrence types.
  • CommandPalette keyboard nav — arrow keys, Enter, Escape, and scrollIntoView are all correct.
  • sounds.js SSR safetytypeof window !== 'undefined' guard in getAudioContext is correct.

Ready to merge. The 3 blocking bugs are all resolved. The remaining items are minor polish that can be addressed in a follow-up.

- CommandPalette: move setState out of effects into event handlers
  (setActiveIndex in onChange, setQuery/reset in Cmd+K handler)
- useCountUp: remove early-return setState, use safeTarget fallback
- DailySummaryCard: rename useStreakFreeze → applyStreakFreeze import
  (was triggering react-hooks/rules-of-hooks)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@vayungodara vayungodara merged commit a0dd406 into main Mar 22, 2026
4 of 5 checks passed
@vayungodara vayungodara deleted the test/streak-templates-tests branch March 22, 2026 12:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant