Skip to content

Claude/missionary calendar app ti6dr6 - #2545

Closed
dadefriez-sudo wants to merge 41 commits into
affaan-m:mainfrom
dadefriez-sudo:claude/missionary-calendar-app-ti6dr6
Closed

Claude/missionary calendar app ti6dr6#2545
dadefriez-sudo wants to merge 41 commits into
affaan-m:mainfrom
dadefriez-sudo:claude/missionary-calendar-app-ti6dr6

Conversation

@dadefriez-sudo

@dadefriez-sudo dadefriez-sudo commented Jul 21, 2026

Copy link
Copy Markdown

What Changed

Why This Change

Testing Done

  • Manual testing completed
  • Automated tests pass locally (node tests/run-all.js)
  • Edge cases considered and tested

Type of Change

  • fix: Bug fix
  • feat: New feature
  • refactor: Code refactoring
  • docs: Documentation
  • test: Tests
  • chore: Maintenance/tooling
  • ci: CI/CD changes

Security & Quality Checklist

  • No secrets or API keys committed (ghp_, sk-, AKIA, xoxb, xoxp patterns checked)
  • JSON files validate cleanly
  • Shell scripts pass shellcheck (if applicable)
  • Pre-commit hooks pass locally (if configured)
  • No sensitive data exposed in logs or output
  • Follows conventional commits format

If you changed dependencies or package.json (bin / files / deps)

  • Ran yarn install --mode=update-lockfile and committed the yarn.lock change. CI runs Yarn in hardened mode on public PRs and fails if the lockfile would be modified, so an out of date yarn.lock breaks the build even when nothing else is wrong.

If you added a skill, command, agent, hook, or CLI tool

  • Registered in package.json (bin and files), manifests/install-components.json, manifests/install-modules.json, and agent.yaml
  • Regenerated the catalog (npm run catalog:sync) and command registry (npm run command-registry:write)
  • Updated the docs tables it belongs in (README.md, COMMANDS-QUICK-REF.md, docs/COMMAND-AGENT-MAP.md)
  • If it ships a new script path, added it to the publish surface allowlist (tests/scripts/npm-publish-surface.test.js)
  • Cross-harness surfaces updated if applicable (for Codex, .agents/skills/<name>/ plus agents/openai.yaml; the Codex frontmatter validator allows only name, description, metadata, license, allowed-tools, so drop keys like version from that copy)
  • Full gauntlet passes locally (npm test)

Documentation

  • Updated relevant documentation
  • Added comments for complex logic
  • README updated (if needed)

claude added 6 commits July 21, 2026 03:47
A self-contained React PWA modeled on the Preach My Gospel planner's
three-part structure, adapted for personal use:

- Goals tab (replaces Key Indicators): weekly targets grouped by category
  with a week selector, progress ring, and +/- steppers; progress tracked
  per week.
- Planner tab: day timeline (tap to add) and week agenda; events link to a
  person, with location, notes, and done state.
- People tab: general contact tracker with user-defined status labels,
  search/filter, and a detail page with call/text/email/log-today actions.
- More tab: theme, custom status management, and JSON export/import/reset.

Data persists locally via localStorage; offline support via a service
worker. Built with Vite + React Router (hash routing).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DaB8NfLY8nsxGAFk7XbCtw
New fifth tab backed by Leaflet + OpenStreetMap (no API key needed):

- Drop pins by tapping the map; each pin has an emoji, text label,
  notes, and an optional linked person.
- Tap a pin for an info card with a "Directions" button that opens
  Google Maps navigation, plus a matching corner directions button.
- "My location" control re-centers the map via geolocation.
- Pins persist in localStorage alongside the rest of the data and are
  unlinked automatically when a linked person is deleted; export/import
  and clear/reset now include pins.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DaB8NfLY8nsxGAFk7XbCtw
People — reconnect nudges:
- Flag contacts not touched within a reminder window (last-contacted, or
  added date if never contacted). Adds a top "Time to reconnect" banner
  with one-tap Log, an amber filter chip, per-row tag/dot, and a chip on
  the contact detail page. Global threshold lives in More; each person can
  override it with their own cadence.

Planner — recurring events:
- Events gain a repeat rule (daily/weekly/biweekly/monthly) with an
  optional end date. Occurrences are expanded for the visible range rather
  than materialized. Per-occurrence done state (doneDates) and single-day
  deletion (skipDates) leave the rest of the series intact; a ↻ glyph marks
  repeating events. Contact "upcoming together" expands recurrences too.

Map — pins from a person:
- Contact detail gets a Places section listing linked pins and an
  "Add a place" action that opens the map in placement mode pre-linked to
  that person; tapping a listed place selects it on the map.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DaB8NfLY8nsxGAFk7XbCtw
Recurring events now support per-occurrence exceptions in addition to
series-level edits:

- The event editor shows a "This event / All events" scope toggle for
  recurring events. "This event" writes a per-occurrence override (title,
  time, contact, location, notes, or even a different day) keyed by the
  original recurrence date; "All events" updates the series master.
- Occurrences are expanded via expandEventOnDay(), which merges overrides,
  honors skips, and can relocate a moved occurrence onto its new day.
- Edited occurrences detach from the series (kept on series-wide edits) and
  are marked with a ✎ glyph; unedited ones keep the ↻ glyph.
- Contact "upcoming together" uses the same expansion so overrides show
  through there too.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DaB8NfLY8nsxGAFk7XbCtw
Adds a workflow that builds schedule-app/ and publishes dist/ to GitHub
Pages via the Actions artifact flow. Runs on pushes that touch the app
(on main or the feature branch) and on manual dispatch. Requires Pages to
be enabled once with Source set to "GitHub Actions".

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DaB8NfLY8nsxGAFk7XbCtw
Goals:
- Daily goals alongside weekly (Today / This week toggle); daily progress
  tracked per day, weekly per week. Per-goal reminder times. New goal is now
  a floating + button.

Planner:
- Event types with colors (managed in More) plus a per-event color override,
  applied to day blocks and week chips via a --ev custom property.
- Per-event reminders (lead time before start).
- Drag an event block on the day timeline to reschedule (pointer-based, with
  tap-vs-drag threshold; recurring drags write a single-occurrence override).
- Event editor opens as a taller sheet with a grip handle and swipe-to-dismiss.

People:
- Floating + on a person's detail page starts a calendar event pre-linked to
  them (passed to the Planner via router state).

More:
- Notifications toggle, event-type management, and a feedback / suggest-a-
  feature button (email or copy).

Branding & polish:
- Compass logo mark in every page header and a map badge.
- Best-effort local reminder scanner (goals + events) with permission handling;
  honest about the serverless "only while open" limitation.
- Reusable swipe-dismiss Modal, entrance animations, tab/press transitions,
  and prefers-reduced-motion support.

Store: goals migrate weeklyProgress -> progress with a period; events gain
typeId/color/reminder; new eventTypes collection; settings.notifications.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DaB8NfLY8nsxGAFk7XbCtw
@dadefriez-sudo
dadefriez-sudo requested a review from affaan-m as a code owner July 21, 2026 05:44
@ecc-tools

ecc-tools Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

ECC bundle files are already tracked in this repository. Skipping generation of another bundle PR.

@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Introduces Stewardly, a React/Vite personal planner PWA with goals, recurring events, contacts, map pins, reminders, local persistence, offline support, and GitHub Pages deployment.

Changes

Stewardly application

Layer / File(s) Summary
Application foundation
schedule-app/src/data/*, schedule-app/src/App.jsx, schedule-app/src/main.jsx, schedule-app/src/components/*, schedule-app/src/styles.css
Adds the React bootstrap, routing shell, reusable UI, seeded localStorage-backed state, date and recurrence utilities, notifications, haptics, and global styling.
Goals and contacts workflows
schedule-app/src/pages/GoalsPage.jsx, schedule-app/src/pages/ContactsPage.jsx, schedule-app/src/pages/ContactDetailPage.jsx, schedule-app/src/components/Avatar.jsx
Adds daily and weekly goals, progress editing, reconnect calculations, contact CRUD flows, avatars, contact details, and linked events and places.
Planner and recurring events
schedule-app/src/pages/PlannerPage.jsx, schedule-app/src/data/helpers.js, schedule-app/src/data/ics.js
Adds day, week, and month schedule views, recurring occurrence expansion, drag rescheduling, occurrence-level edits, reminders, event editing, and iCalendar import/export.
Map and pin management
schedule-app/src/pages/MapPage.jsx, schedule-app/src/components/MiniMapPicker.jsx
Adds Leaflet map rendering, pin placement and editing, contact links, geolocation, directions, and selected-pin controls.
Home, settings, and pricing workflows
schedule-app/src/pages/HomePage.jsx, schedule-app/src/pages/MorePage.jsx, schedule-app/src/pages/PricingPage.jsx
Adds dashboard reminders, tasks, notes, settings and data utilities, feedback actions, notification controls, and a local Pro-mode demo.
PWA and deployment delivery
schedule-app/public/*, schedule-app/index.html, schedule-app/package.json, schedule-app/vite.config.js, .github/workflows/deploy-schedule-app.yml, schedule-app/README.md
Adds install metadata, offline caching, build configuration, project documentation, global feature styling, and GitHub Pages deployment.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Browser
  participant main.jsx
  participant StoreProvider
  participant App
  participant PlannerPage
  participant EventEditor
  Browser->>main.jsx: load Stewardly
  main.jsx->>StoreProvider: initialize persisted data
  StoreProvider->>App: render routes and navigation
  App->>PlannerPage: render planner route
  PlannerPage->>EventEditor: open event editor
  EventEditor->>StoreProvider: persist event changes
Loading

Suggested reviewers: affaan-m

🚥 Pre-merge checks | ✅ 2 | ❌ 3

❌ Failed checks (1 warning, 2 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 43.48% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title is vague and includes an unrelated token, so it does not clearly summarize the PR. Use a concise title that names the main change, such as 'Add Stewardly PWA pages, store, and Pages deploy workflow'.
Description check ❓ Inconclusive The description is a generic template and does not meaningfully describe the actual changes. Replace the template with a brief summary of what changed, why, and any notable testing or caveats.
✅ Passed checks (2 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@greptile-apps

greptile-apps Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds a missionary calendar application with a supporting backend. The main changes are:

  • React and Vite frontend with planning, goals, contacts, maps, and reminders.
  • Offline and installable PWA support.
  • Clerk authentication and per-user cloud data storage.
  • Stripe checkout, customer portal, and subscription webhooks.
  • Prisma schema and PostgreSQL migrations.
  • GitHub Pages deployment workflow.

Confidence Score: 5/5

No additional blocking issue qualifies for a new comment in this review round.

  • The deployment concerns found here are already described by existing comments.
  • The reminder cases found here are already described by existing comments.
  • The backup migration gap found here is already described by an existing comment.
  • No separate failure remains that requires a different fix.

Important Files Changed

Filename Overview
.github/workflows/deploy-schedule-app.yml Adds the GitHub Pages build and deployment workflow.
backend/src/app.js Configures the Express application, middleware, webhooks, and API routes.
backend/src/routes/data.js Adds authenticated storage and retrieval of each user's application data.
backend/src/routes/billing.js Adds Stripe checkout and customer portal endpoints.
schedule-app/src/data/notifications.js Adds reminder scanning for goals and recurring events.
schedule-app/src/data/store.jsx Adds application state persistence, migration, backup import, and cloud synchronization.
schedule-app/src/App.jsx Connects the main application shell, navigation, state, and account features.
schedule-app/src/pages/PlannerPage.jsx Adds the planner interface and calendar interactions.

Reviews (29): Last reviewed commit: "Live 1:1 finger-following swipe between ..." | Re-trigger Greptile

timeout-minutes: 10
steps:
- name: Checkout
uses: actions/checkout@v4

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 security Mutable Actions Run With Write Access

actions/checkout@v4 and the other tagged actions below are mutable references executed with pages: write and id-token: write. If an upstream tag is moved or compromised, that code can publish through this workflow; pin every action to a full commit SHA as required by the repository supply-chain policy.

Rule Used: Flag unpinned third-party GitHub Actions, broad wr... (source)

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment on lines +9 to +11
branches:
- main
- claude/missionary-calendar-app-ti6dr6

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Feature Branch Publishes Production Pages

Every matching push to this feature branch runs the same deploy job, environment, and concurrency group as main. A branch run can therefore replace the production Pages site with unmerged code or finish after a newer main run and overwrite it.

Suggested change
branches:
- main
- claude/missionary-calendar-app-ti6dr6
branches:
- main

Comment thread schedule-app/src/data/notifications.js
Comment thread schedule-app/src/data/notifications.js
Comment thread schedule-app/src/data/store.jsx

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 18

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/deploy-schedule-app.yml:
- Line 35: Update all four third-party uses entries in the workflow, including
actions/checkout and the entries at the referenced locations, to immutable
commit-SHA references rather than mutable version tags. Use the current approved
action versions and their full 40-character commit SHAs, preserving each
action’s existing role and configuration.
- Around line 9-11: Update the push trigger branch list in the workflow
configuration to remove claude/missionary-calendar-app-ti6dr6 unless that branch
is intentionally authorized to deploy the live site. Preserve main and
workflow_dispatch as the production deployment triggers.
- Around line 17-22: Move pages: write and id-token: write from workflow-level
permissions into the deploy job’s permissions block, leaving build with only
contents: read. Keep the deploy job’s Pages and OIDC permissions unchanged in
scope and ensure the build job does not inherit either write permission.

In `@schedule-app/public/manifest.webmanifest`:
- Around line 11-18: Update the manifest icons array alongside the existing SVG
entry to include PNG fallbacks for icon-192.png at 192x192 and icon-512.png at
512x512, using image/png and the specified purposes. Ensure the referenced
raster assets are available while preserving the SVG declaration.

In `@schedule-app/public/sw.js`:
- Around line 27-38: Update the navigation fetch handler around the
`fetch(request).then` callback to cache `./index.html` only when the resolved
response has status 200, matching the existing cache-first guard. Continue
returning the response unchanged and falling back to
`caches.match('./index.html')` on network failure.

In `@schedule-app/src/components/Modal.jsx`:
- Around line 41-76: Update the Modal component’s open/close behavior to move
focus to the first focusable control inside the dialog when it mounts, keep
keyboard focus within the modal while it is open, and restore focus to the
element that was active before opening when it closes. Use the dialog sheet
referenced by the existing modal markup and preserve the current backdrop,
close-button, and pointer-drag behavior.

In `@schedule-app/src/data/helpers.js`:
- Around line 178-230: Extract the shared anchor, date-range, and
daily/weekly/biweekly/monthly frequency logic from occursOn and matchesRule into
a single rule-matching helper, then have both functions reuse it. Keep
occursOn’s skipDates filtering separate so matchesRule continues ignoring skips,
and remove the duplicated matchesRule implementation.

In `@schedule-app/src/data/store.jsx`:
- Around line 130-131: Extract the existing loadState merge, migration, and
defaulting logic into a shared normalizeState(parsed) helper, then call it from
both loadState and the IMPORT_DATA reducer case. Validate action.data’s
top-level imported shape with Zod before normalization, and reject or clearly
report malformed imports instead of merging them into state; preserve the
existing goal period/progress migration and eventTypes/settings defaults.

In `@schedule-app/src/pages/ContactsPage.jsx`:
- Line 94: Update the createdAt assignment in the contact creation flow to reuse
the imported todayISO helper instead of deriving the date with new
Date().toISOString().slice(0, 10), keeping stored dates consistent with
daysSince and local-date handling.

In `@schedule-app/src/pages/GoalsPage.jsx`:
- Around line 75-78: Gate reminder enabling on the resolved result of
requestNotificationPermission: in schedule-app/src/pages/GoalsPage.jsx lines
75-78, capture the returned permission, only call actions.setSettings({
notifications: true }) and retain the reminder when it is 'granted', otherwise
surface the existing “notifications blocked” hint; apply the same gating in
schedule-app/src/pages/PlannerPage.jsx lines 507-513 within setReminder before
enabling notifications.

In `@schedule-app/src/pages/MapPage.jsx`:
- Around line 136-152: Update savePin so editing.label and editing.notes are
normalized with an empty-string fallback before calling trim(), matching the
existing emoji handling. Preserve the current payload fields and save/update
behavior while ensuring pins loaded from older or imported data cannot cause
savePin to throw.
- Around line 110-112: Update the marker options in the L.marker creation flow
so pins remain keyboard-focusable by removing or enabling the keyboard: false
setting. Preserve the existing click handler and marker behavior while ensuring
keyboard-only users can reach and select pins.
- Around line 87-93: Store the timeout returned by setTimeout in the effect
setup, then clear it in the cleanup before calling map.remove() within the same
useEffect block. Preserve the existing 120ms invalidateSize behavior while
ensuring the callback cannot run after unmount.
- Around line 13-14: Replace the hand-rolled escapeHtml-based string
construction used by the marker icon creation flow with DOM-based element
construction for the L.divIcon content, assigning p.emoji and p.label through
textContent rather than interpolating them into innerHTML. Update the relevant
marker icon logic near the existing escapeHtml usage and remove the now-unneeded
helper while preserving the current icon content and behavior.

In `@schedule-app/src/pages/MorePage.jsx`:
- Around line 78-92: Validate the parsed payload in importData before calling
actions.importData, using the project’s existing schema-validation utility if
available, and reject malformed backups such as non-array events with the same
user-facing invalid-file alert. Add a FileReader onerror handler so read
failures also report the import error instead of silently returning.

In `@schedule-app/src/pages/PlannerPage.jsx`:
- Around line 166-177: Update the override construction in the
drag-to-reschedule flow to preserve the occurrence’s existing typeId, color, and
reminder values, matching the fields stored by save()’s single-occurrence path.
Keep the current rescheduled title, start, end, contactId, location, notes, and
conditional date behavior unchanged when calling actions.updateEvent.

In `@schedule-app/src/styles.css`:
- Around line 881-884: Add an empty line before the --ev custom-property
declaration in the .event-block rule, preserving the existing declaration order
and styling behavior.
- Around line 719-726: Rename the `@keyframes` identifier slideUp to slide-up in
the stylesheet, then update both animation references to use slide-up so the
animation remains functional and satisfies the kebab-case naming rule.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 0921ed25-1d2b-4017-8280-7ac539147649

📥 Commits

Reviewing files that changed from the base of the PR and between 5deee34 and 2c7443e.

⛔ Files ignored due to path filters (2)
  • schedule-app/package-lock.json is excluded by !**/package-lock.json
  • schedule-app/public/icon.svg is excluded by !**/*.svg
📒 Files selected for processing (23)
  • .github/workflows/deploy-schedule-app.yml
  • schedule-app/README.md
  • schedule-app/index.html
  • schedule-app/package.json
  • schedule-app/public/manifest.webmanifest
  • schedule-app/public/sw.js
  • schedule-app/src/App.jsx
  • schedule-app/src/components/Logo.jsx
  • schedule-app/src/components/Modal.jsx
  • schedule-app/src/components/TabBar.jsx
  • schedule-app/src/data/helpers.js
  • schedule-app/src/data/notifications.js
  • schedule-app/src/data/seed.js
  • schedule-app/src/data/store.jsx
  • schedule-app/src/main.jsx
  • schedule-app/src/pages/ContactDetailPage.jsx
  • schedule-app/src/pages/ContactsPage.jsx
  • schedule-app/src/pages/GoalsPage.jsx
  • schedule-app/src/pages/MapPage.jsx
  • schedule-app/src/pages/MorePage.jsx
  • schedule-app/src/pages/PlannerPage.jsx
  • schedule-app/src/styles.css
  • schedule-app/vite.config.js
📜 Review details
⏰ Context from checks skipped due to timeout. (1)
  • GitHub Check: Greptile Review
🧰 Additional context used
📓 Path-based instructions (17)
**/*.{js,ts,jsx,tsx,py,java,cs,go,rb,php,scala,kt}

📄 CodeRabbit inference engine (.cursor/rules/common-coding-style.md)

**/*.{js,ts,jsx,tsx,py,java,cs,go,rb,php,scala,kt}: Always create new objects, never mutate existing ones. Use immutable patterns to prevent hidden side effects and enable safe concurrency
Organize code into many small files (200-400 lines typical, 800 lines max) organized by feature/domain rather than by type
Always handle errors explicitly at every level and never silently swallow errors
Always validate all user input before processing at system boundaries
Use schema-based validation where available
Fail fast with clear error messages when validation fails
Never trust external data (API responses, user input, file content)
Ensure code is readable and well-named
Keep functions small (less than 50 lines)
Keep files focused (less than 800 lines)
Avoid deep nesting (more than 4 levels)
Do not use hardcoded values; use constants or configuration instead

Files:

  • schedule-app/vite.config.js
  • schedule-app/src/components/Modal.jsx
  • schedule-app/public/sw.js
  • schedule-app/src/main.jsx
  • schedule-app/src/components/Logo.jsx
  • schedule-app/src/App.jsx
  • schedule-app/src/components/TabBar.jsx
  • schedule-app/src/data/seed.js
  • schedule-app/src/data/store.jsx
  • schedule-app/src/pages/MorePage.jsx
  • schedule-app/src/data/notifications.js
  • schedule-app/src/pages/ContactDetailPage.jsx
  • schedule-app/src/pages/ContactsPage.jsx
  • schedule-app/src/pages/PlannerPage.jsx
  • schedule-app/src/pages/MapPage.jsx
  • schedule-app/src/pages/GoalsPage.jsx
  • schedule-app/src/data/helpers.js
**/*.{js,ts,jsx,tsx,py,java,cs,rb,go,php,swift,kt,rs,c,cpp,h,hpp}

📄 CodeRabbit inference engine (.cursor/rules/common-security.md)

No hardcoded secrets (API keys, passwords, tokens) - validate before any commit

Files:

  • schedule-app/vite.config.js
  • schedule-app/src/components/Modal.jsx
  • schedule-app/public/sw.js
  • schedule-app/src/main.jsx
  • schedule-app/src/components/Logo.jsx
  • schedule-app/src/App.jsx
  • schedule-app/src/components/TabBar.jsx
  • schedule-app/src/data/seed.js
  • schedule-app/src/data/store.jsx
  • schedule-app/src/pages/MorePage.jsx
  • schedule-app/src/data/notifications.js
  • schedule-app/src/pages/ContactDetailPage.jsx
  • schedule-app/src/pages/ContactsPage.jsx
  • schedule-app/src/pages/PlannerPage.jsx
  • schedule-app/src/pages/MapPage.jsx
  • schedule-app/src/pages/GoalsPage.jsx
  • schedule-app/src/data/helpers.js
**/*.{js,ts,jsx,tsx,py,java,cs,rb,go,php}

📄 CodeRabbit inference engine (.cursor/rules/common-security.md)

**/*.{js,ts,jsx,tsx,py,java,cs,rb,go,php}: All user inputs must be validated
Enable CSRF protection on all state-changing endpoints
Verify authentication and authorization for all protected endpoints
Implement rate limiting on all endpoints to prevent abuse
Ensure error messages do not leak sensitive data in responses

Files:

  • schedule-app/vite.config.js
  • schedule-app/src/components/Modal.jsx
  • schedule-app/public/sw.js
  • schedule-app/src/main.jsx
  • schedule-app/src/components/Logo.jsx
  • schedule-app/src/App.jsx
  • schedule-app/src/components/TabBar.jsx
  • schedule-app/src/data/seed.js
  • schedule-app/src/data/store.jsx
  • schedule-app/src/pages/MorePage.jsx
  • schedule-app/src/data/notifications.js
  • schedule-app/src/pages/ContactDetailPage.jsx
  • schedule-app/src/pages/ContactsPage.jsx
  • schedule-app/src/pages/PlannerPage.jsx
  • schedule-app/src/pages/MapPage.jsx
  • schedule-app/src/pages/GoalsPage.jsx
  • schedule-app/src/data/helpers.js
**/*.{js,ts,jsx,tsx,py,java,cs,rb,go,php,sql}

📄 CodeRabbit inference engine (.cursor/rules/common-security.md)

Use parameterized queries to prevent SQL injection

Files:

  • schedule-app/vite.config.js
  • schedule-app/src/components/Modal.jsx
  • schedule-app/public/sw.js
  • schedule-app/src/main.jsx
  • schedule-app/src/components/Logo.jsx
  • schedule-app/src/App.jsx
  • schedule-app/src/components/TabBar.jsx
  • schedule-app/src/data/seed.js
  • schedule-app/src/data/store.jsx
  • schedule-app/src/pages/MorePage.jsx
  • schedule-app/src/data/notifications.js
  • schedule-app/src/pages/ContactDetailPage.jsx
  • schedule-app/src/pages/ContactsPage.jsx
  • schedule-app/src/pages/PlannerPage.jsx
  • schedule-app/src/pages/MapPage.jsx
  • schedule-app/src/pages/GoalsPage.jsx
  • schedule-app/src/data/helpers.js
**/*.{js,ts,jsx,tsx,html,php,java,cs,rb,go}

📄 CodeRabbit inference engine (.cursor/rules/common-security.md)

Implement XSS prevention by sanitizing HTML output

Files:

  • schedule-app/vite.config.js
  • schedule-app/src/components/Modal.jsx
  • schedule-app/public/sw.js
  • schedule-app/index.html
  • schedule-app/src/main.jsx
  • schedule-app/src/components/Logo.jsx
  • schedule-app/src/App.jsx
  • schedule-app/src/components/TabBar.jsx
  • schedule-app/src/data/seed.js
  • schedule-app/src/data/store.jsx
  • schedule-app/src/pages/MorePage.jsx
  • schedule-app/src/data/notifications.js
  • schedule-app/src/pages/ContactDetailPage.jsx
  • schedule-app/src/pages/ContactsPage.jsx
  • schedule-app/src/pages/PlannerPage.jsx
  • schedule-app/src/pages/MapPage.jsx
  • schedule-app/src/pages/GoalsPage.jsx
  • schedule-app/src/data/helpers.js
**/*.{js,ts,jsx,tsx,py,java,cs,rb,go,php,swift,kt,rs,c,cpp,h,hpp,properties,yml,yaml,json,env,config}

📄 CodeRabbit inference engine (.cursor/rules/common-security.md)

NEVER hardcode secrets in source code - ALWAYS use environment variables or a secret manager

Files:

  • schedule-app/vite.config.js
  • schedule-app/src/components/Modal.jsx
  • schedule-app/public/sw.js
  • schedule-app/package.json
  • schedule-app/src/main.jsx
  • schedule-app/src/components/Logo.jsx
  • schedule-app/src/App.jsx
  • schedule-app/src/components/TabBar.jsx
  • schedule-app/src/data/seed.js
  • schedule-app/src/data/store.jsx
  • schedule-app/src/pages/MorePage.jsx
  • schedule-app/src/data/notifications.js
  • schedule-app/src/pages/ContactDetailPage.jsx
  • schedule-app/src/pages/ContactsPage.jsx
  • schedule-app/src/pages/PlannerPage.jsx
  • schedule-app/src/pages/MapPage.jsx
  • schedule-app/src/pages/GoalsPage.jsx
  • schedule-app/src/data/helpers.js
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursor/rules/typescript-coding-style.md)

**/*.{ts,tsx,js,jsx}: Use spread operator for immutable updates in TypeScript/JavaScript instead of direct mutation
Use async/await with try-catch for error handling in TypeScript/JavaScript
Use Zod for schema-based input validation in TypeScript/JavaScript
No console.log statements in production code; use proper logging libraries instead

**/*.{ts,tsx,js,jsx}: Auto-format JavaScript/TypeScript files using Prettier after edit
Warn about console.log statements in edited files
Check all modified files for console.log statements before session ends

**/*.{ts,tsx,js,jsx}: Use the ApiResponse interface pattern with generic type parameter: interface ApiResponse<T> { success: boolean; data?: T; error?: string; meta?: { total: number; page: number; limit: number; } }
Implement custom React hooks following the pattern: export a named function with use prefix, generic type parameters, and proper useEffect cleanup for side effects

**/*.{ts,tsx,js,jsx}: Never hardcode secrets; always use environment variables for sensitive credentials like API keys
Throw an error when required environment variables are not configured to fail fast and ensure security prerequisites are met

Use Playwright as the E2E testing framework for critical user flows in TypeScript/JavaScript

Files:

  • schedule-app/vite.config.js
  • schedule-app/src/components/Modal.jsx
  • schedule-app/public/sw.js
  • schedule-app/src/main.jsx
  • schedule-app/src/components/Logo.jsx
  • schedule-app/src/App.jsx
  • schedule-app/src/components/TabBar.jsx
  • schedule-app/src/data/seed.js
  • schedule-app/src/data/store.jsx
  • schedule-app/src/pages/MorePage.jsx
  • schedule-app/src/data/notifications.js
  • schedule-app/src/pages/ContactDetailPage.jsx
  • schedule-app/src/pages/ContactsPage.jsx
  • schedule-app/src/pages/PlannerPage.jsx
  • schedule-app/src/pages/MapPage.jsx
  • schedule-app/src/pages/GoalsPage.jsx
  • schedule-app/src/data/helpers.js
**/*.{js,ts,jsx,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{js,ts,jsx,tsx}: Always create new objects and never mutate in place; return new copies instead
Keep files between 200–400 lines typical, with a maximum of 800 lines
Extract helpers when a file exceeds 200 lines
Handle errors explicitly at every level; never swallow errors silently
Validate all user input before processing; use schema-based validation where available
Never trust external data (API responses, file content, query params); always validate
All user inputs must be validated and sanitized
Error messages must be scrubbed of sensitive internals
Use readable, well-named identifiers in all code
Keep functions under 50 lines
Keep files under 800 lines
Avoid nesting deeper than 4 levels
Implement comprehensive error handling in all code
Do not hardcode values; use constants or environment configuration instead
Do not use in-place mutation; always return new objects or state

Files:

  • schedule-app/vite.config.js
  • schedule-app/src/components/Modal.jsx
  • schedule-app/public/sw.js
  • schedule-app/src/main.jsx
  • schedule-app/src/components/Logo.jsx
  • schedule-app/src/App.jsx
  • schedule-app/src/components/TabBar.jsx
  • schedule-app/src/data/seed.js
  • schedule-app/src/data/store.jsx
  • schedule-app/src/pages/MorePage.jsx
  • schedule-app/src/data/notifications.js
  • schedule-app/src/pages/ContactDetailPage.jsx
  • schedule-app/src/pages/ContactsPage.jsx
  • schedule-app/src/pages/PlannerPage.jsx
  • schedule-app/src/pages/MapPage.jsx
  • schedule-app/src/pages/GoalsPage.jsx
  • schedule-app/src/data/helpers.js
**/*.{js,ts,jsx,tsx,json,env*}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Do not hardcode secrets, API keys, passwords, or tokens

Files:

  • schedule-app/vite.config.js
  • schedule-app/src/components/Modal.jsx
  • schedule-app/public/sw.js
  • schedule-app/package.json
  • schedule-app/src/main.jsx
  • schedule-app/src/components/Logo.jsx
  • schedule-app/src/App.jsx
  • schedule-app/src/components/TabBar.jsx
  • schedule-app/src/data/seed.js
  • schedule-app/src/data/store.jsx
  • schedule-app/src/pages/MorePage.jsx
  • schedule-app/src/data/notifications.js
  • schedule-app/src/pages/ContactDetailPage.jsx
  • schedule-app/src/pages/ContactsPage.jsx
  • schedule-app/src/pages/PlannerPage.jsx
  • schedule-app/src/pages/MapPage.jsx
  • schedule-app/src/pages/GoalsPage.jsx
  • schedule-app/src/data/helpers.js
**/*.{js,ts}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{js,ts}: Use parameterized queries for all database writes (no string interpolation)
Auth/authz must be checked server-side for every sensitive path
Rate limiting must be applied to all public endpoints

Files:

  • schedule-app/vite.config.js
  • schedule-app/public/sw.js
  • schedule-app/src/data/seed.js
  • schedule-app/src/data/notifications.js
  • schedule-app/src/data/helpers.js
**/*.{jsx,tsx,js,ts}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

HTML output must be sanitized where applicable

Files:

  • schedule-app/vite.config.js
  • schedule-app/src/components/Modal.jsx
  • schedule-app/public/sw.js
  • schedule-app/src/main.jsx
  • schedule-app/src/components/Logo.jsx
  • schedule-app/src/App.jsx
  • schedule-app/src/components/TabBar.jsx
  • schedule-app/src/data/seed.js
  • schedule-app/src/data/store.jsx
  • schedule-app/src/pages/MorePage.jsx
  • schedule-app/src/data/notifications.js
  • schedule-app/src/pages/ContactDetailPage.jsx
  • schedule-app/src/pages/ContactsPage.jsx
  • schedule-app/src/pages/PlannerPage.jsx
  • schedule-app/src/pages/MapPage.jsx
  • schedule-app/src/pages/GoalsPage.jsx
  • schedule-app/src/data/helpers.js
**/*.{js,ts,env*}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Required environment variables must be validated at startup

Files:

  • schedule-app/vite.config.js
  • schedule-app/public/sw.js
  • schedule-app/src/data/seed.js
  • schedule-app/src/data/notifications.js
  • schedule-app/src/data/helpers.js
**/{src,app}/**/*.{jsx,tsx,vue,html}

📄 CodeRabbit inference engine (.cursor/rules/common-coding-style.md)

Provide user-friendly error messages in UI-facing code

Files:

  • schedule-app/src/components/Modal.jsx
  • schedule-app/src/main.jsx
  • schedule-app/src/components/Logo.jsx
  • schedule-app/src/App.jsx
  • schedule-app/src/components/TabBar.jsx
  • schedule-app/src/data/store.jsx
  • schedule-app/src/pages/MorePage.jsx
  • schedule-app/src/pages/ContactDetailPage.jsx
  • schedule-app/src/pages/ContactsPage.jsx
  • schedule-app/src/pages/PlannerPage.jsx
  • schedule-app/src/pages/MapPage.jsx
  • schedule-app/src/pages/GoalsPage.jsx
{**/*.tsx,**/*.jsx,**/components/**}

📄 CodeRabbit inference engine (CLAUDE.md)

When working on React files (*.tsx, *.jsx, components/**), use react-patterns and react-testing skills. Invoke /react-review, /react-build, /react-test commands for React-specific work.

Files:

  • schedule-app/src/components/Modal.jsx
  • schedule-app/src/main.jsx
  • schedule-app/src/components/Logo.jsx
  • schedule-app/src/App.jsx
  • schedule-app/src/components/TabBar.jsx
  • schedule-app/src/data/store.jsx
  • schedule-app/src/pages/MorePage.jsx
  • schedule-app/src/pages/ContactDetailPage.jsx
  • schedule-app/src/pages/ContactsPage.jsx
  • schedule-app/src/pages/PlannerPage.jsx
  • schedule-app/src/pages/MapPage.jsx
  • schedule-app/src/pages/GoalsPage.jsx
.github/workflows/*.yml

📄 CodeRabbit inference engine (CLAUDE.md)

When working on GitHub workflow files, use the /ci-workflow skill.

Files:

  • .github/workflows/deploy-schedule-app.yml
.github/workflows/**

⚙️ CodeRabbit configuration file

.github/workflows/**: Treat workflow changes as security-sensitive. Flag unpinned third-party actions, broad write permissions, persisted checkout credentials in write-token jobs, pull_request_target misuse, and untrusted GitHub context interpolated into shell commands.

Files:

  • .github/workflows/deploy-schedule-app.yml
**/{server,backend,api,src}/**/*.{ts,tsx,js,py,java,cs,go,rb,php,scala,kt}

📄 CodeRabbit inference engine (.cursor/rules/common-coding-style.md)

Log detailed error context on the server side

Files:

  • schedule-app/src/data/seed.js
  • schedule-app/src/data/notifications.js
  • schedule-app/src/data/helpers.js
🪛 ast-grep (0.44.1)
schedule-app/src/components/Modal.jsx

[warning] 31-31: Avoid using the initial state variable in setState
Context: setDragY(dy)
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(setstate-same-var)

schedule-app/src/App.jsx

[warning] 24-24: Avoid using the initial state variable in setState
Context: setInterval(scan, 30000)
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(setstate-same-var)

schedule-app/src/components/TabBar.jsx

[warning] 19-19: A list component should have a key to prevent re-rendering
Context:
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(list-component-needs-key)


[warning] 20-20: A list component should have a key to prevent re-rendering
Context: {label}
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(list-component-needs-key)

schedule-app/src/pages/MorePage.jsx

[warning] 195-201: A list component should have a key to prevent re-rendering
Context: <button className="status-item" onClick={() => setEditingStatus({ ...s })}>
<span className="swatch" style={{ background: s.color }} />
{s.label}

{state.contacts.filter((c) => c.statusId === s.id).length}


Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(list-component-needs-key)


[warning] 196-196: A list component should have a key to prevent re-rendering
Context: <span className="swatch" style={{ background: s.color }} />
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(list-component-needs-key)


[warning] 197-197: A list component should have a key to prevent re-rendering
Context: {s.label}
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(list-component-needs-key)


[warning] 198-200: A list component should have a key to prevent re-rendering
Context:
{state.contacts.filter((c) => c.statusId === s.id).length}

Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(list-component-needs-key)


[warning] 222-228: A list component should have a key to prevent re-rendering
Context: <button className="status-item" onClick={() => setEditingType({ ...t })}>
<span className="swatch" style={{ background: t.color }} />
{t.label}

{state.events.filter((e) => e.typeId === t.id).length}


Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(list-component-needs-key)


[warning] 223-223: A list component should have a key to prevent re-rendering
Context: <span className="swatch" style={{ background: t.color }} />
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(list-component-needs-key)


[warning] 224-224: A list component should have a key to prevent re-rendering
Context: {t.label}
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(list-component-needs-key)


[warning] 225-227: A list component should have a key to prevent re-rendering
Context:
{state.events.filter((e) => e.typeId === t.id).length}

Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(list-component-needs-key)

schedule-app/src/pages/ContactDetailPage.jsx

[warning] 187-194: A list component should have a key to prevent re-rendering
Context: <button
className="place-row"
onClick={() => navigate('/map', { state: { selectPin: p.id } })}
>
{p.emoji || '📍'}
{p.label || 'Dropped pin'}
View ›

Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(list-component-needs-key)


[warning] 191-191: A list component should have a key to prevent re-rendering
Context: {p.emoji || '📍'}
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(list-component-needs-key)


[warning] 192-192: A list component should have a key to prevent re-rendering
Context: {p.label || 'Dropped pin'}
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(list-component-needs-key)


[warning] 193-193: A list component should have a key to prevent re-rendering
Context: View ›
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(list-component-needs-key)


[warning] 209-212: A list component should have a key to prevent re-rendering
Context:
{formatShortDate(e.occDate)}
{formatTime(e.start)}

Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(list-component-needs-key)


[warning] 211-211: A list component should have a key to prevent re-rendering
Context: {formatTime(e.start)}
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(list-component-needs-key)


[warning] 213-218: A list component should have a key to prevent re-rendering
Context:
{e.title}
{e.repeat && e.repeat !== 'none' && (
{e.isException ? '✎' : '↻'}
)}

Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(list-component-needs-key)


[warning] 216-216: A list component should have a key to prevent re-rendering
Context: {e.isException ? '✎' : '↻'}
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(list-component-needs-key)

schedule-app/src/pages/ContactsPage.jsx

[warning] 149-155: A list component should have a key to prevent re-rendering
Context: <button className="reconnect-open" onClick={() => navigate(/contacts/${c.id})}>
<span className="avatar avatar--sm" style={{ background: st?.color || 'var(--muted)' }}>
{initials(c.name)}

{c.name.split(' ')[0]}
{daysAgoLabel(c.lastContacted)}

Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(list-component-needs-key)


[warning] 150-152: A list component should have a key to prevent re-rendering
Context: <span className="avatar avatar--sm" style={{ background: st?.color || 'var(--muted)' }}>
{initials(c.name)}

Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(list-component-needs-key)


[warning] 153-153: A list component should have a key to prevent re-rendering
Context: {c.name.split(' ')[0]}
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(list-component-needs-key)


[warning] 154-154: A list component should have a key to prevent re-rendering
Context: {daysAgoLabel(c.lastContacted)}
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(list-component-needs-key)


[warning] 156-161: A list component should have a key to prevent re-rendering
Context: <button
className="btn btn-ghost btn-sm reconnect-log"
onClick={() => actions.updateContact({ ...c, lastContacted: todayISO() })}
>
✓ Log

Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(list-component-needs-key)


[warning] 190-207: A list component should have a key to prevent re-rendering
Context: <button className="contact-row" onClick={() => navigate(/contacts/${c.id})}>
<span className="avatar" style={{ background: st?.color || 'var(--muted)' }}>
{initials(c.name)}
{over && }



{c.name}
{over && Reconnect}


{st && <span className="dot-badge" style={{ color: st.color }}>{st.label}
}
{st && ' · '}
Last: {daysAgoLabel(c.lastContacted)}




Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(list-component-needs-key)


[warning] 191-194: A list component should have a key to prevent re-rendering
Context: <span className="avatar" style={{ background: st?.color || 'var(--muted)' }}>
{initials(c.name)}
{over && }

Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(list-component-needs-key)


[warning] 193-193: A list component should have a key to prevent re-rendering
Context:
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(list-component-needs-key)


[warning] 195-205: A list component should have a key to prevent re-rendering
Context:

{c.name}
{over && Reconnect}


{st && <span className="dot-badge" style={{ color: st.color }}>{st.label}
}
{st && ' · '}
Last: {daysAgoLabel(c.lastContacted)}


Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(list-component-needs-key)


[warning] 196-199: A list component should have a key to prevent re-rendering
Context:
{c.name}
{over && Reconnect}

Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(list-component-needs-key)


[warning] 198-198: A list component should have a key to prevent re-rendering
Context: Reconnect
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(list-component-needs-key)


[warning] 200-204: A list component should have a key to prevent re-rendering
Context:
{st && <span className="dot-badge" style={{ color: st.color }}>{st.label}
}
{st && ' · '}
Last: {daysAgoLabel(c.lastContacted)}

Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(list-component-needs-key)


[warning] 201-201: A list component should have a key to prevent re-rendering
Context: <span className="dot-badge" style={{ color: st.color }}>{st.label}
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(list-component-needs-key)


[warning] 206-206: A list component should have a key to prevent re-rendering
Context:
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(list-component-needs-key)

schedule-app/src/pages/PlannerPage.jsx

[warning] 226-226: Avoid using the initial state variable in setState
Context: setCursor(iso)
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(setstate-same-var)


[warning] 292-292: Avoid using the initial state variable in setState
Context: setDragDy(dy)
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(setstate-same-var)


[warning] 319-319: A list component should have a key to prevent re-rendering
Context: {formatTime(${String(h).padStart(2, '0')}:00)}
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(list-component-needs-key)


[warning] 320-320: A list component should have a key to prevent re-rendering
Context:


Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(list-component-needs-key)


[warning] 353-359: A list component should have a key to prevent re-rendering
Context:

{isDragging ? formatTime(minutesToTime(clampStart(ev, dragDy))) : formatTime(ev.start)}
{' '}
{ev.title || 'Untitled'}
{recurring && {ev.isException ? '✎' : '↻'}}

Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(list-component-needs-key)


[warning] 354-356: A list component should have a key to prevent re-rendering
Context:
{isDragging ? formatTime(minutesToTime(clampStart(ev, dragDy))) : formatTime(ev.start)}

Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(list-component-needs-key)


[warning] 358-358: A list component should have a key to prevent re-rendering
Context: {ev.isException ? '✎' : '↻'}
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(list-component-needs-key)


[warning] 361-369: A list component should have a key to prevent re-rendering
Context: <>

{isDragging ? formatTime(minutesToTime(clampStart(ev, dragDy))) : formatTime(ev.start)}
{recurring && {ev.isException ? '✎' : '↻'}}
{ev.reminder > 0 && 🔔}

{ev.title || 'Untitled'}
{who && {who}}
</>
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(list-component-needs-key)


[warning] 362-366: A list component should have a key to prevent re-rendering
Context:
{isDragging ? formatTime(minutesToTime(clampStart(ev, dragDy))) : formatTime(ev.start)}
{recurring && {ev.isException ? '✎' : '↻'}}
{ev.reminder > 0 && 🔔}

Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(list-component-needs-key)


[warning] 364-364: A list component should have a key to prevent re-rendering
Context: {ev.isException ? '✎' : '↻'}
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(list-component-needs-key)


[warning] 365-365: A list component should have a key to prevent re-rendering
Context: 🔔
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(list-component-needs-key)


[warning] 367-367: A list component should have a key to prevent re-rendering
Context: {ev.title || 'Untitled'}
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(list-component-needs-key)


[warning] 368-368: A list component should have a key to prevent re-rendering
Context: {who}
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(list-component-needs-key)


[warning] 401-406: A list component should have a key to prevent re-rendering
Context:


<button className="agenda-date-btn" onClick={() => onOpenDay(iso)}>
{weekdayShort(d)}
{d.getDate()}


Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(list-component-needs-key)


[warning] 402-405: A list component should have a key to prevent re-rendering
Context: <button className="agenda-date-btn" onClick={() => onOpenDay(iso)}>
{weekdayShort(d)}
{d.getDate()}

Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(list-component-needs-key)


[warning] 403-403: A list component should have a key to prevent re-rendering
Context: {weekdayShort(d)}
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(list-component-needs-key)


[warning] 404-404: A list component should have a key to prevent re-rendering
Context: {d.getDate()}
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(list-component-needs-key)


[warning] 409-411: A list component should have a key to prevent re-rendering
Context: <button className="agenda-empty" onClick={() => onAdd(iso)}>
+ Add

Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(list-component-needs-key)


[warning] 422-422: A list component should have a key to prevent re-rendering
Context: {formatTime(ev.start)}
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(list-component-needs-key)


[warning] 423-423: A list component should have a key to prevent re-rendering
Context: {ev.title || 'Untitled'}
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(list-component-needs-key)


[warning] 424-424: A list component should have a key to prevent re-rendering
Context: 🔔
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(list-component-needs-key)


[warning] 425-425: A list component should have a key to prevent re-rendering
Context: {ev.isException ? '✎' : '↻'}
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(list-component-needs-key)


[error] 446-467: React's useState should not be directly called
Context: setDraft({
_key: key,
id: editing.id,
scope: recurringMaster ? 'this' : 'all',
title: editing.title,
start: editing.start,
end: editing.end,
contactId: editing.contactId || '',
location: editing.location || '',
notes: editing.notes || '',
date: recurringMaster ? editing.occDate || editing.date : editing.date,
done: !!editing.done,
repeat: editing.repeat || 'none',
repeatUntil: editing.repeatUntil || '',
typeId: editing.typeId || '',
color: editing.color || '',
reminder: Number(editing.reminder) || 0,
recDate: editing.recDate || editing.date,
occDate: editing.occDate || editing.date,
masterDate: editing.date,
base: editing.base || null,
})
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(usestate-direct-usage)


[error] 469-469: React's useState should not be directly called
Context: setDraft(null)
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(usestate-direct-usage)

schedule-app/src/pages/MapPage.jsx

[warning] 13-13: Avoid hand-rolled HTML escaping (replacing characters with HTML entities); use a vetted encoder/sanitizer such as DOMPurify or sanitize-html.
Context: s.replace(/[&<>"']/g, (c) => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": '&#39;' }[c]))
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting').

(manual-sanitization)

schedule-app/src/pages/GoalsPage.jsx

[warning] 159-159: A list component should have a key to prevent re-rendering
Context:

{category}


Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(list-component-needs-key)


[warning] 166-178: A list component should have a key to prevent re-rendering
Context: <button className="goal-info" onClick={() => openEdit(g)}>


{g.title}
{g.reminder && <span className="bell-badge" title={Reminder at ${g.reminder.time}}>🔔}
{done && }


<div className="progress-fill" style={{ width: ${pct}% }} />


{value} / {g.target} {g.unit}


Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(list-component-needs-key)


[warning] 167-171: A list component should have a key to prevent re-rendering
Context:


{g.title}
{g.reminder && <span className="bell-badge" title={Reminder at ${g.reminder.time}}>🔔}
{done && }

Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(list-component-needs-key)


[warning] 168-168: A list component should have a key to prevent re-rendering
Context: {g.title}
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(list-component-needs-key)


[warning] 169-169: A list component should have a key to prevent re-rendering
Context: <span className="bell-badge" title={Reminder at ${g.reminder.time}}>🔔
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(list-component-needs-key)


[warning] 170-170: A list component should have a key to prevent re-rendering
Context:
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(list-component-needs-key)


[warning] 172-174: A list component should have a key to prevent re-rendering
Context:


<div className="progress-fill" style={{ width: ${pct}% }} />

Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(list-component-needs-key)


[warning] 173-173: A list component should have a key to prevent re-rendering
Context: <div className="progress-fill" style={{ width: ${pct}% }} />
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(list-component-needs-key)


[warning] 175-177: A list component should have a key to prevent re-rendering
Context:
{value} / {g.target} {g.unit}

Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(list-component-needs-key)


[warning] 179-195: A list component should have a key to prevent re-rendering
Context:


<button
className="step-btn"
onClick={() => actions.setGoalProgress(g.id, key, value - 1)}
disabled={value <= 0}
aria-label={Decrease ${g.title}}
>


<button
className="step-btn step-btn--plus"
onClick={() => actions.setGoalProgress(g.id, key, value + 1)}
aria-label={Increase ${g.title}}
>
+


Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(list-component-needs-key)


[warning] 180-187: A list component should have a key to prevent re-rendering
Context: <button
className="step-btn"
onClick={() => actions.setGoalProgress(g.id, key, value - 1)}
disabled={value <= 0}
aria-label={Decrease ${g.title}}
>


Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(list-component-needs-key)


[warning] 188-194: A list component should have a key to prevent re-rendering
Context: <button
className="step-btn step-btn--plus"
onClick={() => actions.setGoalProgress(g.id, key, value + 1)}
aria-label={Increase ${g.title}}
>
+

Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(list-component-needs-key)

🪛 markdownlint-cli2 (0.23.0)
schedule-app/README.md

[warning] 56-56: Blank line inside blockquote

(MD028, no-blanks-blockquote)

🪛 Stylelint (17.14.0)
schedule-app/src/styles.css

[error] 884-884: Expected empty line before declaration (declaration-empty-line-before)

(declaration-empty-line-before)


[error] 719-719: Expected keyframe name "slideUp" to be kebab-case (keyframes-name-pattern)

(keyframes-name-pattern)

🪛 zizmor (1.26.1)
.github/workflows/deploy-schedule-app.yml

[error] 20-20: overly broad permissions (excessive-permissions): pages: write is overly broad at the workflow level

(excessive-permissions)


[error] 21-21: overly broad permissions (excessive-permissions): id-token: write is overly broad at the workflow level

(excessive-permissions)


[error] 35-35: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)


[error] 40-40: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)


[error] 55-55: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)


[error] 70-70: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)


[warning] 20-20: permissions without explanatory comments (undocumented-permissions): needs an explanatory comment

(undocumented-permissions)

🔇 Additional comments (23)
schedule-app/src/pages/MapPage.jsx (1)

55-64: 🎯 Functional Correctness

Navigation-intent effect only runs on mount.

[] deps mean location.state (placeForContact/selectPin) is only consumed once. If MapPage stays mounted while another Link/navigate() targets /map again with a new state (e.g. a second "view on map" tap from a contact while the Map tab is already active), the intent is silently dropped since the effect won't re-run.

Whether MapPage remounts on every navigation to /map depends on the router setup in schedule-app/src/App.jsx, which isn't in this batch. Please confirm the route/tab-bar implementation always remounts this component, otherwise this effect needs to depend on location.state/location.key (using a ref to avoid re-triggering setPlacing/setSelectedId loops).

schedule-app/src/pages/ContactDetailPage.jsx (1)

1-370: LGTM!

schedule-app/src/pages/ContactsPage.jsx (1)

20-90: LGTM!

schedule-app/src/pages/GoalsPage.jsx (1)

32-70: LGTM!

schedule-app/src/pages/PlannerPage.jsx (2)

446-470: 📐 Maintainability & Code Quality

Confirm the render-phase setDraft calls won't fail CI lint.

Calling the state setter during render (guarded by draft._key !== key) is a React-supported "adjust state during render" pattern and won't loop here, so runtime is fine. However, ast-grep flags it as an error (usestate-direct-usage), and some eslint configs treat this as a build-breaker. Verify the project's lint gate accepts it; otherwise reset via a key prop on the modal instead.

Source: Linters/SAST tools


55-254: LGTM!

schedule-app/index.html (1)

1-22: LGTM!

schedule-app/src/components/Modal.jsx (1)

1-39: LGTM!

schedule-app/src/data/helpers.js (2)

1-176: LGTM!


235-285: LGTM!

schedule-app/src/data/notifications.js (1)

1-109: LGTM!

schedule-app/src/data/seed.js (1)

1-242: LGTM!

schedule-app/src/data/store.jsx (1)

9-37: LGTM!

Also applies to: 41-149, 153-219

schedule-app/package.json (1)

1-22: LGTM!

schedule-app/vite.config.js (1)

1-13: LGTM!

schedule-app/src/main.jsx (1)

8-25: LGTM!

schedule-app/src/App.jsx (2)

20-32: LGTM!

(The ast-grep setstate-same-var hint on Line 24 is a false positive — this is a stateRef/empty-deps pattern to keep a single interval reading the latest state, not a setState call.)


35-50: LGTM!

schedule-app/src/components/TabBar.jsx (1)

11-26: LGTM!

(The ast-grep list-component-needs-key hints on Lines 19–20 are false positives — the key is correctly on the mapped NavLink; <Icon /> and <span> are not list siblings.)

schedule-app/src/components/Logo.jsx (1)

8-13: 📐 Maintainability & Code Quality

Duplicate SVG id is not a current issue. The app renders one route at a time, and the only Logo/Brand usages are page-local, so id="logoBg" does not collide in the current UI.

			> Likely an incorrect or invalid review comment.
schedule-app/src/pages/MorePage.jsx (1)

1-439: LGTM! Remaining code (settings, statuses/event types editors, backup export, confirm modals) is consistent with the store contract and uses immutable update patterns throughout.

schedule-app/public/sw.js (1)

1-19: LGTM! Install/activate lifecycle and cache versioning are standard and correct.

schedule-app/README.md (1)

1-76: LGTM!

Comment on lines +9 to +11
branches:
- main
- claude/missionary-calendar-app-ti6dr6

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the workflow file around the trigger and deploy steps.
git ls-files .github/workflows/deploy-schedule-app.yml
wc -l .github/workflows/deploy-schedule-app.yml
cat -n .github/workflows/deploy-schedule-app.yml | sed -n '1,220p'

Repository: affaan-m/ECC

Length of output: 2427


Drop the extra push branch unless it is meant to publish to Pages.
claude/missionary-calendar-app-ti6dr6 is wired into the push trigger, so every push there will deploy the live site. Keep production deploys on main/workflow_dispatch only if this is just a dev branch.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/deploy-schedule-app.yml around lines 9 - 11, Update the
push trigger branch list in the workflow configuration to remove
claude/missionary-calendar-app-ti6dr6 unless that branch is intentionally
authorized to deploy the live site. Preserve main and workflow_dispatch as the
production deployment triggers.

Comment on lines +17 to +22
# Least-privilege token, plus the scopes the Pages deploy needs.
permissions:
contents: read
pages: write
id-token: write

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Scope pages: write/id-token: write to the deploy job only.

The build job only checks out, runs npm ci/npm run build, and uploads an artifact — it never calls deploy-pages and doesn't need Pages/OIDC write access. Granting these at the workflow level extends unnecessary write scope to every job. Official guidance scopes these to the deploying job: "# Grant GITHUB_TOKEN the permissions required to make a Pages deployment permissions: pages: write # to deploy to Pages id-token: write # to verify the deployment originates from an appropriate source"

As per path instructions, ".github/workflows/**" changes should "Flag unpinned third-party actions, broad write permissions, persisted checkout credentials in write-token jobs..."

🔒 Proposed fix
 permissions:
   contents: read
-  pages: write
-  id-token: write
 
 ...
 
 jobs:
   build:
     ...
   deploy:
     name: Deploy
     needs: build
     runs-on: ubuntu-latest
     timeout-minutes: 10
+    permissions:
+      pages: write
+      id-token: write
     environment:
       name: github-pages
       url: ${{ steps.deployment.outputs.page_url }}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# Least-privilege token, plus the scopes the Pages deploy needs.
permissions:
contents: read
pages: write
id-token: write
# Least-privilege token, plus the scopes the Pages deploy needs.
permissions:
contents: read
jobs:
deploy:
permissions:
pages: write
id-token: write
🧰 Tools
🪛 zizmor (1.26.1)

[error] 20-20: overly broad permissions (excessive-permissions): pages: write is overly broad at the workflow level

(excessive-permissions)


[error] 21-21: overly broad permissions (excessive-permissions): id-token: write is overly broad at the workflow level

(excessive-permissions)


[warning] 20-20: permissions without explanatory comments (undocumented-permissions): needs an explanatory comment

(undocumented-permissions)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/deploy-schedule-app.yml around lines 17 - 22, Move pages:
write and id-token: write from workflow-level permissions into the deploy job’s
permissions block, leaving build with only contents: read. Keep the deploy job’s
Pages and OIDC permissions unchanged in scope and ensure the build job does not
inherit either write permission.

Sources: Path instructions, Linters/SAST tools

timeout-minutes: 10
steps:
- name: Checkout
uses: actions/checkout@v4

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Pin third-party/reusable action references to a commit SHA.

All four uses: entries reference mutable major-version tags rather than immutable SHAs, flagged by zizmor's blanket unpinned-uses policy. actions/checkout@v4 is also behind the action's current major (v7, per search). Given this workflow carries Pages/OIDC write permissions in the deploy job, pin each action to a commit SHA to reduce supply-chain exposure.

As per path instructions, "Flag unpinned third-party actions" for .github/workflows/** changes.

Also applies to: 40-40, 55-55, 70-70

🧰 Tools
🪛 zizmor (1.26.1)

[error] 35-35: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/deploy-schedule-app.yml at line 35, Update all four
third-party uses entries in the workflow, including actions/checkout and the
entries at the referenced locations, to immutable commit-SHA references rather
than mutable version tags. Use the current approved action versions and their
full 40-character commit SHAs, preserving each action’s existing role and
configuration.

Sources: Path instructions, Linters/SAST tools

Comment thread schedule-app/public/manifest.webmanifest
Comment thread schedule-app/public/sw.js
Comment on lines +27 to +38
if (request.mode === 'navigate') {
event.respondWith(
fetch(request)
.then((res) => {
const copy = res.clone();
caches.open(CACHE).then((cache) => cache.put('./index.html', copy));
return res;
})
.catch(() => caches.match('./index.html'))
);
return;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Guard against caching error responses as the offline shell.

fetch(request).then((res) => { ... cache.put('./index.html', copy) ...}) caches any resolved response, including HTTP error statuses (fetch only rejects on network failure). A transient 5xx during a deploy would get cached as the offline fallback page. The cache-first branch below already checks res.status === 200 — apply the same guard here.

🔧 Proposed fix
     event.respondWith(
       fetch(request)
         .then((res) => {
-          const copy = res.clone();
-          caches.open(CACHE).then((cache) => cache.put('./index.html', copy));
+          if (res.ok) {
+            const copy = res.clone();
+            caches.open(CACHE).then((cache) => cache.put('./index.html', copy));
+          }
           return res;
         })
         .catch(() => caches.match('./index.html'))
     );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (request.mode === 'navigate') {
event.respondWith(
fetch(request)
.then((res) => {
const copy = res.clone();
caches.open(CACHE).then((cache) => cache.put('./index.html', copy));
return res;
})
.catch(() => caches.match('./index.html'))
);
return;
}
if (request.mode === 'navigate') {
event.respondWith(
fetch(request)
.then((res) => {
if (res.ok) {
const copy = res.clone();
caches.open(CACHE).then((cache) => cache.put('./index.html', copy));
}
return res;
})
.catch(() => caches.match('./index.html'))
);
return;
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@schedule-app/public/sw.js` around lines 27 - 38, Update the navigation fetch
handler around the `fetch(request).then` callback to cache `./index.html` only
when the resolved response has status 200, matching the existing cache-first
guard. Continue returning the response unchanged and falling back to
`caches.match('./index.html')` on network failure.

Comment thread schedule-app/src/pages/MapPage.jsx
Comment thread schedule-app/src/pages/MorePage.jsx
Comment thread schedule-app/src/pages/PlannerPage.jsx
Comment on lines +719 to +726
@keyframes slideUp {
from {
transform: translateY(100%);
}
to {
transform: none;
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Stylelint error: keyframe name must be kebab-case.

slideUp violates keyframes-name-pattern and will fail lint. Rename to slide-up and update both usages.

Proposed fix (rename + update usages)
-@keyframes slideUp {
+@keyframes slide-up {

Update the two animation references:

-  animation: slideUp 0.24s cubic-bezier(0.2, 0.8, 0.2, 1);
+  animation: slide-up 0.24s cubic-bezier(0.2, 0.8, 0.2, 1);
-  animation: slideUp 0.22s cubic-bezier(0.2, 0.8, 0.2, 1);
+  animation: slide-up 0.22s cubic-bezier(0.2, 0.8, 0.2, 1);
🧰 Tools
🪛 Stylelint (17.14.0)

[error] 719-719: Expected keyframe name "slideUp" to be kebab-case (keyframes-name-pattern)

(keyframes-name-pattern)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@schedule-app/src/styles.css` around lines 719 - 726, Rename the `@keyframes`
identifier slideUp to slide-up in the stylesheet, then update both animation
references to use slide-up so the animation remains functional and satisfies the
kebab-case naming rule.

Source: Linters/SAST tools

Comment thread schedule-app/src/styles.css Outdated
- Rename Compass -> Stewardly across manifest/index.html/SW/README/UI copy;
  new shield-and-sprout icon and emerald/teal brand color (existing
  localStorage key kept as-is so nobody's data disappears on upgrade).
- Remove the logo badge from the Map page per feedback.
- App-wide haptic feedback via one delegated pointerdown listener (tap/
  confirm/warn tiers) instead of wiring every button individually.
- New Select component: app-styled bottom-sheet dropdown replacing native
  <select> in People and Map (Planner's selects follow in the next pass).
- Contact profile pictures: camera/photo picker, client-side downscaled to
  a small square JPEG data URL before storage; Avatar component renders the
  photo or falls back to initials everywhere a contact appears.
- Audited light mode across every page/modal — no defects found.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DaB8NfLY8nsxGAFk7XbCtw
@ecc-tools

ecc-tools Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

ECC bundle files are already tracked in this repository. Skipping generation of another bundle PR.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
schedule-app/src/pages/ContactsPage.jsx (1)

30-30: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Default reconnect cadence (30) is hardcoded independently in two files. Both derive reconnectDays from state.settings?.reconnectDays ?? 30; the shared root cause is no single source of truth for this default, risking silent divergence if it's ever changed in one place but not the other.

  • schedule-app/src/pages/ContactsPage.jsx#L30: extract 30 into an exported constant (e.g. DEFAULT_RECONNECT_DAYS in helpers.js) and use it here.
  • schedule-app/src/pages/ContactDetailPage.jsx#L28: import and use the same shared constant instead of repeating the literal 30.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@schedule-app/src/pages/ContactsPage.jsx` at line 30, Define and export a
shared DEFAULT_RECONNECT_DAYS constant with value 30 in helpers.js, then update
schedule-app/src/pages/ContactsPage.jsx lines 30-30 and
schedule-app/src/pages/ContactDetailPage.jsx lines 28-28 to import and use it
instead of hardcoding 30.
♻️ Duplicate comments (5)
schedule-app/public/manifest.webmanifest (1)

11-18: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Add PNG fallback icons alongside the SVG.

The manifest only declares an SVG icon. Chromium requires at least a 192x192 and a 512x512 pixel icon (PNG or WebP) to present a PWA install prompt. Provide raster fallbacks to ensure full browser compatibility.

🔧 Proposed fix
   "icons": [
     {
       "src": "icon.svg",
       "sizes": "any",
       "type": "image/svg+xml",
       "purpose": "any maskable"
-    }
+    },
+    {
+      "src": "icon-192.png",
+      "sizes": "192x192",
+      "type": "image/png",
+      "purpose": "any"
+    },
+    {
+      "src": "icon-512.png",
+      "sizes": "512x512",
+      "type": "image/png",
+      "purpose": "any maskable"
+    }
   ]
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@schedule-app/public/manifest.webmanifest` around lines 11 - 18, Add 192x192
and 512x512 PNG fallback entries to the manifest’s icons array alongside the
existing icon.svg declaration, using the correct PNG sources, dimensions, MIME
type, and compatible purpose so Chromium can discover installable raster icons
while preserving the SVG entry.
schedule-app/src/pages/MorePage.jsx (1)

78-92: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Validate imported backup data before merging into state.

importData parses arbitrary file content and passes it straight to actions.importData(data). A malformed or hand-edited backup (e.g., missing arrays for events or contacts) will propagate into the application state, leading to crashes in downstream components. Additionally, the FileReader lacks an onerror handler to catch and report read failures.

As per path instructions, "Never trust external data (API responses, file content, query params); always validate" and "Use schema-based validation where available."

🛡️ Proposed fix
   const importData = (e) => {
     const file = e.target.files?.[0];
     if (!file) return;
     const reader = new FileReader();
     reader.onload = () => {
       try {
         const data = JSON.parse(reader.result);
+        if (
+          !data ||
+          typeof data !== 'object' ||
+          !Array.isArray(data.goals) ||
+          !Array.isArray(data.events) ||
+          !Array.isArray(data.contacts)
+        ) {
+          throw new Error('Unexpected backup shape');
+        }
         actions.importData(data);
       } catch {
         alert('That file could not be read as a Stewardly backup.');
       }
     };
+    reader.onerror = () => alert('That file could not be read as a Stewardly backup.');
     reader.readAsText(file);
     e.target.value = '';
   };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@schedule-app/src/pages/MorePage.jsx` around lines 78 - 92, Update importData
to validate the parsed backup against the project’s existing schema or
validation utility before calling actions.importData, ensuring required fields
such as events and contacts have the expected structure and rejecting invalid
data with the existing alert. Add a FileReader onerror handler that reports read
failures through the same user-facing error path, while preserving the current
successful import behavior.

Source: Coding guidelines

schedule-app/public/sw.js (1)

27-38: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Guard against caching error responses as the offline shell.

The network-first navigation handler caches the resolved response without checking its status. A transient server error (e.g., 502 or 503 during deployment) will be cached as the offline fallback page, breaking the PWA's offline capabilities until a successful network fetch completes.

🔧 Proposed fix
   if (request.mode === 'navigate') {
     event.respondWith(
       fetch(request)
         .then((res) => {
-          const copy = res.clone();
-          caches.open(CACHE).then((cache) => cache.put('./index.html', copy));
+          if (res.ok) {
+            const copy = res.clone();
+            caches.open(CACHE).then((cache) => cache.put('./index.html', copy));
+          }
           return res;
         })
         .catch(() => caches.match('./index.html'))
     );
     return;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@schedule-app/public/sw.js` around lines 27 - 38, Update the navigation
handler around the fetch(request) success path to cache the offline shell only
when the resolved response is successful, using the response status/ok check
before caches.open(CACHE) and cache.put('./index.html', copy). Continue
returning the original response for all outcomes and preserve the existing
caches.match('./index.html') fallback on fetch rejection.
schedule-app/src/pages/ContactsPage.jsx (1)

98-98: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

createdAt still uses UTC date, inconsistent with local-date helpers.

new Date().toISOString().slice(0, 10) is UTC-based while daysSince/todayISO (used elsewhere in this same file) operate on local dates. Near midnight in negative-offset timezones this stores tomorrow's date, skewing overdue calculations by a day.

🐛 Proposed fix
-      lastContacted: '',
-      createdAt: new Date().toISOString().slice(0, 10),
+      lastContacted: '',
+      createdAt: todayISO(),
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@schedule-app/src/pages/ContactsPage.jsx` at line 98, Update the new contact
initialization around createdAt in ContactsPage so it uses the existing
local-date helper todayISO instead of deriving the date through new
Date().toISOString().slice(0, 10). Keep the stored value in the same YYYY-MM-DD
format and align it with the local-date behavior used by daysSince.
schedule-app/src/data/helpers.js (1)

186-240: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Recurrence-matching duplication between occursOn and matchesRule still present.

Per the change-detail description, occursOn and matchesRule still carry the same daily/weekly/biweekly/monthly switch, differing only in the skipDates check — the exact duplication flagged previously. Any future frequency-semantics change risks touching one and not the other (e.g., planner display vs. notifications.js reminder scanning).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@schedule-app/src/data/helpers.js` around lines 186 - 240, Remove the
duplicated recurrence frequency logic by having occursOn reuse matchesRule for
anchor, end-date, and daily/weekly/biweekly/monthly matching, while retaining
its skipDates check and existing non-recurring behavior. Keep matchesRule as the
single source of truth for recurrence semantics.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@schedule-app/src/App.jsx`:
- Around line 35-51: Update the onPointerDown handler in the App-wide haptic
feedback useEffect to also return when the matched element has
aria-disabled="true", alongside the existing native disabled check. Preserve the
current selector and haptic classification behavior for enabled elements.

In `@schedule-app/src/components/Select.jsx`:
- Around line 23-34: Update the Select component’s trigger and open-option-list
flow around the visible button and lines 36–63: add aria-expanded={open} to the
select trigger, and implement focus trapping while the option sheet is open so
keyboard focus cannot move into background content. Preserve the existing
disabled behavior and selection interactions.

In `@schedule-app/src/components/TabBar.jsx`:
- Line 19: Remove the redundant onClick={tapTick} handler from the TabBar
navigation element and delete the now-unused tapTick import in TabBar.jsx,
preserving the delegated haptic behavior provided by App.jsx.

In `@schedule-app/src/styles.css`:
- Line 858: Update the animation declaration using the slideUp keyframe to
reference the renamed kebab-case slide-up keyframe, keeping the existing
duration and timing function unchanged.

---

Outside diff comments:
In `@schedule-app/src/pages/ContactsPage.jsx`:
- Line 30: Define and export a shared DEFAULT_RECONNECT_DAYS constant with value
30 in helpers.js, then update schedule-app/src/pages/ContactsPage.jsx lines
30-30 and schedule-app/src/pages/ContactDetailPage.jsx lines 28-28 to import and
use it instead of hardcoding 30.

---

Duplicate comments:
In `@schedule-app/public/manifest.webmanifest`:
- Around line 11-18: Add 192x192 and 512x512 PNG fallback entries to the
manifest’s icons array alongside the existing icon.svg declaration, using the
correct PNG sources, dimensions, MIME type, and compatible purpose so Chromium
can discover installable raster icons while preserving the SVG entry.

In `@schedule-app/public/sw.js`:
- Around line 27-38: Update the navigation handler around the fetch(request)
success path to cache the offline shell only when the resolved response is
successful, using the response status/ok check before caches.open(CACHE) and
cache.put('./index.html', copy). Continue returning the original response for
all outcomes and preserve the existing caches.match('./index.html') fallback on
fetch rejection.

In `@schedule-app/src/data/helpers.js`:
- Around line 186-240: Remove the duplicated recurrence frequency logic by
having occursOn reuse matchesRule for anchor, end-date, and
daily/weekly/biweekly/monthly matching, while retaining its skipDates check and
existing non-recurring behavior. Keep matchesRule as the single source of truth
for recurrence semantics.

In `@schedule-app/src/pages/ContactsPage.jsx`:
- Line 98: Update the new contact initialization around createdAt in
ContactsPage so it uses the existing local-date helper todayISO instead of
deriving the date through new Date().toISOString().slice(0, 10). Keep the stored
value in the same YYYY-MM-DD format and align it with the local-date behavior
used by daysSince.

In `@schedule-app/src/pages/MorePage.jsx`:
- Around line 78-92: Update importData to validate the parsed backup against the
project’s existing schema or validation utility before calling
actions.importData, ensuring required fields such as events and contacts have
the expected structure and rejecting invalid data with the existing alert. Add a
FileReader onerror handler that reports read failures through the same
user-facing error path, while preserving the current successful import behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 5febbf88-0fd0-4be6-b073-684f17ddd652

📥 Commits

Reviewing files that changed from the base of the PR and between 2c7443e and 8130078.

⛔ Files ignored due to path filters (1)
  • schedule-app/public/icon.svg is excluded by !**/*.svg
📒 Files selected for processing (19)
  • schedule-app/README.md
  • schedule-app/index.html
  • schedule-app/package.json
  • schedule-app/public/manifest.webmanifest
  • schedule-app/public/sw.js
  • schedule-app/src/App.jsx
  • schedule-app/src/components/Avatar.jsx
  • schedule-app/src/components/Logo.jsx
  • schedule-app/src/components/Select.jsx
  • schedule-app/src/components/TabBar.jsx
  • schedule-app/src/data/haptics.js
  • schedule-app/src/data/helpers.js
  • schedule-app/src/data/image.js
  • schedule-app/src/data/notifications.js
  • schedule-app/src/pages/ContactDetailPage.jsx
  • schedule-app/src/pages/ContactsPage.jsx
  • schedule-app/src/pages/MapPage.jsx
  • schedule-app/src/pages/MorePage.jsx
  • schedule-app/src/styles.css
📜 Review details
⏰ Context from checks skipped due to timeout. (1)
  • GitHub Check: Greptile Review
🧰 Additional context used
📓 Path-based instructions (15)
**/*.{js,ts,jsx,tsx,html,php,java,cs,rb,go}

📄 CodeRabbit inference engine (.cursor/rules/common-security.md)

Implement XSS prevention by sanitizing HTML output

Files:

  • schedule-app/index.html
  • schedule-app/src/data/image.js
  • schedule-app/src/components/Avatar.jsx
  • schedule-app/src/data/haptics.js
  • schedule-app/src/components/Select.jsx
  • schedule-app/src/components/Logo.jsx
  • schedule-app/src/components/TabBar.jsx
  • schedule-app/public/sw.js
  • schedule-app/src/App.jsx
  • schedule-app/src/pages/ContactsPage.jsx
  • schedule-app/src/pages/MorePage.jsx
  • schedule-app/src/data/notifications.js
  • schedule-app/src/pages/MapPage.jsx
  • schedule-app/src/pages/ContactDetailPage.jsx
  • schedule-app/src/data/helpers.js
**/*.{js,ts,jsx,tsx,py,java,cs,rb,go,php,swift,kt,rs,c,cpp,h,hpp,properties,yml,yaml,json,env,config}

📄 CodeRabbit inference engine (.cursor/rules/common-security.md)

NEVER hardcode secrets in source code - ALWAYS use environment variables or a secret manager

Files:

  • schedule-app/package.json
  • schedule-app/src/data/image.js
  • schedule-app/src/components/Avatar.jsx
  • schedule-app/src/data/haptics.js
  • schedule-app/src/components/Select.jsx
  • schedule-app/src/components/Logo.jsx
  • schedule-app/src/components/TabBar.jsx
  • schedule-app/public/sw.js
  • schedule-app/src/App.jsx
  • schedule-app/src/pages/ContactsPage.jsx
  • schedule-app/src/pages/MorePage.jsx
  • schedule-app/src/data/notifications.js
  • schedule-app/src/pages/MapPage.jsx
  • schedule-app/src/pages/ContactDetailPage.jsx
  • schedule-app/src/data/helpers.js
**/*.{js,ts,jsx,tsx,json,env*}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Do not hardcode secrets, API keys, passwords, or tokens

Files:

  • schedule-app/package.json
  • schedule-app/src/data/image.js
  • schedule-app/src/components/Avatar.jsx
  • schedule-app/src/data/haptics.js
  • schedule-app/src/components/Select.jsx
  • schedule-app/src/components/Logo.jsx
  • schedule-app/src/components/TabBar.jsx
  • schedule-app/public/sw.js
  • schedule-app/src/App.jsx
  • schedule-app/src/pages/ContactsPage.jsx
  • schedule-app/src/pages/MorePage.jsx
  • schedule-app/src/data/notifications.js
  • schedule-app/src/pages/MapPage.jsx
  • schedule-app/src/pages/ContactDetailPage.jsx
  • schedule-app/src/data/helpers.js
**/*.{js,ts,jsx,tsx,py,java,cs,go,rb,php,scala,kt}

📄 CodeRabbit inference engine (.cursor/rules/common-coding-style.md)

**/*.{js,ts,jsx,tsx,py,java,cs,go,rb,php,scala,kt}: Always create new objects, never mutate existing ones. Use immutable patterns to prevent hidden side effects and enable safe concurrency
Organize code into many small files (200-400 lines typical, 800 lines max) organized by feature/domain rather than by type
Always handle errors explicitly at every level and never silently swallow errors
Always validate all user input before processing at system boundaries
Use schema-based validation where available
Fail fast with clear error messages when validation fails
Never trust external data (API responses, user input, file content)
Ensure code is readable and well-named
Keep functions small (less than 50 lines)
Keep files focused (less than 800 lines)
Avoid deep nesting (more than 4 levels)
Do not use hardcoded values; use constants or configuration instead

Files:

  • schedule-app/src/data/image.js
  • schedule-app/src/components/Avatar.jsx
  • schedule-app/src/data/haptics.js
  • schedule-app/src/components/Select.jsx
  • schedule-app/src/components/Logo.jsx
  • schedule-app/src/components/TabBar.jsx
  • schedule-app/public/sw.js
  • schedule-app/src/App.jsx
  • schedule-app/src/pages/ContactsPage.jsx
  • schedule-app/src/pages/MorePage.jsx
  • schedule-app/src/data/notifications.js
  • schedule-app/src/pages/MapPage.jsx
  • schedule-app/src/pages/ContactDetailPage.jsx
  • schedule-app/src/data/helpers.js
**/{server,backend,api,src}/**/*.{ts,tsx,js,py,java,cs,go,rb,php,scala,kt}

📄 CodeRabbit inference engine (.cursor/rules/common-coding-style.md)

Log detailed error context on the server side

Files:

  • schedule-app/src/data/image.js
  • schedule-app/src/data/haptics.js
  • schedule-app/src/data/notifications.js
  • schedule-app/src/data/helpers.js
**/*.{js,ts,jsx,tsx,py,java,cs,rb,go,php,swift,kt,rs,c,cpp,h,hpp}

📄 CodeRabbit inference engine (.cursor/rules/common-security.md)

No hardcoded secrets (API keys, passwords, tokens) - validate before any commit

Files:

  • schedule-app/src/data/image.js
  • schedule-app/src/components/Avatar.jsx
  • schedule-app/src/data/haptics.js
  • schedule-app/src/components/Select.jsx
  • schedule-app/src/components/Logo.jsx
  • schedule-app/src/components/TabBar.jsx
  • schedule-app/public/sw.js
  • schedule-app/src/App.jsx
  • schedule-app/src/pages/ContactsPage.jsx
  • schedule-app/src/pages/MorePage.jsx
  • schedule-app/src/data/notifications.js
  • schedule-app/src/pages/MapPage.jsx
  • schedule-app/src/pages/ContactDetailPage.jsx
  • schedule-app/src/data/helpers.js
**/*.{js,ts,jsx,tsx,py,java,cs,rb,go,php}

📄 CodeRabbit inference engine (.cursor/rules/common-security.md)

**/*.{js,ts,jsx,tsx,py,java,cs,rb,go,php}: All user inputs must be validated
Enable CSRF protection on all state-changing endpoints
Verify authentication and authorization for all protected endpoints
Implement rate limiting on all endpoints to prevent abuse
Ensure error messages do not leak sensitive data in responses

Files:

  • schedule-app/src/data/image.js
  • schedule-app/src/components/Avatar.jsx
  • schedule-app/src/data/haptics.js
  • schedule-app/src/components/Select.jsx
  • schedule-app/src/components/Logo.jsx
  • schedule-app/src/components/TabBar.jsx
  • schedule-app/public/sw.js
  • schedule-app/src/App.jsx
  • schedule-app/src/pages/ContactsPage.jsx
  • schedule-app/src/pages/MorePage.jsx
  • schedule-app/src/data/notifications.js
  • schedule-app/src/pages/MapPage.jsx
  • schedule-app/src/pages/ContactDetailPage.jsx
  • schedule-app/src/data/helpers.js
**/*.{js,ts,jsx,tsx,py,java,cs,rb,go,php,sql}

📄 CodeRabbit inference engine (.cursor/rules/common-security.md)

Use parameterized queries to prevent SQL injection

Files:

  • schedule-app/src/data/image.js
  • schedule-app/src/components/Avatar.jsx
  • schedule-app/src/data/haptics.js
  • schedule-app/src/components/Select.jsx
  • schedule-app/src/components/Logo.jsx
  • schedule-app/src/components/TabBar.jsx
  • schedule-app/public/sw.js
  • schedule-app/src/App.jsx
  • schedule-app/src/pages/ContactsPage.jsx
  • schedule-app/src/pages/MorePage.jsx
  • schedule-app/src/data/notifications.js
  • schedule-app/src/pages/MapPage.jsx
  • schedule-app/src/pages/ContactDetailPage.jsx
  • schedule-app/src/data/helpers.js
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursor/rules/typescript-coding-style.md)

**/*.{ts,tsx,js,jsx}: Use spread operator for immutable updates in TypeScript/JavaScript instead of direct mutation
Use async/await with try-catch for error handling in TypeScript/JavaScript
Use Zod for schema-based input validation in TypeScript/JavaScript
No console.log statements in production code; use proper logging libraries instead

**/*.{ts,tsx,js,jsx}: Auto-format JavaScript/TypeScript files using Prettier after edit
Warn about console.log statements in edited files
Check all modified files for console.log statements before session ends

**/*.{ts,tsx,js,jsx}: Use the ApiResponse interface pattern with generic type parameter: interface ApiResponse<T> { success: boolean; data?: T; error?: string; meta?: { total: number; page: number; limit: number; } }
Implement custom React hooks following the pattern: export a named function with use prefix, generic type parameters, and proper useEffect cleanup for side effects

**/*.{ts,tsx,js,jsx}: Never hardcode secrets; always use environment variables for sensitive credentials like API keys
Throw an error when required environment variables are not configured to fail fast and ensure security prerequisites are met

Use Playwright as the E2E testing framework for critical user flows in TypeScript/JavaScript

Files:

  • schedule-app/src/data/image.js
  • schedule-app/src/components/Avatar.jsx
  • schedule-app/src/data/haptics.js
  • schedule-app/src/components/Select.jsx
  • schedule-app/src/components/Logo.jsx
  • schedule-app/src/components/TabBar.jsx
  • schedule-app/public/sw.js
  • schedule-app/src/App.jsx
  • schedule-app/src/pages/ContactsPage.jsx
  • schedule-app/src/pages/MorePage.jsx
  • schedule-app/src/data/notifications.js
  • schedule-app/src/pages/MapPage.jsx
  • schedule-app/src/pages/ContactDetailPage.jsx
  • schedule-app/src/data/helpers.js
**/*.{js,ts,jsx,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{js,ts,jsx,tsx}: Always create new objects and never mutate in place; return new copies instead
Keep files between 200–400 lines typical, with a maximum of 800 lines
Extract helpers when a file exceeds 200 lines
Handle errors explicitly at every level; never swallow errors silently
Validate all user input before processing; use schema-based validation where available
Never trust external data (API responses, file content, query params); always validate
All user inputs must be validated and sanitized
Error messages must be scrubbed of sensitive internals
Use readable, well-named identifiers in all code
Keep functions under 50 lines
Keep files under 800 lines
Avoid nesting deeper than 4 levels
Implement comprehensive error handling in all code
Do not hardcode values; use constants or environment configuration instead
Do not use in-place mutation; always return new objects or state

Files:

  • schedule-app/src/data/image.js
  • schedule-app/src/components/Avatar.jsx
  • schedule-app/src/data/haptics.js
  • schedule-app/src/components/Select.jsx
  • schedule-app/src/components/Logo.jsx
  • schedule-app/src/components/TabBar.jsx
  • schedule-app/public/sw.js
  • schedule-app/src/App.jsx
  • schedule-app/src/pages/ContactsPage.jsx
  • schedule-app/src/pages/MorePage.jsx
  • schedule-app/src/data/notifications.js
  • schedule-app/src/pages/MapPage.jsx
  • schedule-app/src/pages/ContactDetailPage.jsx
  • schedule-app/src/data/helpers.js
**/*.{js,ts}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{js,ts}: Use parameterized queries for all database writes (no string interpolation)
Auth/authz must be checked server-side for every sensitive path
Rate limiting must be applied to all public endpoints

Files:

  • schedule-app/src/data/image.js
  • schedule-app/src/data/haptics.js
  • schedule-app/public/sw.js
  • schedule-app/src/data/notifications.js
  • schedule-app/src/data/helpers.js
**/*.{jsx,tsx,js,ts}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

HTML output must be sanitized where applicable

Files:

  • schedule-app/src/data/image.js
  • schedule-app/src/components/Avatar.jsx
  • schedule-app/src/data/haptics.js
  • schedule-app/src/components/Select.jsx
  • schedule-app/src/components/Logo.jsx
  • schedule-app/src/components/TabBar.jsx
  • schedule-app/public/sw.js
  • schedule-app/src/App.jsx
  • schedule-app/src/pages/ContactsPage.jsx
  • schedule-app/src/pages/MorePage.jsx
  • schedule-app/src/data/notifications.js
  • schedule-app/src/pages/MapPage.jsx
  • schedule-app/src/pages/ContactDetailPage.jsx
  • schedule-app/src/data/helpers.js
**/*.{js,ts,env*}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Required environment variables must be validated at startup

Files:

  • schedule-app/src/data/image.js
  • schedule-app/src/data/haptics.js
  • schedule-app/public/sw.js
  • schedule-app/src/data/notifications.js
  • schedule-app/src/data/helpers.js
**/{src,app}/**/*.{jsx,tsx,vue,html}

📄 CodeRabbit inference engine (.cursor/rules/common-coding-style.md)

Provide user-friendly error messages in UI-facing code

Files:

  • schedule-app/src/components/Avatar.jsx
  • schedule-app/src/components/Select.jsx
  • schedule-app/src/components/Logo.jsx
  • schedule-app/src/components/TabBar.jsx
  • schedule-app/src/App.jsx
  • schedule-app/src/pages/ContactsPage.jsx
  • schedule-app/src/pages/MorePage.jsx
  • schedule-app/src/pages/MapPage.jsx
  • schedule-app/src/pages/ContactDetailPage.jsx
{**/*.tsx,**/*.jsx,**/components/**}

📄 CodeRabbit inference engine (CLAUDE.md)

When working on React files (*.tsx, *.jsx, components/**), use react-patterns and react-testing skills. Invoke /react-review, /react-build, /react-test commands for React-specific work.

Files:

  • schedule-app/src/components/Avatar.jsx
  • schedule-app/src/components/Select.jsx
  • schedule-app/src/components/Logo.jsx
  • schedule-app/src/components/TabBar.jsx
  • schedule-app/src/App.jsx
  • schedule-app/src/pages/ContactsPage.jsx
  • schedule-app/src/pages/MorePage.jsx
  • schedule-app/src/pages/MapPage.jsx
  • schedule-app/src/pages/ContactDetailPage.jsx
🪛 ast-grep (0.44.1)
schedule-app/src/components/Select.jsx

[warning] 54-54: A list component should have a key to prevent re-rendering
Context: <span className="select-swatch" style={{ background: o.color }} />
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(list-component-needs-key)


[warning] 55-55: A list component should have a key to prevent re-rendering
Context: {o.label}
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(list-component-needs-key)


[warning] 56-56: A list component should have a key to prevent re-rendering
Context:
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(list-component-needs-key)

schedule-app/src/pages/ContactsPage.jsx

[warning] 155-155: A list component should have a key to prevent re-rendering
Context: {c.name.split(' ')[0]}
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(list-component-needs-key)


[warning] 194-194: A list component should have a key to prevent re-rendering
Context:
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(list-component-needs-key)


[warning] 195-195: A list component should have a key to prevent re-rendering
Context:
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(list-component-needs-key)

🔇 Additional comments (13)
schedule-app/src/pages/MapPage.jsx (2)

7-7: LGTM!


301-306: LGTM!

schedule-app/index.html (1)

9-16: LGTM!

schedule-app/package.json (1)

6-6: LGTM!

schedule-app/src/data/notifications.js (1)

6-6: LGTM!

schedule-app/src/data/haptics.js (1)

1-36: LGTM!

schedule-app/src/App.jsx (1)

5-5: LGTM!

schedule-app/src/components/Select.jsx (1)

54-56: Static-analysis key warnings are false positives.

Each list item already has key={o.value} on the returned <button> (line 45); the flagged children are non-list siblings inside that keyed element, not separate list items.

schedule-app/src/data/helpers.js (1)

154-162: LGTM!

schedule-app/src/components/Logo.jsx (1)

1-40: LGTM!

schedule-app/src/data/image.js (1)

1-26: LGTM!

schedule-app/src/components/Avatar.jsx (1)

1-80: LGTM!

schedule-app/src/styles.css (1)

1-38: LGTM!

Also applies to: 528-531, 802-899, 1205-1260

Comment thread schedule-app/src/App.jsx
Comment on lines +35 to +51
// App-wide haptic feedback: one delegated listener instead of wiring every
// button individually. Danger actions get a firmer double-pulse, primary
// actions a slightly stronger tick, everything else a light tap.
useEffect(() => {
const onPointerDown = (e) => {
const el = e.target.closest?.(
'button, a, [role="button"], [role="switch"], input[type="checkbox"], input[type="radio"]'
);
if (!el || el.disabled) return;
if (el.classList.contains('btn-danger') || el.classList.contains('btn-danger-ghost')) warnTick();
else if (el.classList.contains('btn-primary') || el.classList.contains('fab')) confirmTick();
else tapTick();
};
document.addEventListener('pointerdown', onPointerDown, { passive: true });
return () => document.removeEventListener('pointerdown', onPointerDown);
}, []);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

Consider ignoring elements with aria-disabled="true".

The delegated haptic listener correctly ignores elements with the native disabled property, but links (<a>) and custom ARIA controls ([role="button"]) use the aria-disabled="true" attribute when disabled. Adding a check for this attribute ensures disabled UI components don't erroneously trigger tactile feedback.

💡 Proposed refactor
-      if (!el || el.disabled) return;
+      if (!el || el.disabled || el.getAttribute('aria-disabled') === 'true') return;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// App-wide haptic feedback: one delegated listener instead of wiring every
// button individually. Danger actions get a firmer double-pulse, primary
// actions a slightly stronger tick, everything else a light tap.
useEffect(() => {
const onPointerDown = (e) => {
const el = e.target.closest?.(
'button, a, [role="button"], [role="switch"], input[type="checkbox"], input[type="radio"]'
);
if (!el || el.disabled) return;
if (el.classList.contains('btn-danger') || el.classList.contains('btn-danger-ghost')) warnTick();
else if (el.classList.contains('btn-primary') || el.classList.contains('fab')) confirmTick();
else tapTick();
};
document.addEventListener('pointerdown', onPointerDown, { passive: true });
return () => document.removeEventListener('pointerdown', onPointerDown);
}, []);
// App-wide haptic feedback: one delegated listener instead of wiring every
// button individually. Danger actions get a firmer double-pulse, primary
// actions a slightly stronger tick, everything else a light tap.
useEffect(() => {
const onPointerDown = (e) => {
const el = e.target.closest?.(
'button, a, [role="button"], [role="switch"], input[type="checkbox"], input[type="radio"]'
);
if (!el || el.disabled || el.getAttribute('aria-disabled') === 'true') return;
if (el.classList.contains('btn-danger') || el.classList.contains('btn-danger-ghost')) warnTick();
else if (el.classList.contains('btn-primary') || el.classList.contains('fab')) confirmTick();
else tapTick();
};
document.addEventListener('pointerdown', onPointerDown, { passive: true });
return () => document.removeEventListener('pointerdown', onPointerDown);
}, []);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@schedule-app/src/App.jsx` around lines 35 - 51, Update the onPointerDown
handler in the App-wide haptic feedback useEffect to also return when the
matched element has aria-disabled="true", alongside the existing native disabled
check. Preserve the current selector and haptic classification behavior for
enabled elements.

Comment on lines +23 to +34
<button
type="button"
className="select-trigger"
onClick={() => !disabled && setOpen(true)}
disabled={disabled}
>
<span className="select-trigger-label">
{current?.color && <span className="select-swatch" style={{ background: current.color }} />}
{current ? current.label : <span className="muted">{placeholder}</span>}
</span>
<ChevronDown />
</button>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Trigger lacks aria-expanded; open sheet doesn't trap focus.

Keyboard users can Tab past the open option list into background content since these are plain buttons, not a native <dialog>. Also, aria-expanded={open} on the trigger would help screen readers reflect state.

Also applies to: 36-63

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@schedule-app/src/components/Select.jsx` around lines 23 - 34, Update the
Select component’s trigger and open-option-list flow around the visible button
and lines 36–63: add aria-expanded={open} to the select trigger, and implement
focus trapping while the option sheet is open so keyboard focus cannot move into
background content. Preserve the existing disabled behavior and selection
interactions.

Comment thread schedule-app/src/components/TabBar.jsx Outdated
background: var(--surface);
border-radius: 20px 20px 0 0;
padding-bottom: env(safe-area-inset-bottom, 0px);
animation: slideUp 0.2s cubic-bezier(0.2, 0.8, 0.2, 1);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Another usage of the non-kebab-case slideUp keyframe.

Same stylelint keyframes-name-pattern violation flagged previously — this usage must be updated too when the keyframe is renamed to slide-up, or the animation breaks.

🛠️ Fix (pairs with the rename at the `@keyframes` definition)
-  animation: slideUp 0.2s cubic-bezier(0.2, 0.8, 0.2, 1);
+  animation: slide-up 0.2s cubic-bezier(0.2, 0.8, 0.2, 1);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@schedule-app/src/styles.css` at line 858, Update the animation declaration
using the slideUp keyframe to reference the renamed kebab-case slide-up
keyframe, keeping the existing duration and timing function unchanged.

…view

The biggest single change so far — reworks how you interact with events,
goals, and people:

- New EditorSheet component: full-page (not 3/4) editor used for events,
  goals, and people. Checkmark save top-right with a pop animation; swipe
  the grip down (or tap the X) prompts Save/Discard/Cancel when there are
  unsaved changes, otherwise just closes.
- Events: single tap opens a read-only detail view (date, time, repeat,
  reminder, location + mini map, contact, notes); a pencil button opens the
  same event in the full editor. Delete-this-day / delete-series moved into
  the editor's footer.
- Day timeline drag now requires a ~500ms press-and-hold to "arm" (haptic +
  grip-dots indicator) before it moves; a plain tap always opens the detail
  view instead of nudging the event. Short/accidental presses no longer
  reschedule anything.
- Custom recurrence: a "Custom days" repeat option with a Sun-Sat toggle
  row (event.repeatDays), on top of the existing daily/weekly/biweekly/
  monthly rules.
- Month view added alongside Day/Week, with a 6-week grid and per-day event
  dots; confirmed events schedule fine 4+ months out (no artificial cap).
- Multi-select: a "Select" toggle lets you tap multiple occurrences and
  shift them all by +1 day / +1 week / -1 day from a bottom action bar.
- Event location can now drop a temporary pin on an embedded mini map
  (locLat/locLng) instead of only free-text, surfaced as a Directions link
  in the detail view.
- Fixed a real bug in the new Select component: picking an option closed
  the sheet but a "ghost" click immediately landed on the now-revealed
  trigger underneath and reopened it. Select now briefly swallows the
  trigger's next click right after a selection closes it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DaB8NfLY8nsxGAFk7XbCtw
@ecc-tools

ecc-tools Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

ECC bundle files are already tracked in this repository. Skipping generation of another bundle PR.

New Home tab (now first in the tab bar):
- Goal overview bubble with mini daily/weekly progress rings, tapping
  through to Goals.
- Important reminders: goals/tasks/events with a reminder or due date
  today, merged into one list.
- Tasks: simple checkable list with an inline add row; tasks can carry a
  reminder time (shown on Home and counted in "Important reminders").
- Notes: Google Keep-style masonry grid — free text or a checklist, an
  optional color, and a pin-to-top toggle. Full-page editor via the same
  EditorSheet used elsewhere.
- A "Pro" bubble (crown icon) in the top-right for non-Pro users, linking
  to a new pricing page.
- Quick-add "+" opens a small sheet for New event / New person / New task /
  New note; event and contact quick-adds hand off to the Planner/People
  editors via router state.

New Pricing page:
- Monthly ($4) / annual ($40) toggle, a feature comparison table, and a
  "Try Pro" button that flips a local settings.isPro demo flag. Explicitly
  labeled as a demo — there's no payment processor wired up, so this
  previews the Pro experience without charging anyone. Real subscriptions
  need a backend (Stripe or similar) before this can take payments.

Store: new tasks/notes collections and settings.isPro/colorScheme.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DaB8NfLY8nsxGAFk7XbCtw
@ecc-tools

ecc-tools Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

ECC bundle files are already tracked in this repository. Skipping generation of another bundle PR.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (5)
schedule-app/src/pages/ContactDetailPage.jsx (1)

44-52: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Extract hardcoded configuration values into constants.

As per coding guidelines, do not use hardcoded values; use constants instead. Extract the 60-day horizon and the 5-event limit into named constants at the module level.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@schedule-app/src/pages/ContactDetailPage.jsx` around lines 44 - 52, In the
event collection loop on ContactDetailPage, replace the hardcoded 60-day horizon
and 5-event limit with descriptive module-level constants, then use those
constants in the loop condition and final out.slice call.

Source: Coding guidelines

schedule-app/src/pages/ContactsPage.jsx (1)

12-15: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Allow explicit 0 to disable reconnect reminders.

Currently, explicitly setting a cadence of 0 to disable reminders is lost during save and read because 0 evaluates to falsy, making the logic fall back to the default (e.g. 30 days). The user's intent to disable the reminder is silently discarded.

  • schedule-app/src/pages/ContactsPage.jsx#L12-L15: Update reconnectDaysOf to use an explicit null check: const days = contact.cadenceDays != null ? Number(contact.cadenceDays) : defaultDays;.
  • schedule-app/src/pages/ContactDetailPage.jsx#L73-L73: Change the initialization to cadenceText: contact.cadenceDays != null ? String(contact.cadenceDays) : '' to preserve 0.
  • schedule-app/src/pages/ContactDetailPage.jsx#L92-L92: Change the save payload to cadenceDays: editing.cadenceText === '' ? null : Number(editing.cadenceText) to persist the distinction between an unset value (null) and an explicit 0.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@schedule-app/src/pages/ContactsPage.jsx` around lines 12 - 15, Allow an
explicit cadence of 0 to disable reminders instead of falling back to the
default. In schedule-app/src/pages/ContactsPage.jsx:12-15, update
reconnectDaysOf to use a null check; in
schedule-app/src/pages/ContactDetailPage.jsx:73, preserve 0 when initializing
cadenceText; and in schedule-app/src/pages/ContactDetailPage.jsx:92, save an
empty value as null while persisting numeric values, including 0.
schedule-app/src/pages/GoalsPage.jsx (1)

32-325: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoff

Component functions exceed the 50-line limit.

The main page components significantly exceed the maximum 50 lines per function rule mandated by the coding guidelines.

  • schedule-app/src/pages/GoalsPage.jsx#L32-L325: Extract EditorSheet forms, EmptyState, or list sections into separate components to reduce function size.
  • schedule-app/src/pages/ContactsPage.jsx#L22-L286: Extract the EditorSheet or the main list rendering logic.
  • schedule-app/src/pages/ContactDetailPage.jsx#L19-L346: Extract the EditorSheet and quick actions/detail sections.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@schedule-app/src/pages/GoalsPage.jsx` around lines 32 - 325, Reduce the
component function sizes to 50 lines or fewer by extracting cohesive rendering
sections into separate components: in schedule-app/src/pages/GoalsPage.jsx lines
32-325, extract the EditorSheet form, EmptyState, or goal-list sections; in
schedule-app/src/pages/ContactsPage.jsx lines 22-286, extract the EditorSheet or
main list rendering; and in schedule-app/src/pages/ContactDetailPage.jsx lines
19-346, extract the EditorSheet and quick-actions/detail sections. Preserve
existing state, props, actions, and behavior while keeping each page component
within the limit.

Source: Coding guidelines

schedule-app/src/pages/PlannerPage.jsx (2)

845-851: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Gate notifications on permission setReminder sets settings.notifications to true even when requestNotificationPermission() returns denied. That leaves the app showing notifications as enabled while reminders still won’t fire; only persist the setting when permission is granted.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@schedule-app/src/pages/PlannerPage.jsx` around lines 845 - 851, Update
setReminder so it captures the result of requestNotificationPermission and only
calls setSettings({ notifications: true }) when the returned permission status
is granted; keep updating the draft reminder and requesting permission for
positive mins unchanged.

811-826: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Reset the missing series fields in applyScope('all'). editing.base only restores title/start/end/contactId/location/notes/date, so typeId, color, reminder, locLat, and locLng stay on the draft and doSave can write those occurrence-only values back onto the series. Add them to the base snapshot or clear them when switching to “All events”.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@schedule-app/src/pages/PlannerPage.jsx` around lines 811 - 826, Update the s
=== 'all' branch of applyScope to restore or reset the series fields typeId,
color, reminder, locLat, and locLng from the base snapshot, preventing
occurrence-only draft values from being saved onto the series. Extend the base
snapshot if those fields are part of its contract; otherwise clear them when
switching to “All events,” while preserving the existing title, timing, and
location restoration behavior.
♻️ Duplicate comments (2)
schedule-app/src/pages/ContactsPage.jsx (1)

103-103: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

createdAt uses UTC while the rest of the app uses local dates.

new Date().toISOString().slice(0, 10) is UTC-based, but daysSince/todayISO operate on local dates. Near midnight in negative-offset timezones this stores tomorrow's date, skewing the overdue calculation by a day. todayISO is already imported.

🐛 Proposed fix
-      createdAt: new Date().toISOString().slice(0, 10),
+      createdAt: todayISO(),
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@schedule-app/src/pages/ContactsPage.jsx` at line 103, Update the createdAt
assignment in the contact creation flow to use the imported todayISO helper
instead of deriving the date through new Date().toISOString().slice(0, 10),
keeping the stored date consistent with the local-date logic used by daysSince.
schedule-app/src/pages/GoalsPage.jsx (1)

85-88: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Honor the resolved notification permission before enabling reminders.

Both call sites await requestNotificationPermission() (which can return 'denied') but ignore the result — they flip notifications: true and persist the reminder unconditionally, so a bell shows for a notification that will never fire.

🐛 Proposed fix
-    if (editing.reminderOn) {
-      await requestNotificationPermission();
-      actions.setSettings({ notifications: true });
-    }
-    const payload = {
-      title,
-      category: editing.category.trim(),
-      period: editing.period,
-      target: Math.max(1, Number(editing.target) || 1),
-      unit: editing.unit.trim(),
-      reminder: editing.reminderOn ? { time: editing.reminderTime } : null,
-    };
+    let reminder = editing.reminderOn ? { time: editing.reminderTime } : null;
+    if (editing.reminderOn) {
+      const perm = await requestNotificationPermission();
+      if (perm === 'granted') {
+        actions.setSettings({ notifications: true });
+      } else {
+        reminder = null;
+        alert('Notifications are blocked by your browser settings.');
+      }
+    }
+    const payload = {
+      title,
+      category: editing.category.trim(),
+      period: editing.period,
+      target: Math.max(1, Number(editing.target) || 1),
+      unit: editing.unit.trim(),
+      reminder,
+    };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@schedule-app/src/pages/GoalsPage.jsx` around lines 85 - 88, Update both call
sites that handle reminder enabling to capture the result of
requestNotificationPermission() and only call actions.setSettings({
notifications: true }) and persist or enable the reminder when permission
resolves successfully; leave notifications disabled and avoid enabling the
reminder when the result is denied.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@schedule-app/src/components/MiniMapPicker.jsx`:
- Around line 29-34: Clear the 60ms resize timer created by setTimeout in the
effect before removing the map. Store the timer handle, then update the cleanup
function to cancel it before calling map.remove() and resetting mapRef.current.

In `@schedule-app/src/pages/GoalsPage.jsx`:
- Around line 218-235: Prevent saving forms with empty required primary fields
by passing the specified saveDisabled expressions to each EditorSheet:
schedule-app/src/pages/GoalsPage.jsx lines 218-235 use !editing?.title?.trim(),
schedule-app/src/pages/ContactsPage.jsx lines 222-223 use !adding?.name?.trim(),
and schedule-app/src/pages/ContactDetailPage.jsx lines 242-255 use
!editing?.name?.trim().

In `@schedule-app/src/pages/MapPage.jsx`:
- Line 188: Update the temporary pin creation in the MapPage marker flow to
remove the keyboard: false option from the L.marker configuration, preserving
the default keyboard accessibility while leaving the existing icon and layer
behavior unchanged.
- Around line 86-94: Clear the active long-press timeout during component
unmount cleanup. Add an effect near the handlersRef setup that returns cleanup
calling clearTimeout for the timer stored by the press-handling logic, while
preserving the existing timeout behavior during mounted interactions.

In `@schedule-app/src/styles.css`:
- Around line 812-822: Rename the sheetUp and savePop keyframes to kebab-case
names, and update both corresponding animation declarations to reference the
renamed keyframes. Preserve the existing animation behavior and timing.

---

Outside diff comments:
In `@schedule-app/src/pages/ContactDetailPage.jsx`:
- Around line 44-52: In the event collection loop on ContactDetailPage, replace
the hardcoded 60-day horizon and 5-event limit with descriptive module-level
constants, then use those constants in the loop condition and final out.slice
call.

In `@schedule-app/src/pages/ContactsPage.jsx`:
- Around line 12-15: Allow an explicit cadence of 0 to disable reminders instead
of falling back to the default. In
schedule-app/src/pages/ContactsPage.jsx:12-15, update reconnectDaysOf to use a
null check; in schedule-app/src/pages/ContactDetailPage.jsx:73, preserve 0 when
initializing cadenceText; and in
schedule-app/src/pages/ContactDetailPage.jsx:92, save an empty value as null
while persisting numeric values, including 0.

In `@schedule-app/src/pages/GoalsPage.jsx`:
- Around line 32-325: Reduce the component function sizes to 50 lines or fewer
by extracting cohesive rendering sections into separate components: in
schedule-app/src/pages/GoalsPage.jsx lines 32-325, extract the EditorSheet form,
EmptyState, or goal-list sections; in schedule-app/src/pages/ContactsPage.jsx
lines 22-286, extract the EditorSheet or main list rendering; and in
schedule-app/src/pages/ContactDetailPage.jsx lines 19-346, extract the
EditorSheet and quick-actions/detail sections. Preserve existing state, props,
actions, and behavior while keeping each page component within the limit.

In `@schedule-app/src/pages/PlannerPage.jsx`:
- Around line 845-851: Update setReminder so it captures the result of
requestNotificationPermission and only calls setSettings({ notifications: true
}) when the returned permission status is granted; keep updating the draft
reminder and requesting permission for positive mins unchanged.
- Around line 811-826: Update the s === 'all' branch of applyScope to restore or
reset the series fields typeId, color, reminder, locLat, and locLng from the
base snapshot, preventing occurrence-only draft values from being saved onto the
series. Extend the base snapshot if those fields are part of its contract;
otherwise clear them when switching to “All events,” while preserving the
existing title, timing, and location restoration behavior.

---

Duplicate comments:
In `@schedule-app/src/pages/ContactsPage.jsx`:
- Line 103: Update the createdAt assignment in the contact creation flow to use
the imported todayISO helper instead of deriving the date through new
Date().toISOString().slice(0, 10), keeping the stored date consistent with the
local-date logic used by daysSince.

In `@schedule-app/src/pages/GoalsPage.jsx`:
- Around line 85-88: Update both call sites that handle reminder enabling to
capture the result of requestNotificationPermission() and only call
actions.setSettings({ notifications: true }) and persist or enable the reminder
when permission resolves successfully; leave notifications disabled and avoid
enabling the reminder when the result is denied.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 6882571e-c8fc-4fc9-bae9-355c15b583b5

📥 Commits

Reviewing files that changed from the base of the PR and between 8130078 and 297274c.

📒 Files selected for processing (10)
  • schedule-app/src/components/EditorSheet.jsx
  • schedule-app/src/components/MiniMapPicker.jsx
  • schedule-app/src/components/Select.jsx
  • schedule-app/src/data/helpers.js
  • schedule-app/src/pages/ContactDetailPage.jsx
  • schedule-app/src/pages/ContactsPage.jsx
  • schedule-app/src/pages/GoalsPage.jsx
  • schedule-app/src/pages/MapPage.jsx
  • schedule-app/src/pages/PlannerPage.jsx
  • schedule-app/src/styles.css
📜 Review details
⏰ Context from checks skipped due to timeout. (1)
  • GitHub Check: Greptile Review
🧰 Additional context used
📓 Path-based instructions (15)
**/*.{js,ts,jsx,tsx,py,java,cs,go,rb,php,scala,kt}

📄 CodeRabbit inference engine (.cursor/rules/common-coding-style.md)

**/*.{js,ts,jsx,tsx,py,java,cs,go,rb,php,scala,kt}: Always create new objects, never mutate existing ones. Use immutable patterns to prevent hidden side effects and enable safe concurrency
Organize code into many small files (200-400 lines typical, 800 lines max) organized by feature/domain rather than by type
Always handle errors explicitly at every level and never silently swallow errors
Always validate all user input before processing at system boundaries
Use schema-based validation where available
Fail fast with clear error messages when validation fails
Never trust external data (API responses, user input, file content)
Ensure code is readable and well-named
Keep functions small (less than 50 lines)
Keep files focused (less than 800 lines)
Avoid deep nesting (more than 4 levels)
Do not use hardcoded values; use constants or configuration instead

Files:

  • schedule-app/src/components/MiniMapPicker.jsx
  • schedule-app/src/components/EditorSheet.jsx
  • schedule-app/src/components/Select.jsx
  • schedule-app/src/pages/MapPage.jsx
  • schedule-app/src/pages/ContactDetailPage.jsx
  • schedule-app/src/pages/ContactsPage.jsx
  • schedule-app/src/pages/GoalsPage.jsx
  • schedule-app/src/pages/PlannerPage.jsx
  • schedule-app/src/data/helpers.js
**/{src,app}/**/*.{jsx,tsx,vue,html}

📄 CodeRabbit inference engine (.cursor/rules/common-coding-style.md)

Provide user-friendly error messages in UI-facing code

Files:

  • schedule-app/src/components/MiniMapPicker.jsx
  • schedule-app/src/components/EditorSheet.jsx
  • schedule-app/src/components/Select.jsx
  • schedule-app/src/pages/MapPage.jsx
  • schedule-app/src/pages/ContactDetailPage.jsx
  • schedule-app/src/pages/ContactsPage.jsx
  • schedule-app/src/pages/GoalsPage.jsx
  • schedule-app/src/pages/PlannerPage.jsx
**/*.{js,ts,jsx,tsx,py,java,cs,rb,go,php,swift,kt,rs,c,cpp,h,hpp}

📄 CodeRabbit inference engine (.cursor/rules/common-security.md)

No hardcoded secrets (API keys, passwords, tokens) - validate before any commit

Files:

  • schedule-app/src/components/MiniMapPicker.jsx
  • schedule-app/src/components/EditorSheet.jsx
  • schedule-app/src/components/Select.jsx
  • schedule-app/src/pages/MapPage.jsx
  • schedule-app/src/pages/ContactDetailPage.jsx
  • schedule-app/src/pages/ContactsPage.jsx
  • schedule-app/src/pages/GoalsPage.jsx
  • schedule-app/src/pages/PlannerPage.jsx
  • schedule-app/src/data/helpers.js
**/*.{js,ts,jsx,tsx,py,java,cs,rb,go,php}

📄 CodeRabbit inference engine (.cursor/rules/common-security.md)

**/*.{js,ts,jsx,tsx,py,java,cs,rb,go,php}: All user inputs must be validated
Enable CSRF protection on all state-changing endpoints
Verify authentication and authorization for all protected endpoints
Implement rate limiting on all endpoints to prevent abuse
Ensure error messages do not leak sensitive data in responses

Files:

  • schedule-app/src/components/MiniMapPicker.jsx
  • schedule-app/src/components/EditorSheet.jsx
  • schedule-app/src/components/Select.jsx
  • schedule-app/src/pages/MapPage.jsx
  • schedule-app/src/pages/ContactDetailPage.jsx
  • schedule-app/src/pages/ContactsPage.jsx
  • schedule-app/src/pages/GoalsPage.jsx
  • schedule-app/src/pages/PlannerPage.jsx
  • schedule-app/src/data/helpers.js
**/*.{js,ts,jsx,tsx,py,java,cs,rb,go,php,sql}

📄 CodeRabbit inference engine (.cursor/rules/common-security.md)

Use parameterized queries to prevent SQL injection

Files:

  • schedule-app/src/components/MiniMapPicker.jsx
  • schedule-app/src/components/EditorSheet.jsx
  • schedule-app/src/components/Select.jsx
  • schedule-app/src/pages/MapPage.jsx
  • schedule-app/src/pages/ContactDetailPage.jsx
  • schedule-app/src/pages/ContactsPage.jsx
  • schedule-app/src/pages/GoalsPage.jsx
  • schedule-app/src/pages/PlannerPage.jsx
  • schedule-app/src/data/helpers.js
**/*.{js,ts,jsx,tsx,html,php,java,cs,rb,go}

📄 CodeRabbit inference engine (.cursor/rules/common-security.md)

Implement XSS prevention by sanitizing HTML output

Files:

  • schedule-app/src/components/MiniMapPicker.jsx
  • schedule-app/src/components/EditorSheet.jsx
  • schedule-app/src/components/Select.jsx
  • schedule-app/src/pages/MapPage.jsx
  • schedule-app/src/pages/ContactDetailPage.jsx
  • schedule-app/src/pages/ContactsPage.jsx
  • schedule-app/src/pages/GoalsPage.jsx
  • schedule-app/src/pages/PlannerPage.jsx
  • schedule-app/src/data/helpers.js
**/*.{js,ts,jsx,tsx,py,java,cs,rb,go,php,swift,kt,rs,c,cpp,h,hpp,properties,yml,yaml,json,env,config}

📄 CodeRabbit inference engine (.cursor/rules/common-security.md)

NEVER hardcode secrets in source code - ALWAYS use environment variables or a secret manager

Files:

  • schedule-app/src/components/MiniMapPicker.jsx
  • schedule-app/src/components/EditorSheet.jsx
  • schedule-app/src/components/Select.jsx
  • schedule-app/src/pages/MapPage.jsx
  • schedule-app/src/pages/ContactDetailPage.jsx
  • schedule-app/src/pages/ContactsPage.jsx
  • schedule-app/src/pages/GoalsPage.jsx
  • schedule-app/src/pages/PlannerPage.jsx
  • schedule-app/src/data/helpers.js
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursor/rules/typescript-coding-style.md)

**/*.{ts,tsx,js,jsx}: Use spread operator for immutable updates in TypeScript/JavaScript instead of direct mutation
Use async/await with try-catch for error handling in TypeScript/JavaScript
Use Zod for schema-based input validation in TypeScript/JavaScript
No console.log statements in production code; use proper logging libraries instead

**/*.{ts,tsx,js,jsx}: Auto-format JavaScript/TypeScript files using Prettier after edit
Warn about console.log statements in edited files
Check all modified files for console.log statements before session ends

**/*.{ts,tsx,js,jsx}: Use the ApiResponse interface pattern with generic type parameter: interface ApiResponse<T> { success: boolean; data?: T; error?: string; meta?: { total: number; page: number; limit: number; } }
Implement custom React hooks following the pattern: export a named function with use prefix, generic type parameters, and proper useEffect cleanup for side effects

**/*.{ts,tsx,js,jsx}: Never hardcode secrets; always use environment variables for sensitive credentials like API keys
Throw an error when required environment variables are not configured to fail fast and ensure security prerequisites are met

Use Playwright as the E2E testing framework for critical user flows in TypeScript/JavaScript

Files:

  • schedule-app/src/components/MiniMapPicker.jsx
  • schedule-app/src/components/EditorSheet.jsx
  • schedule-app/src/components/Select.jsx
  • schedule-app/src/pages/MapPage.jsx
  • schedule-app/src/pages/ContactDetailPage.jsx
  • schedule-app/src/pages/ContactsPage.jsx
  • schedule-app/src/pages/GoalsPage.jsx
  • schedule-app/src/pages/PlannerPage.jsx
  • schedule-app/src/data/helpers.js
{**/*.tsx,**/*.jsx,**/components/**}

📄 CodeRabbit inference engine (CLAUDE.md)

When working on React files (*.tsx, *.jsx, components/**), use react-patterns and react-testing skills. Invoke /react-review, /react-build, /react-test commands for React-specific work.

Files:

  • schedule-app/src/components/MiniMapPicker.jsx
  • schedule-app/src/components/EditorSheet.jsx
  • schedule-app/src/components/Select.jsx
  • schedule-app/src/pages/MapPage.jsx
  • schedule-app/src/pages/ContactDetailPage.jsx
  • schedule-app/src/pages/ContactsPage.jsx
  • schedule-app/src/pages/GoalsPage.jsx
  • schedule-app/src/pages/PlannerPage.jsx
**/*.{js,ts,jsx,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{js,ts,jsx,tsx}: Always create new objects and never mutate in place; return new copies instead
Keep files between 200–400 lines typical, with a maximum of 800 lines
Extract helpers when a file exceeds 200 lines
Handle errors explicitly at every level; never swallow errors silently
Validate all user input before processing; use schema-based validation where available
Never trust external data (API responses, file content, query params); always validate
All user inputs must be validated and sanitized
Error messages must be scrubbed of sensitive internals
Use readable, well-named identifiers in all code
Keep functions under 50 lines
Keep files under 800 lines
Avoid nesting deeper than 4 levels
Implement comprehensive error handling in all code
Do not hardcode values; use constants or environment configuration instead
Do not use in-place mutation; always return new objects or state

Files:

  • schedule-app/src/components/MiniMapPicker.jsx
  • schedule-app/src/components/EditorSheet.jsx
  • schedule-app/src/components/Select.jsx
  • schedule-app/src/pages/MapPage.jsx
  • schedule-app/src/pages/ContactDetailPage.jsx
  • schedule-app/src/pages/ContactsPage.jsx
  • schedule-app/src/pages/GoalsPage.jsx
  • schedule-app/src/pages/PlannerPage.jsx
  • schedule-app/src/data/helpers.js
**/*.{js,ts,jsx,tsx,json,env*}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Do not hardcode secrets, API keys, passwords, or tokens

Files:

  • schedule-app/src/components/MiniMapPicker.jsx
  • schedule-app/src/components/EditorSheet.jsx
  • schedule-app/src/components/Select.jsx
  • schedule-app/src/pages/MapPage.jsx
  • schedule-app/src/pages/ContactDetailPage.jsx
  • schedule-app/src/pages/ContactsPage.jsx
  • schedule-app/src/pages/GoalsPage.jsx
  • schedule-app/src/pages/PlannerPage.jsx
  • schedule-app/src/data/helpers.js
**/*.{jsx,tsx,js,ts}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

HTML output must be sanitized where applicable

Files:

  • schedule-app/src/components/MiniMapPicker.jsx
  • schedule-app/src/components/EditorSheet.jsx
  • schedule-app/src/components/Select.jsx
  • schedule-app/src/pages/MapPage.jsx
  • schedule-app/src/pages/ContactDetailPage.jsx
  • schedule-app/src/pages/ContactsPage.jsx
  • schedule-app/src/pages/GoalsPage.jsx
  • schedule-app/src/pages/PlannerPage.jsx
  • schedule-app/src/data/helpers.js
**/{server,backend,api,src}/**/*.{ts,tsx,js,py,java,cs,go,rb,php,scala,kt}

📄 CodeRabbit inference engine (.cursor/rules/common-coding-style.md)

Log detailed error context on the server side

Files:

  • schedule-app/src/data/helpers.js
**/*.{js,ts}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{js,ts}: Use parameterized queries for all database writes (no string interpolation)
Auth/authz must be checked server-side for every sensitive path
Rate limiting must be applied to all public endpoints

Files:

  • schedule-app/src/data/helpers.js
**/*.{js,ts,env*}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Required environment variables must be validated at startup

Files:

  • schedule-app/src/data/helpers.js
🪛 ast-grep (0.44.1)
schedule-app/src/components/EditorSheet.jsx

[warning] 66-66: Avoid using the initial state variable in setState
Context: setDragY(dy)
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(setstate-same-var)

schedule-app/src/pages/MapPage.jsx

[warning] 85-92: Avoid using the initial state variable in setState
Context: setTimeout(() => {
if (!pressRef.current) return;
pressRef.current.fired = true;
suppressClickRef.current = true;
setSelectedId(null);
setTempPin({ lat, lng });
confirmTick();
}, LONG_PRESS_MS)
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(setstate-same-var)

schedule-app/src/pages/ContactDetailPage.jsx

[warning] 74-74: Avoid using the initial state variable in setState
Context: setEditing(d)
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(setstate-same-var)

schedule-app/src/pages/ContactsPage.jsx

[warning] 81-81: Avoid using the initial state variable in setState
Context: setAdding(d)
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(setstate-same-var)

schedule-app/src/pages/GoalsPage.jsx

[warning] 71-71: Avoid using the initial state variable in setState
Context: setEditing(d)
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(setstate-same-var)


[warning] 76-76: Avoid using the initial state variable in setState
Context: setEditing(d)
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(setstate-same-var)

schedule-app/src/pages/PlannerPage.jsx

[warning] 99-99: Avoid using the initial state variable in setState
Context: setViewing(occ)
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(setstate-same-var)


[warning] 194-194: Avoid using the initial state variable in setState
Context: setMembership(master.doneDates, occ.recDate, nextDone)
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(setstate-same-var)


[warning] 275-275: Avoid using the initial state variable in setState
Context: setCursor(iso)
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(setstate-same-var)


[warning] 445-451: Avoid using the initial state variable in setState
Context: setTimeout(() => {
if (gestureRef.current === g && g.phase === 'pending') {
g.phase = 'armed';
setArmedKey(key);
confirmTick();
}
}, LONG_PRESS_MS)
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(setstate-same-var)


[warning] 448-448: Avoid using the initial state variable in setState
Context: setArmedKey(key)
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(setstate-same-var)


[warning] 466-466: Avoid using the initial state variable in setState
Context: setDragDy(dy)
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(setstate-same-var)


[warning] 530-530: A list component should have a key to prevent re-rendering
Context: ⠿⠿
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(list-component-needs-key)


[warning] 531-531: A list component should have a key to prevent re-rendering
Context: <span className={select-dot${isSel ? ' select-dot--on' : ''}} />
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(list-component-needs-key)


[warning] 533-539: A list component should have a key to prevent re-rendering
Context:

{isArmed ? formatTime(minutesToTime(clampStart(ev, dragDy))) : formatTime(ev.start)}
{' '}
{ev.title || 'Untitled'}
{recurring && {ev.isException ? '✎' : '↻'}}

Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(list-component-needs-key)


[warning] 534-536: A list component should have a key to prevent re-rendering
Context:
{isArmed ? formatTime(minutesToTime(clampStart(ev, dragDy))) : formatTime(ev.start)}

Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(list-component-needs-key)


[warning] 538-538: A list component should have a key to prevent re-rendering
Context: {ev.isException ? '✎' : '↻'}
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(list-component-needs-key)


[warning] 544-544: A list component should have a key to prevent re-rendering
Context: {ev.isException ? '✎' : '↻'}
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(list-component-needs-key)


[warning] 604-604: A list component should have a key to prevent re-rendering
Context: <span className={select-dot${isSel ? ' select-dot--on' : ''}} />
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(list-component-needs-key)


[warning] 605-605: A list component should have a key to prevent re-rendering
Context: {formatTime(ev.start)}
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(list-component-needs-key)


[warning] 645-645: A list component should have a key to prevent re-rendering
Context: {d.getDate()}
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(list-component-needs-key)


[warning] 650-650: A list component should have a key to prevent re-rendering
Context: +{dayEvents.length - 3}
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(list-component-needs-key)


[warning] 796-796: Avoid using the initial state variable in setState
Context: setDraft(d)
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(setstate-same-var)


[warning] 629-631: Do not use array indexes for a list component's key
Context: WEEKDAY_LETTERS.map((l, i) => (
{l}
))
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(list-component-no-index)


[warning] 647-649: Do not use array indexes for a list component's key
Context: dayEvents.slice(0, 3).map((ev, i) => (
<span key={i} className="month-dot" style={{ background: ev.color || 'var(--accent)' }} />
))
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(list-component-no-index)


[error] 796-796: React's useState should not be directly called
Context: setDraft(d)
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(usestate-direct-usage)


[error] 797-797: React's useState should not be directly called
Context: setInitialJson(JSON.stringify(d))
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(usestate-direct-usage)


[error] 798-798: React's useState should not be directly called
Context: setShowMap(d.locLat != null)
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(usestate-direct-usage)


[warning] 994-1003: Do not use array indexes for a list component's key
Context: WEEKDAY_LETTERS.map((l, i) => (
<button
key={i}
type="button"
className={weekday-btn${(draft.repeatDays || []).includes(i) ? ' weekday-btn--on' : ''}}
onClick={() => toggleWeekday(i)}
>
{l}

))
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(list-component-no-index)

🪛 Stylelint (17.14.0)
schedule-app/src/styles.css

[error] 815-815: Expected keyframe name "sheetUp" to be kebab-case (keyframes-name-pattern)

(keyframes-name-pattern)


[error] 875-875: Expected keyframe name "savePop" to be kebab-case (keyframes-name-pattern)

(keyframes-name-pattern)

🔇 Additional comments (10)
schedule-app/src/data/helpers.js (3)

252-275: 📐 Maintainability & Code Quality | ⚡ Quick win

Duplicated recurrence logic gets a third identical branch.

The occursOn/matchesRule switch duplication was already flagged in a prior review. The new 'custom' case (Line 270-271, Line 303-304) was added identically to both instead of being consolidated, so the drift risk this comment originally warned about is now realized — three cases duplicated instead of two.

♻️ Extract shared rule test (same fix as prior comment, now also covers 'custom')
-export function occursOn(event, iso) {
-  const repeat = event.repeat || 'none';
-  if (repeat === 'none') return event.date === iso;
-  if (iso < event.date) return false;
-  if (event.repeatUntil && iso > event.repeatUntil) return false;
-  if ((event.skipDates || []).includes(iso)) return false;
-  const start = fromISODate(event.date);
-  const day = fromISODate(iso);
-  const diff = Math.round((day - start) / 86400000);
-  switch (repeat) {
-    case 'daily':
-      return true;
-    case 'weekly':
-      return diff % 7 === 0;
-    case 'biweekly':
-      return diff % 14 === 0;
-    case 'monthly':
-      return start.getDate() === day.getDate();
-    case 'custom':
-      return (event.repeatDays || []).includes(day.getDay());
-    default:
-      return false;
-  }
-}
+export function matchesRule(event, iso) {
+  const repeat = event.repeat || 'none';
+  if (repeat === 'none') return event.date === iso;
+  if (iso < event.date) return false;
+  if (event.repeatUntil && iso > event.repeatUntil) return false;
+  const start = fromISODate(event.date);
+  const day = fromISODate(iso);
+  const diff = Math.round((day - start) / 86400000);
+  switch (repeat) {
+    case 'daily':
+      return true;
+    case 'weekly':
+      return diff % 7 === 0;
+    case 'biweekly':
+      return diff % 14 === 0;
+    case 'monthly':
+      return start.getDate() === day.getDate();
+    case 'custom':
+      return (event.repeatDays || []).includes(day.getDay());
+    default:
+      return false;
+  }
+}
+
+export function occursOn(event, iso) {
+  return matchesRule(event, iso) && !(event.skipDates || []).includes(iso);
+}

(Remove the now-duplicated matchesRule definition further down.)

Also applies to: 286-308


53-84: LGTM!

Also applies to: 137-156


225-248: LGTM!

schedule-app/src/styles.css (1)

183-188: LGTM!

Also applies to: 901-934, 1033-1112, 1143-1199, 1309-1377, 1935-1953

schedule-app/src/components/Select.jsx (1)

35-49: Trigger lacks aria-expanded; open sheet doesn't trap focus.

Keyboard users can Tab past the open option list into background content since these are plain buttons, not a native <dialog>. Also, aria-expanded={open} on the trigger would help screen readers reflect state.

♿ Proposed fix for `aria-expanded`
       <button
         type="button"
         className="select-trigger"
+        aria-expanded={open}
+        aria-haspopup="listbox"
         onClick={() => {
           if (suppressReopenRef.current) return;
           if (!disabled) setOpen(true);
schedule-app/src/components/EditorSheet.jsx (1)

64-68: 🎯 Functional Correctness | ⚡ Quick win

Fix visual freeze when dragging back up.

If the user drags the sheet down and then drags back up above the starting point, dy becomes negative or zero. Because of the if (dy > 0) check, dragY is not updated to 0, causing the sheet to visually freeze at the last positive value rather than cleanly returning to its fully open position.

Use Math.max(0, dy) to ensure it smoothly tracks back to 0 and stops.

🐛 Proposed fix
   const onPointerMove = (e) => {
     if (!dragging.current || startY.current == null) return;
     const dy = e.clientY - startY.current;
-    if (dy > 0) setDragY(dy);
+    setDragY(Math.max(0, dy));
   };
schedule-app/src/pages/PlannerPage.jsx (4)

200-224: Drag override still drops typeId/color/reminder — and also locLat/locLng.

save()'s single-occurrence path persists the full field set (ov = { ...pl.fields }, which includes typeId, color, reminder, locLat, locLng). This rebuilt override only carries title/start/end/contactId/location/notes/date, so dragging a customized occurrence silently reverts those keys to the master's values. The previously flagged loss of typeId/color/reminder remains, and the location pin (locLat/locLng) is dropped too.

Proposed fix
       overrides[occ.recDate] = {
         title: occ.title,
         start,
         end,
         contactId: occ.contactId,
         location: occ.location,
         notes: occ.notes,
+        locLat: occ.locLat ?? null,
+        locLng: occ.locLng ?? null,
+        typeId: occ.typeId,
+        color: occ.color,
+        reminder: occ.reminder,
         ...(occ.occDate !== occ.recDate ? { date: occ.occDate } : {}),
       };

54-85: LGTM!


227-256: LGTM!


1134-1172: LGTM!

Comment on lines +29 to +34
setTimeout(() => map.invalidateSize(), 60);
return () => {
map.remove();
mapRef.current = null;
};
}, []); // eslint-disable-line react-hooks/exhaustive-deps

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Clear the resize timer on unmount to prevent errors.

If this component unmounts before the 60ms timeout elapses, the cleanup function will destroy the map instance (map.remove()). The pending timeout will then attempt to call map.invalidateSize() on the removed instance, throwing a runtime error.

🔧 Proposed fix
-    setTimeout(() => map.invalidateSize(), 60);
+    const resizeTimer = setTimeout(() => map.invalidateSize(), 60);
     return () => {
+      clearTimeout(resizeTimer);
       map.remove();
       mapRef.current = null;
     };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
setTimeout(() => map.invalidateSize(), 60);
return () => {
map.remove();
mapRef.current = null;
};
}, []); // eslint-disable-line react-hooks/exhaustive-deps
const resizeTimer = setTimeout(() => map.invalidateSize(), 60);
return () => {
clearTimeout(resizeTimer);
map.remove();
mapRef.current = null;
};
}, []); // eslint-disable-line react-hooks/exhaustive-deps
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@schedule-app/src/components/MiniMapPicker.jsx` around lines 29 - 34, Clear
the 60ms resize timer created by setTimeout in the effect before removing the
map. Store the timer handle, then update the cleanup function to cancel it
before calling map.remove() and resetting mapRef.current.

Comment on lines +218 to +235
<EditorSheet
open={!!editing}
title={editing?.id ? 'Edit goal' : 'New goal'}
dirty={dirty}
onSave={saveGoal}
onDiscard={() => setEditing(null)}
danger={
editing?.id
? {
label: 'Delete goal',
onClick: () => {
actions.deleteGoal(editing.id);
setEditing(null);
},
}
: undefined
}
>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Use saveDisabled to prevent silent validation failures.

The coding guidelines mandate failing fast with clear UI feedback on validation failure. The current form save handlers (saveGoal, saveNew, saveEdit) silently abort using early returns when required primary fields (title/name) are empty. Explicitly disable the save button in EditorSheet to communicate this invalid state to the user.

  • schedule-app/src/pages/GoalsPage.jsx#L218-L235: Pass saveDisabled={!editing?.title?.trim()} to EditorSheet.
  • schedule-app/src/pages/ContactsPage.jsx#L222-L223: Pass saveDisabled={!adding?.name?.trim()} to EditorSheet.
  • schedule-app/src/pages/ContactDetailPage.jsx#L242-L255: Pass saveDisabled={!editing?.name?.trim()} to EditorSheet.
📍 Affects 3 files
  • schedule-app/src/pages/GoalsPage.jsx#L218-L235 (this comment)
  • schedule-app/src/pages/ContactsPage.jsx#L222-L223
  • schedule-app/src/pages/ContactDetailPage.jsx#L242-L255
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@schedule-app/src/pages/GoalsPage.jsx` around lines 218 - 235, Prevent saving
forms with empty required primary fields by passing the specified saveDisabled
expressions to each EditorSheet: schedule-app/src/pages/GoalsPage.jsx lines
218-235 use !editing?.title?.trim(), schedule-app/src/pages/ContactsPage.jsx
lines 222-223 use !adding?.name?.trim(), and
schedule-app/src/pages/ContactDetailPage.jsx lines 242-255 use
!editing?.name?.trim().

Source: Coding guidelines

Comment thread schedule-app/src/pages/MapPage.jsx
Comment thread schedule-app/src/pages/MapPage.jsx
Comment on lines +812 to +822
animation: sheetUp 0.26s cubic-bezier(0.2, 0.8, 0.2, 1);
will-change: transform;
}
@keyframes sheetUp {
from {
transform: translateY(100%);
}
to {
transform: translateY(0);
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Stylelint: sheetUp/savePop keyframes aren't kebab-case.

Both new keyframes trip keyframes-name-pattern. Rename and update their two usages.

🪛 Proposed fix
-  animation: sheetUp 0.26s cubic-bezier(0.2, 0.8, 0.2, 1);
+  animation: sheet-up 0.26s cubic-bezier(0.2, 0.8, 0.2, 1);
   will-change: transform;
 }
-@keyframes sheetUp {
+@keyframes sheet-up {
 .editor-sheet-save--pop {
-  animation: savePop 0.38s cubic-bezier(0.34, 1.56, 0.64, 1);
+  animation: save-pop 0.38s cubic-bezier(0.34, 1.56, 0.64, 1);
 }
-@keyframes savePop {
+@keyframes save-pop {

Also applies to: 872-886

🧰 Tools
🪛 Stylelint (17.14.0)

[error] 815-815: Expected keyframe name "sheetUp" to be kebab-case (keyframes-name-pattern)

(keyframes-name-pattern)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@schedule-app/src/styles.css` around lines 812 - 822, Rename the sheetUp and
savePop keyframes to kebab-case names, and update both corresponding animation
declarations to reference the renamed keyframes. Preserve the existing animation
behavior and timing.

Source: Linters/SAST tools

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
schedule-app/src/data/store.jsx (1)

38-40: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Do not silently swallow errors.

The catch block swallows the error without explicit handling or logging. As per coding guidelines, "Handle errors explicitly at every level; never swallow errors silently".

🔧 Proposed fix
-  } catch {
+  } catch (error) {
+    console.error("Failed to load state from localStorage:", error);
     return makeSeed();
   }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@schedule-app/src/data/store.jsx` around lines 38 - 40, Update the catch block
surrounding makeSeed() to handle the failure explicitly instead of silently
returning a seed; log or otherwise propagate the caught error according to the
project’s established error-handling pattern, while preserving the intended
fallback behavior only if appropriate.

Source: Coding guidelines

schedule-app/src/pages/PlannerPage.jsx (2)

231-249: 🗄️ Data Integrity & Integration | 🔴 Critical | ⚡ Quick win

Data loss when moving multiple occurrences of the same recurring event.

The loop sequentially reads the master event from state.events and dispatches an update. If the user selects multiple occurrences of the same recurring event (e.g., Monday and Tuesday occurrences of a daily event) and shifts them, the loop reads from state.events before the store has processed the first update. The second iteration uses a stale master object (missing Monday's override) and dispatches a new update that overwrites and destroys Monday's shift.

Accumulate the updates in a local map so each iteration builds upon the last before dispatching.

🔒️ Proposed fix
   const moveSelected = (dayOffset) => {
+    const nextEvents = new Map();
     for (const key of selected) {
       const [id, recDate] = key.split('|');
-      const master = state.events.find((e) => e.id === id);
+      const master = nextEvents.get(id) || state.events.find((e) => e.id === id);
       if (!master) continue;
       const newDate = toISODate(addDays(recDate, dayOffset));
       if ((master.repeat || 'none') === 'none') {
-        actions.updateEvent({ ...master, date: newDate });
+        nextEvents.set(id, { ...master, date: newDate });
       } else {
         const overrides = { ...(master.overrides || {}) };
         const existing = overrides[recDate] || {};
         overrides[recDate] = { ...existing, date: newDate };
-        actions.updateEvent({ ...master, overrides });
+        nextEvents.set(id, { ...master, overrides });
       }
     }
+    for (const ev of nextEvents.values()) {
+      actions.updateEvent(ev);
+    }
     confirmTick();
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@schedule-app/src/pages/PlannerPage.jsx` around lines 231 - 249, Update
moveSelected so recurring-event updates accumulate per event in a local map
before dispatching. For each selected occurrence, use the latest locally
accumulated master/overrides rather than rereading stale state.events, then
dispatch each final event update after the loop while preserving non-recurring
behavior and existing selection cleanup.

864-866: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Prevent end-time from overflowing past midnight.

If an event is created or edited to start late (e.g., "23:45") and the end time is left untouched or evaluates to earlier, adding 30 minutes pushes the total minutes to 1455. minutesToTime will serialize this as "24:15", which is an invalid value for <input type="time"> and breaks the date boundary.

💚 Proposed fix
   const doSave = () => {
     let end = draft.end;
-    if (timeToMinutes(end) <= timeToMinutes(draft.start)) end = minutesToTime(timeToMinutes(draft.start) + 30);
+    const startMins = timeToMinutes(draft.start);
+    if (timeToMinutes(end) <= startMins) {
+      end = minutesToTime(Math.min(1439, startMins + 30));
+    }
     onSave({
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@schedule-app/src/pages/PlannerPage.jsx` around lines 864 - 866, Update the
end-time fallback in doSave so adding 30 minutes to draft.start cannot produce a
value beyond the valid same-day time range or serialize as “24:xx”. Clamp the
fallback to the latest valid time before passing it to minutesToTime, while
preserving the existing behavior for valid end times.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@schedule-app/src/pages/ContactsPage.jsx`:
- Around line 88-94: Replace the direct window.history.replaceState call in
ContactsPage.jsx at lines 88-94 with the existing navigate function, preserving
the current pathname while replacing location state with an empty object. In
PlannerPage.jsx at lines 106-117, import and instantiate useNavigate, then make
the same navigate(location.pathname, { replace: true, state: {} }) change in the
quick-add effect.

In `@schedule-app/src/pages/HomePage.jsx`:
- Around line 99-101: Update addChecklistItem to assign each new checklist item
a unique stable id, and update the preview and editable checklist renders to use
item.id with the existing index as a fallback for legacy items without ids.
Ensure both key expressions reference the item identity rather than only the
array index.
- Around line 244-245: Replace the document.querySelector call in the “✅ New
task” button handler with a React useRef attached to the task-add input, and
focus the ref after closing the quick-add menu. Ensure the ref is declared in
HomePage and remains valid across markup or className changes.

In `@schedule-app/src/pages/PricingPage.jsx`:
- Around line 6-14: Define shared monthly and annual pricing constants near
FEATURES in PricingPage, then replace every repeated $4/mo and $40/yr literal in
the pricing display with those constants. Derive the annual savings text from
the same constants rather than hardcoding $8, updating the affected pricing
sections consistently.

---

Outside diff comments:
In `@schedule-app/src/data/store.jsx`:
- Around line 38-40: Update the catch block surrounding makeSeed() to handle the
failure explicitly instead of silently returning a seed; log or otherwise
propagate the caught error according to the project’s established error-handling
pattern, while preserving the intended fallback behavior only if appropriate.

In `@schedule-app/src/pages/PlannerPage.jsx`:
- Around line 231-249: Update moveSelected so recurring-event updates accumulate
per event in a local map before dispatching. For each selected occurrence, use
the latest locally accumulated master/overrides rather than rereading stale
state.events, then dispatch each final event update after the loop while
preserving non-recurring behavior and existing selection cleanup.
- Around line 864-866: Update the end-time fallback in doSave so adding 30
minutes to draft.start cannot produce a value beyond the valid same-day time
range or serialize as “24:xx”. Clamp the fallback to the latest valid time
before passing it to minutesToTime, while preserving the existing behavior for
valid end times.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 67f65abf-ee6f-451f-b6bf-5d5bacb4e4d6

📥 Commits

Reviewing files that changed from the base of the PR and between 297274c and 07f2351.

📒 Files selected for processing (9)
  • schedule-app/src/App.jsx
  • schedule-app/src/components/TabBar.jsx
  • schedule-app/src/data/seed.js
  • schedule-app/src/data/store.jsx
  • schedule-app/src/pages/ContactsPage.jsx
  • schedule-app/src/pages/HomePage.jsx
  • schedule-app/src/pages/PlannerPage.jsx
  • schedule-app/src/pages/PricingPage.jsx
  • schedule-app/src/styles.css
📜 Review details
⏰ Context from checks skipped due to timeout. (1)
  • GitHub Check: Greptile Review
🧰 Additional context used
📓 Path-based instructions (15)
**/*.{js,ts,jsx,tsx,py,java,cs,go,rb,php,scala,kt}

📄 CodeRabbit inference engine (.cursor/rules/common-coding-style.md)

**/*.{js,ts,jsx,tsx,py,java,cs,go,rb,php,scala,kt}: Always create new objects, never mutate existing ones. Use immutable patterns to prevent hidden side effects and enable safe concurrency
Organize code into many small files (200-400 lines typical, 800 lines max) organized by feature/domain rather than by type
Always handle errors explicitly at every level and never silently swallow errors
Always validate all user input before processing at system boundaries
Use schema-based validation where available
Fail fast with clear error messages when validation fails
Never trust external data (API responses, user input, file content)
Ensure code is readable and well-named
Keep functions small (less than 50 lines)
Keep files focused (less than 800 lines)
Avoid deep nesting (more than 4 levels)
Do not use hardcoded values; use constants or configuration instead

Files:

  • schedule-app/src/components/TabBar.jsx
  • schedule-app/src/pages/PricingPage.jsx
  • schedule-app/src/data/seed.js
  • schedule-app/src/pages/HomePage.jsx
  • schedule-app/src/App.jsx
  • schedule-app/src/data/store.jsx
  • schedule-app/src/pages/ContactsPage.jsx
  • schedule-app/src/pages/PlannerPage.jsx
**/{src,app}/**/*.{jsx,tsx,vue,html}

📄 CodeRabbit inference engine (.cursor/rules/common-coding-style.md)

Provide user-friendly error messages in UI-facing code

Files:

  • schedule-app/src/components/TabBar.jsx
  • schedule-app/src/pages/PricingPage.jsx
  • schedule-app/src/pages/HomePage.jsx
  • schedule-app/src/App.jsx
  • schedule-app/src/data/store.jsx
  • schedule-app/src/pages/ContactsPage.jsx
  • schedule-app/src/pages/PlannerPage.jsx
**/*.{js,ts,jsx,tsx,py,java,cs,rb,go,php,swift,kt,rs,c,cpp,h,hpp}

📄 CodeRabbit inference engine (.cursor/rules/common-security.md)

No hardcoded secrets (API keys, passwords, tokens) - validate before any commit

Files:

  • schedule-app/src/components/TabBar.jsx
  • schedule-app/src/pages/PricingPage.jsx
  • schedule-app/src/data/seed.js
  • schedule-app/src/pages/HomePage.jsx
  • schedule-app/src/App.jsx
  • schedule-app/src/data/store.jsx
  • schedule-app/src/pages/ContactsPage.jsx
  • schedule-app/src/pages/PlannerPage.jsx
**/*.{js,ts,jsx,tsx,py,java,cs,rb,go,php}

📄 CodeRabbit inference engine (.cursor/rules/common-security.md)

**/*.{js,ts,jsx,tsx,py,java,cs,rb,go,php}: All user inputs must be validated
Enable CSRF protection on all state-changing endpoints
Verify authentication and authorization for all protected endpoints
Implement rate limiting on all endpoints to prevent abuse
Ensure error messages do not leak sensitive data in responses

Files:

  • schedule-app/src/components/TabBar.jsx
  • schedule-app/src/pages/PricingPage.jsx
  • schedule-app/src/data/seed.js
  • schedule-app/src/pages/HomePage.jsx
  • schedule-app/src/App.jsx
  • schedule-app/src/data/store.jsx
  • schedule-app/src/pages/ContactsPage.jsx
  • schedule-app/src/pages/PlannerPage.jsx
**/*.{js,ts,jsx,tsx,py,java,cs,rb,go,php,sql}

📄 CodeRabbit inference engine (.cursor/rules/common-security.md)

Use parameterized queries to prevent SQL injection

Files:

  • schedule-app/src/components/TabBar.jsx
  • schedule-app/src/pages/PricingPage.jsx
  • schedule-app/src/data/seed.js
  • schedule-app/src/pages/HomePage.jsx
  • schedule-app/src/App.jsx
  • schedule-app/src/data/store.jsx
  • schedule-app/src/pages/ContactsPage.jsx
  • schedule-app/src/pages/PlannerPage.jsx
**/*.{js,ts,jsx,tsx,html,php,java,cs,rb,go}

📄 CodeRabbit inference engine (.cursor/rules/common-security.md)

Implement XSS prevention by sanitizing HTML output

Files:

  • schedule-app/src/components/TabBar.jsx
  • schedule-app/src/pages/PricingPage.jsx
  • schedule-app/src/data/seed.js
  • schedule-app/src/pages/HomePage.jsx
  • schedule-app/src/App.jsx
  • schedule-app/src/data/store.jsx
  • schedule-app/src/pages/ContactsPage.jsx
  • schedule-app/src/pages/PlannerPage.jsx
**/*.{js,ts,jsx,tsx,py,java,cs,rb,go,php,swift,kt,rs,c,cpp,h,hpp,properties,yml,yaml,json,env,config}

📄 CodeRabbit inference engine (.cursor/rules/common-security.md)

NEVER hardcode secrets in source code - ALWAYS use environment variables or a secret manager

Files:

  • schedule-app/src/components/TabBar.jsx
  • schedule-app/src/pages/PricingPage.jsx
  • schedule-app/src/data/seed.js
  • schedule-app/src/pages/HomePage.jsx
  • schedule-app/src/App.jsx
  • schedule-app/src/data/store.jsx
  • schedule-app/src/pages/ContactsPage.jsx
  • schedule-app/src/pages/PlannerPage.jsx
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursor/rules/typescript-coding-style.md)

**/*.{ts,tsx,js,jsx}: Use spread operator for immutable updates in TypeScript/JavaScript instead of direct mutation
Use async/await with try-catch for error handling in TypeScript/JavaScript
Use Zod for schema-based input validation in TypeScript/JavaScript
No console.log statements in production code; use proper logging libraries instead

**/*.{ts,tsx,js,jsx}: Auto-format JavaScript/TypeScript files using Prettier after edit
Warn about console.log statements in edited files
Check all modified files for console.log statements before session ends

**/*.{ts,tsx,js,jsx}: Use the ApiResponse interface pattern with generic type parameter: interface ApiResponse<T> { success: boolean; data?: T; error?: string; meta?: { total: number; page: number; limit: number; } }
Implement custom React hooks following the pattern: export a named function with use prefix, generic type parameters, and proper useEffect cleanup for side effects

**/*.{ts,tsx,js,jsx}: Never hardcode secrets; always use environment variables for sensitive credentials like API keys
Throw an error when required environment variables are not configured to fail fast and ensure security prerequisites are met

Use Playwright as the E2E testing framework for critical user flows in TypeScript/JavaScript

Files:

  • schedule-app/src/components/TabBar.jsx
  • schedule-app/src/pages/PricingPage.jsx
  • schedule-app/src/data/seed.js
  • schedule-app/src/pages/HomePage.jsx
  • schedule-app/src/App.jsx
  • schedule-app/src/data/store.jsx
  • schedule-app/src/pages/ContactsPage.jsx
  • schedule-app/src/pages/PlannerPage.jsx
{**/*.tsx,**/*.jsx,**/components/**}

📄 CodeRabbit inference engine (CLAUDE.md)

When working on React files (*.tsx, *.jsx, components/**), use react-patterns and react-testing skills. Invoke /react-review, /react-build, /react-test commands for React-specific work.

Files:

  • schedule-app/src/components/TabBar.jsx
  • schedule-app/src/pages/PricingPage.jsx
  • schedule-app/src/pages/HomePage.jsx
  • schedule-app/src/App.jsx
  • schedule-app/src/data/store.jsx
  • schedule-app/src/pages/ContactsPage.jsx
  • schedule-app/src/pages/PlannerPage.jsx
**/*.{js,ts,jsx,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{js,ts,jsx,tsx}: Always create new objects and never mutate in place; return new copies instead
Keep files between 200–400 lines typical, with a maximum of 800 lines
Extract helpers when a file exceeds 200 lines
Handle errors explicitly at every level; never swallow errors silently
Validate all user input before processing; use schema-based validation where available
Never trust external data (API responses, file content, query params); always validate
All user inputs must be validated and sanitized
Error messages must be scrubbed of sensitive internals
Use readable, well-named identifiers in all code
Keep functions under 50 lines
Keep files under 800 lines
Avoid nesting deeper than 4 levels
Implement comprehensive error handling in all code
Do not hardcode values; use constants or environment configuration instead
Do not use in-place mutation; always return new objects or state

Files:

  • schedule-app/src/components/TabBar.jsx
  • schedule-app/src/pages/PricingPage.jsx
  • schedule-app/src/data/seed.js
  • schedule-app/src/pages/HomePage.jsx
  • schedule-app/src/App.jsx
  • schedule-app/src/data/store.jsx
  • schedule-app/src/pages/ContactsPage.jsx
  • schedule-app/src/pages/PlannerPage.jsx
**/*.{js,ts,jsx,tsx,json,env*}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Do not hardcode secrets, API keys, passwords, or tokens

Files:

  • schedule-app/src/components/TabBar.jsx
  • schedule-app/src/pages/PricingPage.jsx
  • schedule-app/src/data/seed.js
  • schedule-app/src/pages/HomePage.jsx
  • schedule-app/src/App.jsx
  • schedule-app/src/data/store.jsx
  • schedule-app/src/pages/ContactsPage.jsx
  • schedule-app/src/pages/PlannerPage.jsx
**/*.{jsx,tsx,js,ts}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

HTML output must be sanitized where applicable

Files:

  • schedule-app/src/components/TabBar.jsx
  • schedule-app/src/pages/PricingPage.jsx
  • schedule-app/src/data/seed.js
  • schedule-app/src/pages/HomePage.jsx
  • schedule-app/src/App.jsx
  • schedule-app/src/data/store.jsx
  • schedule-app/src/pages/ContactsPage.jsx
  • schedule-app/src/pages/PlannerPage.jsx
**/{server,backend,api,src}/**/*.{ts,tsx,js,py,java,cs,go,rb,php,scala,kt}

📄 CodeRabbit inference engine (.cursor/rules/common-coding-style.md)

Log detailed error context on the server side

Files:

  • schedule-app/src/data/seed.js
**/*.{js,ts}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{js,ts}: Use parameterized queries for all database writes (no string interpolation)
Auth/authz must be checked server-side for every sensitive path
Rate limiting must be applied to all public endpoints

Files:

  • schedule-app/src/data/seed.js
**/*.{js,ts,env*}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Required environment variables must be validated at startup

Files:

  • schedule-app/src/data/seed.js
🪛 ast-grep (0.44.1)
schedule-app/src/pages/PricingPage.jsx

[warning] 62-62: A list component should have a key to prevent re-rendering
Context: {f.label}
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(list-component-needs-key)


[warning] 63-63: A list component should have a key to prevent re-rendering
Context: {f.free ? '✓' : '—'}
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(list-component-needs-key)


[warning] 64-64: A list component should have a key to prevent re-rendering
Context: {f.pro ? '✓' : '—'}
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(list-component-needs-key)

schedule-app/src/pages/HomePage.jsx

[warning] 210-215: Do not use array indexes for a list component's key
Context: n.checklist.slice(0, 5).map((item, i) => (
<li key={i} className={item.done ? 'note-check--done' : ''}>
{item.done ? '✓' : ''}
{item.text || 'Item'}

))
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(list-component-no-index)


[warning] 134-144: A list component should have a key to prevent re-rendering
Context: <button
className="reminder-row"
onClick={() => {
if (r.kind === 'goal') navigate('/goals');
else if (r.kind === 'event') navigate('/planner');
}}
>
<span className={reminder-kind reminder-kind--${r.kind}}>{r.kind}
{r.label}
{r.time && {formatTime(r.time)}}

Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(list-component-needs-key)


[warning] 141-141: A list component should have a key to prevent re-rendering
Context: <span className={reminder-kind reminder-kind--${r.kind}}>{r.kind}
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(list-component-needs-key)


[warning] 142-142: A list component should have a key to prevent re-rendering
Context: {r.label}
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(list-component-needs-key)


[warning] 143-143: A list component should have a key to prevent re-rendering
Context: {formatTime(r.time)}
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(list-component-needs-key)


[warning] 156-165: A list component should have a key to prevent re-rendering
Context: <button
className={task-check${t.done ? ' task-check--on' : ''}}
onClick={() => {
actions.updateTask({ ...t, done: !t.done });
if (!t.done) confirmTick();
}}
aria-label={t.done ? 'Mark not done' : 'Mark done'}
>
{t.done && }

Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(list-component-needs-key)


[warning] 164-164: A list component should have a key to prevent re-rendering
Context:
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(list-component-needs-key)


[warning] 166-166: A list component should have a key to prevent re-rendering
Context: <span className={task-title${t.done ? ' task-title--done' : ''}}>{t.title}
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(list-component-needs-key)


[warning] 167-167: A list component should have a key to prevent re-rendering
Context: {formatTime(t.reminder.time)}
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(list-component-needs-key)


[warning] 168-170: A list component should have a key to prevent re-rendering
Context: <button className="icon-btn task-del" onClick={() => actions.deleteTask(t.id)} aria-label="Delete task">


Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(list-component-needs-key)


[warning] 206-206: A list component should have a key to prevent re-rendering
Context: 📌
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(list-component-needs-key)


[warning] 207-207: A list component should have a key to prevent re-rendering
Context: {n.title}
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(list-component-needs-key)


[warning] 212-212: A list component should have a key to prevent re-rendering
Context: {item.done ? '✓' : ''}
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(list-component-needs-key)


[warning] 218-218: A list component should have a key to prevent re-rendering
Context:

{n.body}


Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(list-component-needs-key)


[warning] 291-301: A list component should have a key to prevent re-rendering
Context: <button
type="button"
className={task-check${item.done ? ' task-check--on' : ''}}
onClick={() => {
const next = editingNote.checklist.slice();
next[i] = { ...next[i], done: !next[i].done };
setEditingNote({ ...editingNote, checklist: next });
}}
>
{item.done && }

Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(list-component-needs-key)


[warning] 300-300: A list component should have a key to prevent re-rendering
Context:
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(list-component-needs-key)


[warning] 302-310: A list component should have a key to prevent re-rendering
Context: <input
value={item.text}
onChange={(e) => {
const next = editingNote.checklist.slice();
next[i] = { ...next[i], text: e.target.value };
setEditingNote({ ...editingNote, checklist: next });
}}
placeholder="List item"
/>
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(list-component-needs-key)


[warning] 311-321: A list component should have a key to prevent re-rendering
Context: <button
type="button"
className="icon-btn"
onClick={() => {
const next = editingNote.checklist.filter((_, idx) => idx !== i);
setEditingNote({ ...editingNote, checklist: next });
}}
aria-label="Remove item"
>


Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(list-component-needs-key)


[warning] 77-77: Avoid using the initial state variable in setState
Context: setEditingNote(d)
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(setstate-same-var)

🔇 Additional comments (10)
schedule-app/src/data/store.jsx (2)

150-151: IMPORT_DATA trusts imported JSON unvalidated and skips loadState's migration.

{ ...makeSeed(), ...action.data } merges the imported file straight into state with no schema check, and — unlike loadState (lines 9-37) — doesn't run the goal period/progress migration or eventTypes/settings defaulting. Importing an older export (or a hand-edited/corrupted file) can silently produce malformed data that the rest of the app assumes is well-shaped.

Extract loadState's merge/migration block into a shared normalizeState(parsed) used by both loadState and IMPORT_DATA, and validate action.data's top-level shape with Zod before merging. As per coding guidelines, "Never trust external data" and "Use Zod for schema-based input validation in TypeScript/JavaScript".

Source: Coding guidelines


117-132: LGTM!

Also applies to: 161-162, 232-244

schedule-app/src/components/TabBar.jsx (1)

21-21: Remove redundant haptic listener to prevent double vibration.

The PR introduces an app-wide delegated pointerdown listener in App.jsx that automatically triggers haptic feedback for all <a> elements (which NavLink renders). Leaving onClick={tapTick} here causes the feedback to fire twice on touch devices (once on pointer-down, once on click), creating a jarring double-vibration UX.

🔧 Proposed fix
-          onClick={tapTick}
schedule-app/src/App.jsx (1)

42-45: Consider ignoring elements with aria-disabled="true".

The delegated haptic listener correctly ignores elements with the native disabled property, but links (<a>) and custom ARIA controls ([role="button"]) use the aria-disabled="true" attribute when disabled. Adding a check for this attribute ensures disabled UI components don't erroneously trigger tactile feedback.

💡 Proposed fix
       const el = e.target.closest?.(
         'button, a, [role="button"], [role="switch"], input[type="checkbox"], input[type="radio"]'
       );
-      if (!el || el.disabled) return;
+      if (!el || el.disabled || el.getAttribute('aria-disabled') === 'true') return;
schedule-app/src/data/seed.js (1)

232-274: LGTM!

schedule-app/src/styles.css (1)

1994-2299: LGTM!

schedule-app/src/pages/HomePage.jsx (1)

1-30: LGTM!

Also applies to: 34-98, 106-209, 225-243, 246-401

schedule-app/src/pages/PricingPage.jsx (1)

1-40: LGTM!

Also applies to: 50-79, 83-92

schedule-app/src/pages/ContactsPage.jsx (1)

112-112: 🎯 Functional Correctness | ⚡ Quick win

createdAt uses UTC while the rest of the app uses local dates.

new Date().toISOString().slice(0,10) is UTC-based, but daysSince/todayISO operate on local dates. Near midnight in negative-offset timezones this stores tomorrow's date, skewing the overdue calculation by a day. todayISO is already imported.

Proposed fix
-      createdAt: new Date().toISOString().slice(0, 10),
+      createdAt: todayISO(),
schedule-app/src/pages/PlannerPage.jsx (1)

216-227: 🗄️ Data Integrity & Integration | ⚡ Quick win

Drag-to-reschedule silently drops per-occurrence typeId, color, and reminder.

The override written here only carries title/start/end/contactId/location/notes/date. But save()'s single-occurrence path (ov = { ...draft.fields }) stores typeId, color, and reminder in the override too. So if a user customizes one occurrence's color/type/reminder and later drags it, this rebuilt override omits those keys and the occurrence reverts to the master's values — silent data loss.

Proposed fix
       overrides[occ.recDate] = {
         title: occ.title,
         start,
         end,
         contactId: occ.contactId,
         location: occ.location,
         notes: occ.notes,
+        typeId: occ.typeId,
+        color: occ.color,
+        reminder: occ.reminder,
         ...(occ.occDate !== occ.recDate ? { date: occ.occDate } : {}),
       };

Comment on lines +88 to +94
// Opened from the Home page's quick-add menu.
useEffect(() => {
if (location.state?.quickNewContact) {
startAdd();
window.history.replaceState({}, '');
}
}, []); // eslint-disable-line react-hooks/exhaustive-deps

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Replace window.history.replaceState with React Router's navigate. Bypassing React Router to mutate history state leaves the router's internal location.state representation out of sync. This can cause phantom re-triggers if the component re-renders or the user navigates back.

  • schedule-app/src/pages/ContactsPage.jsx#L88-L94: Replace window.history.replaceState({}, '') with navigate(location.pathname, { replace: true, state: {} }) (using the already-instantiated navigate).
  • schedule-app/src/pages/PlannerPage.jsx#L106-L117: Import and instantiate useNavigate, then replace window.history.replaceState({}, '') with navigate(location.pathname, { replace: true, state: {} }).
📍 Affects 2 files
  • schedule-app/src/pages/ContactsPage.jsx#L88-L94 (this comment)
  • schedule-app/src/pages/PlannerPage.jsx#L106-L117
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@schedule-app/src/pages/ContactsPage.jsx` around lines 88 - 94, Replace the
direct window.history.replaceState call in ContactsPage.jsx at lines 88-94 with
the existing navigate function, preserving the current pathname while replacing
location state with an empty object. In PlannerPage.jsx at lines 106-117, import
and instantiate useNavigate, then make the same navigate(location.pathname, {
replace: true, state: {} }) change in the quick-add effect.

Comment on lines +99 to +101
const addChecklistItem = () => {
setEditingNote((n) => ({ ...n, checklist: [...(n.checklist || []), { text: '', done: false }] }));
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Checklist items lack stable ids, so both renders key by array index.

addChecklistItem never assigns an id, so the preview list (Line 211) and the editable form (Line 291) both fall back to key={i}/key={index}. Once an item is deleted or reordered mid-list, React can reuse DOM nodes for the wrong item — most impactful in the editable form where users are actively typing (focus/cursor state can jump to the wrong row after a deletion).

🔧 Proposed fix
   const addChecklistItem = () => {
-    setEditingNote((n) => ({ ...n, checklist: [...(n.checklist || []), { text: '', done: false }] }));
+    setEditingNote((n) => ({
+      ...n,
+      checklist: [...(n.checklist || []), { id: crypto.randomUUID(), text: '', done: false }],
+    }));
   };

Then use item.id ?? i as the key at both render sites (or fall back for pre-existing notes without ids).

Also applies to: 210-217, 288-324

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@schedule-app/src/pages/HomePage.jsx` around lines 99 - 101, Update
addChecklistItem to assign each new checklist item a unique stable id, and
update the preview and editable checklist renders to use item.id with the
existing index as a fallback for legacy items without ids. Ensure both key
expressions reference the item identity rather than only the array index.

Source: Linters/SAST tools

Comment thread schedule-app/src/pages/HomePage.jsx Outdated
Comment on lines +244 to +245
<button className="select-option" onClick={() => { setQuickAddOpen(false); document.querySelector('.task-add-row input')?.focus(); }}>
✅ New task

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use a ref instead of document.querySelector to focus the task input.

Reaching into the DOM by CSS class ties this handler to a specific className string elsewhere in the file; a useRef on the input is more robust to markup changes and is the idiomatic React approach.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@schedule-app/src/pages/HomePage.jsx` around lines 244 - 245, Replace the
document.querySelector call in the “✅ New task” button handler with a React
useRef attached to the task-add input, and focus the ref after closing the
quick-add menu. Ensure the ref is declared in HomePage and remains valid across
markup or className changes.

Comment thread schedule-app/src/pages/PricingPage.jsx
Wraps up the premium/settings side of the feature request:

- 8 color themes (accent-only re-tint over the existing light/dark
  palette), applied via document.documentElement.dataset.scheme; only
  "Emerald" (default) is free, the other 7 are Pro-locked with a lock
  glyph and a tap-through to Pricing.
- Real premium gating: the People tab (list + detail) now shows an
  upgrade prompt instead of contacts when settings.isPro is false; color
  themes, cloud sync, Google sign-in, and .ics import/export are all
  gated the same way via a shared requirePro() helper.
- Real .ics calendar export/import (src/data/ics.js) — genuine
  client-side iCalendar generation/parsing, no backend needed, so it
  actually interoperates with Google/Apple/Outlook calendars. Recurring
  events export with RRULE where possible.
- A large "Upgrade to Stewardly Pro" bubble in More (swaps to an active
  state once Pro is on), plus a smaller crown bubble on Home — both link
  to the Pricing page.
- Donation button in More, below the Pro bubble — opens an honest dialog
  explaining no payment link is wired up yet rather than faking one.
- Local profile settings (name + photo, device-only) and honestly-labeled
  Account & Sync stubs (cloud sync toggle, "Sign in with Google") that
  say plainly they need a backend that doesn't exist in this build.
- A "Share event" action on the event detail view (Pro-gated) with the
  same honest "needs an account backend" messaging.
- Slightly more deliberate page-mount transition, applied to every page
  including the full-bleed Map.

README rewritten to describe every tab/feature added across this whole
round of work, plus a clear "what's real vs. demo" section for Pro.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DaB8NfLY8nsxGAFk7XbCtw
@ecc-tools

ecc-tools Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

ECC bundle files are already tracked in this repository. Skipping generation of another bundle PR.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
schedule-app/src/pages/PlannerPage.jsx (1)

231-249: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Data loss in bulk-shift when multiple occurrences of the same event are selected.

The moveSelected loop reads the master event from the state.events closure on every iteration. Because state.events doesn't update until the next render, shifting multiple occurrences of the same recurring event causes successive actions.updateEvent dispatches to overwrite the previous ones, resulting in only the last occurrence's shift being saved. Group the updates by event ID before dispatching to preserve all modifications.

Proposed fix
   // Multi-select: shift every selected occurrence by a fixed offset (days).
   const moveSelected = (dayOffset) => {
+    const masterUpdates = {};
     for (const key of selected) {
       const [id, recDate] = key.split('|');
-      const master = state.events.find((e) => e.id === id);
+      if (!masterUpdates[id]) {
+        const master = state.events.find((e) => e.id === id);
+        if (master) masterUpdates[id] = { ...master, overrides: { ...(master.overrides || {}) } };
+      }
+      const master = masterUpdates[id];
       if (!master) continue;
       const newDate = toISODate(addDays(recDate, dayOffset));
       if ((master.repeat || 'none') === 'none') {
-        actions.updateEvent({ ...master, date: newDate });
+        master.date = newDate;
       } else {
-        const overrides = { ...(master.overrides || {}) };
-        const existing = overrides[recDate] || {};
-        overrides[recDate] = { ...existing, date: newDate };
-        actions.updateEvent({ ...master, overrides });
+        master.overrides[recDate] = { ...(master.overrides[recDate] || {}), date: newDate };
       }
     }
+    Object.values(masterUpdates).forEach((ev) => actions.updateEvent(ev));
     confirmTick();
     setSelected(new Set());
     setSelectMode(false);
   };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@schedule-app/src/pages/PlannerPage.jsx` around lines 231 - 249, Update
moveSelected to group selected occurrences by event ID and build each recurring
event’s complete overrides set from a single master snapshot before dispatching
one actions.updateEvent call per event. Preserve non-recurring date updates, and
ensure all selected occurrences of the same recurring event remain in the final
overrides object.
schedule-app/src/pages/MorePage.jsx (1)

36-651: 📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift

MorePage is a single ~615-line function covering nine unrelated concerns.

Profile, account/sync, calendar import/export, appearance, notifications, reconnect cadence, statuses, event types, feedback, data tools, and six modals all live in one component body. This far exceeds the guideline thresholds and makes the file hard to reason about and test in isolation.

As per coding guidelines: "Keep functions under 50 lines," "Extract helpers when a file exceeds 200 lines," and "Organize code into many small files (200-400 lines typical, 800 lines max) organized by feature/domain rather than by type."

Consider extracting each detail-section (Profile, AccountSync, CalendarIO, Appearance, Notifications, Statuses/Types, Feedback, DataTools) and each modal into its own component/file under pages/more/, with MorePage composing them and passing down state/actions/requirePro.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@schedule-app/src/pages/MorePage.jsx` around lines 36 - 651, Refactor MorePage
into smaller feature components under pages/more/, extracting the Profile,
AccountSync, CalendarIO, Appearance, Notifications, reconnect cadence,
statuses/event types, feedback, and data tools sections along with their modals.
Keep MorePage focused on shared store/navigation state and composition, passing
state, actions, and requirePro to child components; preserve all existing
behavior and interactions.

Source: Path instructions

schedule-app/src/App.jsx (1)

23-24: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Avoid mutating refs during the render phase.

Writing to ref.current during rendering violates React's rules for pure functions and can cause unintended behavior in Concurrent Mode or Strict Mode. Sync the ref inside an effect to ensure the render phase remains pure.

♻️ Proposed fix
   const stateRef = useRef(state);
-  stateRef.current = state;
+  useEffect(() => {
+    stateRef.current = state;
+  }, [state]);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@schedule-app/src/App.jsx` around lines 23 - 24, Update the stateRef
synchronization near stateRef in App so ref.current is assigned inside a
useEffect that depends on state, removing the direct render-phase mutation while
preserving the ref’s current-state value after committed renders.
♻️ Duplicate comments (4)
schedule-app/src/pages/ContactsPage.jsx (2)

112-112: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

createdAt uses UTC while the rest of the app uses local dates.

new Date().toISOString().slice(0, 10) is UTC-based, but daysSince/todayISO operate on local dates. Near midnight in negative-offset timezones this stores tomorrow's date, skewing the overdue calculation by a day. Use the imported todayISO() helper instead.

Proposed fix
-      createdAt: new Date().toISOString().slice(0, 10),
+      createdAt: todayISO(),
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@schedule-app/src/pages/ContactsPage.jsx` at line 112, Replace the UTC-based
date expression assigned to createdAt in the contact creation flow with the
imported todayISO() helper, keeping the stored format consistent with daysSince
and the rest of the app’s local-date handling.

88-94: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Replace window.history.replaceState with React Router's navigate.

Bypassing React Router to mutate history state leaves the router's internal location.state representation out of sync. This can cause phantom re-triggers if the component re-renders or the user navigates back.

  • schedule-app/src/pages/ContactsPage.jsx#L88-L94: Replace window.history.replaceState({}, '') with navigate(location.pathname, { replace: true, state: {} }) (using the already-instantiated navigate).
  • schedule-app/src/pages/PlannerPage.jsx#L108-L117: Make the same navigate(location.pathname, { replace: true, state: {} }) replacements in both quick-add branches (using the already-instantiated navigate).
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@schedule-app/src/pages/ContactsPage.jsx` around lines 88 - 94, The quick-add
effects bypass React Router by calling window.history.replaceState. In
schedule-app/src/pages/ContactsPage.jsx lines 88-94, replace it with
navigate(location.pathname, { replace: true, state: {} }) using the existing
navigate; make the same replacement in both quick-add branches in
schedule-app/src/pages/PlannerPage.jsx lines 108-117, preserving the existing
startAdd behavior.
schedule-app/src/pages/PlannerPage.jsx (1)

216-227: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Drag-to-reschedule silently drops per-occurrence typeId, color, and reminder.

The override constructed here only preserves title/start/end/contactId/location/notes/date. If an occurrence previously had customized typeId, color, or reminder fields (which are populated by the single-occurrence save path), this object omits them, causing those fields to silently revert to the master event's values.

Proposed fix
       const overrides = { ...(master.overrides || {}) };
       overrides[occ.recDate] = {
         title: occ.title,
         start,
         end,
         contactId: occ.contactId,
         location: occ.location,
         notes: occ.notes,
+        typeId: occ.typeId,
+        color: occ.color,
+        reminder: occ.reminder,
         ...(occ.occDate !== occ.recDate ? { date: occ.occDate } : {}),
       };
       actions.updateEvent({ ...master, overrides });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@schedule-app/src/pages/PlannerPage.jsx` around lines 216 - 227, Update the
override construction in the PlannerPage drag-to-reschedule flow to preserve the
occurrence’s existing typeId, color, and reminder values, alongside the
currently copied fields. Reuse the occurrence data or established fallback
values so customized fields remain intact when actions.updateEvent is called.
schedule-app/src/App.jsx (1)

45-45: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Consider ignoring elements with aria-disabled="true".

The delegated haptic listener correctly ignores elements with the native disabled property, but links (<a>) and custom ARIA controls ([role="button"]) use the aria-disabled="true" attribute when disabled. Adding a check for this attribute ensures disabled UI components don't erroneously trigger tactile feedback.

💡 Proposed refactor
-      if (!el || el.disabled) return;
+      if (!el || el.disabled || el.getAttribute('aria-disabled') === 'true') return;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@schedule-app/src/App.jsx` at line 45, Update the delegated haptic listener’s
element guard to also return when the target element has aria-disabled="true",
while preserving the existing null and native disabled checks in the same
handler.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@schedule-app/src/App.jsx`:
- Around line 23-24: Update the stateRef synchronization near stateRef in App so
ref.current is assigned inside a useEffect that depends on state, removing the
direct render-phase mutation while preserving the ref’s current-state value
after committed renders.

In `@schedule-app/src/pages/MorePage.jsx`:
- Around line 36-651: Refactor MorePage into smaller feature components under
pages/more/, extracting the Profile, AccountSync, CalendarIO, Appearance,
Notifications, reconnect cadence, statuses/event types, feedback, and data tools
sections along with their modals. Keep MorePage focused on shared
store/navigation state and composition, passing state, actions, and requirePro
to child components; preserve all existing behavior and interactions.

In `@schedule-app/src/pages/PlannerPage.jsx`:
- Around line 231-249: Update moveSelected to group selected occurrences by
event ID and build each recurring event’s complete overrides set from a single
master snapshot before dispatching one actions.updateEvent call per event.
Preserve non-recurring date updates, and ensure all selected occurrences of the
same recurring event remain in the final overrides object.

---

Duplicate comments:
In `@schedule-app/src/App.jsx`:
- Line 45: Update the delegated haptic listener’s element guard to also return
when the target element has aria-disabled="true", while preserving the existing
null and native disabled checks in the same handler.

In `@schedule-app/src/pages/ContactsPage.jsx`:
- Line 112: Replace the UTC-based date expression assigned to createdAt in the
contact creation flow with the imported todayISO() helper, keeping the stored
format consistent with daysSince and the rest of the app’s local-date handling.
- Around line 88-94: The quick-add effects bypass React Router by calling
window.history.replaceState. In schedule-app/src/pages/ContactsPage.jsx lines
88-94, replace it with navigate(location.pathname, { replace: true, state: {} })
using the existing navigate; make the same replacement in both quick-add
branches in schedule-app/src/pages/PlannerPage.jsx lines 108-117, preserving the
existing startAdd behavior.

In `@schedule-app/src/pages/PlannerPage.jsx`:
- Around line 216-227: Update the override construction in the PlannerPage
drag-to-reschedule flow to preserve the occurrence’s existing typeId, color, and
reminder values, alongside the currently copied fields. Reuse the occurrence
data or established fallback values so customized fields remain intact when
actions.updateEvent is called.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 5677c2b6-57f3-4d58-9d6c-7e4c434ef46b

📥 Commits

Reviewing files that changed from the base of the PR and between 07f2351 and ade9cef.

📒 Files selected for processing (8)
  • schedule-app/README.md
  • schedule-app/src/App.jsx
  • schedule-app/src/data/ics.js
  • schedule-app/src/pages/ContactDetailPage.jsx
  • schedule-app/src/pages/ContactsPage.jsx
  • schedule-app/src/pages/MorePage.jsx
  • schedule-app/src/pages/PlannerPage.jsx
  • schedule-app/src/styles.css
📜 Review details
⏰ Context from checks skipped due to timeout. (1)
  • GitHub Check: Greptile Review
🧰 Additional context used
📓 Path-based instructions (15)
**/*.{js,ts,jsx,tsx,py,java,cs,go,rb,php,scala,kt}

📄 CodeRabbit inference engine (.cursor/rules/common-coding-style.md)

**/*.{js,ts,jsx,tsx,py,java,cs,go,rb,php,scala,kt}: Always create new objects, never mutate existing ones. Use immutable patterns to prevent hidden side effects and enable safe concurrency
Organize code into many small files (200-400 lines typical, 800 lines max) organized by feature/domain rather than by type
Always handle errors explicitly at every level and never silently swallow errors
Always validate all user input before processing at system boundaries
Use schema-based validation where available
Fail fast with clear error messages when validation fails
Never trust external data (API responses, user input, file content)
Ensure code is readable and well-named
Keep functions small (less than 50 lines)
Keep files focused (less than 800 lines)
Avoid deep nesting (more than 4 levels)
Do not use hardcoded values; use constants or configuration instead

Files:

  • schedule-app/src/data/ics.js
  • schedule-app/src/App.jsx
  • schedule-app/src/pages/MorePage.jsx
  • schedule-app/src/pages/ContactsPage.jsx
  • schedule-app/src/pages/ContactDetailPage.jsx
  • schedule-app/src/pages/PlannerPage.jsx
**/{server,backend,api,src}/**/*.{ts,tsx,js,py,java,cs,go,rb,php,scala,kt}

📄 CodeRabbit inference engine (.cursor/rules/common-coding-style.md)

Log detailed error context on the server side

Files:

  • schedule-app/src/data/ics.js
**/*.{js,ts,jsx,tsx,py,java,cs,rb,go,php,swift,kt,rs,c,cpp,h,hpp}

📄 CodeRabbit inference engine (.cursor/rules/common-security.md)

No hardcoded secrets (API keys, passwords, tokens) - validate before any commit

Files:

  • schedule-app/src/data/ics.js
  • schedule-app/src/App.jsx
  • schedule-app/src/pages/MorePage.jsx
  • schedule-app/src/pages/ContactsPage.jsx
  • schedule-app/src/pages/ContactDetailPage.jsx
  • schedule-app/src/pages/PlannerPage.jsx
**/*.{js,ts,jsx,tsx,py,java,cs,rb,go,php}

📄 CodeRabbit inference engine (.cursor/rules/common-security.md)

**/*.{js,ts,jsx,tsx,py,java,cs,rb,go,php}: All user inputs must be validated
Enable CSRF protection on all state-changing endpoints
Verify authentication and authorization for all protected endpoints
Implement rate limiting on all endpoints to prevent abuse
Ensure error messages do not leak sensitive data in responses

Files:

  • schedule-app/src/data/ics.js
  • schedule-app/src/App.jsx
  • schedule-app/src/pages/MorePage.jsx
  • schedule-app/src/pages/ContactsPage.jsx
  • schedule-app/src/pages/ContactDetailPage.jsx
  • schedule-app/src/pages/PlannerPage.jsx
**/*.{js,ts,jsx,tsx,py,java,cs,rb,go,php,sql}

📄 CodeRabbit inference engine (.cursor/rules/common-security.md)

Use parameterized queries to prevent SQL injection

Files:

  • schedule-app/src/data/ics.js
  • schedule-app/src/App.jsx
  • schedule-app/src/pages/MorePage.jsx
  • schedule-app/src/pages/ContactsPage.jsx
  • schedule-app/src/pages/ContactDetailPage.jsx
  • schedule-app/src/pages/PlannerPage.jsx
**/*.{js,ts,jsx,tsx,html,php,java,cs,rb,go}

📄 CodeRabbit inference engine (.cursor/rules/common-security.md)

Implement XSS prevention by sanitizing HTML output

Files:

  • schedule-app/src/data/ics.js
  • schedule-app/src/App.jsx
  • schedule-app/src/pages/MorePage.jsx
  • schedule-app/src/pages/ContactsPage.jsx
  • schedule-app/src/pages/ContactDetailPage.jsx
  • schedule-app/src/pages/PlannerPage.jsx
**/*.{js,ts,jsx,tsx,py,java,cs,rb,go,php,swift,kt,rs,c,cpp,h,hpp,properties,yml,yaml,json,env,config}

📄 CodeRabbit inference engine (.cursor/rules/common-security.md)

NEVER hardcode secrets in source code - ALWAYS use environment variables or a secret manager

Files:

  • schedule-app/src/data/ics.js
  • schedule-app/src/App.jsx
  • schedule-app/src/pages/MorePage.jsx
  • schedule-app/src/pages/ContactsPage.jsx
  • schedule-app/src/pages/ContactDetailPage.jsx
  • schedule-app/src/pages/PlannerPage.jsx
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursor/rules/typescript-coding-style.md)

**/*.{ts,tsx,js,jsx}: Use spread operator for immutable updates in TypeScript/JavaScript instead of direct mutation
Use async/await with try-catch for error handling in TypeScript/JavaScript
Use Zod for schema-based input validation in TypeScript/JavaScript
No console.log statements in production code; use proper logging libraries instead

**/*.{ts,tsx,js,jsx}: Auto-format JavaScript/TypeScript files using Prettier after edit
Warn about console.log statements in edited files
Check all modified files for console.log statements before session ends

**/*.{ts,tsx,js,jsx}: Use the ApiResponse interface pattern with generic type parameter: interface ApiResponse<T> { success: boolean; data?: T; error?: string; meta?: { total: number; page: number; limit: number; } }
Implement custom React hooks following the pattern: export a named function with use prefix, generic type parameters, and proper useEffect cleanup for side effects

**/*.{ts,tsx,js,jsx}: Never hardcode secrets; always use environment variables for sensitive credentials like API keys
Throw an error when required environment variables are not configured to fail fast and ensure security prerequisites are met

Use Playwright as the E2E testing framework for critical user flows in TypeScript/JavaScript

Files:

  • schedule-app/src/data/ics.js
  • schedule-app/src/App.jsx
  • schedule-app/src/pages/MorePage.jsx
  • schedule-app/src/pages/ContactsPage.jsx
  • schedule-app/src/pages/ContactDetailPage.jsx
  • schedule-app/src/pages/PlannerPage.jsx
**/*.{js,ts,jsx,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{js,ts,jsx,tsx}: Always create new objects and never mutate in place; return new copies instead
Keep files between 200–400 lines typical, with a maximum of 800 lines
Extract helpers when a file exceeds 200 lines
Handle errors explicitly at every level; never swallow errors silently
Validate all user input before processing; use schema-based validation where available
Never trust external data (API responses, file content, query params); always validate
All user inputs must be validated and sanitized
Error messages must be scrubbed of sensitive internals
Use readable, well-named identifiers in all code
Keep functions under 50 lines
Keep files under 800 lines
Avoid nesting deeper than 4 levels
Implement comprehensive error handling in all code
Do not hardcode values; use constants or environment configuration instead
Do not use in-place mutation; always return new objects or state

Files:

  • schedule-app/src/data/ics.js
  • schedule-app/src/App.jsx
  • schedule-app/src/pages/MorePage.jsx
  • schedule-app/src/pages/ContactsPage.jsx
  • schedule-app/src/pages/ContactDetailPage.jsx
  • schedule-app/src/pages/PlannerPage.jsx
**/*.{js,ts,jsx,tsx,json,env*}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Do not hardcode secrets, API keys, passwords, or tokens

Files:

  • schedule-app/src/data/ics.js
  • schedule-app/src/App.jsx
  • schedule-app/src/pages/MorePage.jsx
  • schedule-app/src/pages/ContactsPage.jsx
  • schedule-app/src/pages/ContactDetailPage.jsx
  • schedule-app/src/pages/PlannerPage.jsx
**/*.{js,ts}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{js,ts}: Use parameterized queries for all database writes (no string interpolation)
Auth/authz must be checked server-side for every sensitive path
Rate limiting must be applied to all public endpoints

Files:

  • schedule-app/src/data/ics.js
**/*.{jsx,tsx,js,ts}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

HTML output must be sanitized where applicable

Files:

  • schedule-app/src/data/ics.js
  • schedule-app/src/App.jsx
  • schedule-app/src/pages/MorePage.jsx
  • schedule-app/src/pages/ContactsPage.jsx
  • schedule-app/src/pages/ContactDetailPage.jsx
  • schedule-app/src/pages/PlannerPage.jsx
**/*.{js,ts,env*}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Required environment variables must be validated at startup

Files:

  • schedule-app/src/data/ics.js
**/{src,app}/**/*.{jsx,tsx,vue,html}

📄 CodeRabbit inference engine (.cursor/rules/common-coding-style.md)

Provide user-friendly error messages in UI-facing code

Files:

  • schedule-app/src/App.jsx
  • schedule-app/src/pages/MorePage.jsx
  • schedule-app/src/pages/ContactsPage.jsx
  • schedule-app/src/pages/ContactDetailPage.jsx
  • schedule-app/src/pages/PlannerPage.jsx
{**/*.tsx,**/*.jsx,**/components/**}

📄 CodeRabbit inference engine (CLAUDE.md)

When working on React files (*.tsx, *.jsx, components/**), use react-patterns and react-testing skills. Invoke /react-review, /react-build, /react-test commands for React-specific work.

Files:

  • schedule-app/src/App.jsx
  • schedule-app/src/pages/MorePage.jsx
  • schedule-app/src/pages/ContactsPage.jsx
  • schedule-app/src/pages/ContactDetailPage.jsx
  • schedule-app/src/pages/PlannerPage.jsx
🪛 ast-grep (0.44.1)
schedule-app/src/pages/MorePage.jsx

[warning] 276-276: A list component should have a key to prevent re-rendering
Context:
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(list-component-needs-key)

🔇 Additional comments (6)
schedule-app/src/pages/ContactDetailPage.jsx (1)

55-68: LGTM!

schedule-app/src/data/ics.js (1)

74-121: LGTM!

schedule-app/src/pages/MorePage.jsx (3)

132-146: Unvalidated backup import — same issue flagged previously.

importData still parses arbitrary file content and passes it straight to actions.importData(data) with no schema check, and reader still has no onerror handler. A malformed backup can propagate bad shapes into state and crash downstream .map()/.filter() calls; a read failure silently no-ops.

As per coding guidelines, **/*.{js,ts,jsx,tsx} requires: "Never trust external data (API responses, file content, query params); always validate" and "Use schema-based validation where available."


59-87: 🩺 Stability & Availability | ⚡ Quick win

Add a reader.onerror handler for the .ics import.

Same gap as the JSON backup import: reader has no onerror, so a failed file read silently does nothing instead of surfacing the existing "could not be read" alert.

As per coding guidelines, **/*.{js,ts,jsx,tsx} requires "Never trust external data (API responses, file content, query params); always validate" for handling of external file content.

🛡️ Proposed fix
     reader.onload = () => {
       try {
         const imported = parseICS(reader.result);
         ...
       } catch {
         alert('That file could not be read as an .ics calendar.');
       }
     };
+    reader.onerror = () => alert('That file could not be read as an .ics calendar.');
     reader.readAsText(file);

Source: Coding guidelines


1-35: LGTM!

Also applies to: 88-131, 148-680

schedule-app/README.md (1)

1-97: LGTM!

…ings

- Map: replace the unreliable Leaflet-mouse-event long-press with native
  Pointer Events on the map container, and swap the full-width temp-pin
  card for a small floating action bubble (directions / save / dismiss).
- Planner: stop the global haptic delegate from double-firing on event
  blocks; add a selectTick on each 15-min drag snap and a confirmTick on
  commit. Add expand/contract timeline zoom controls, persisted in
  settings.
- Fix off-center checkmarks (task list + note checklists) caused by
  default button padding shrinking the icon's content box; unify note
  checklist styling with the task list's green accent.
- Fix colored notes going unreadable in dark mode by forcing dark text
  on the (always-light) pastel note swatches.
- Pricing: $40/yr -> $35/yr.
- Settings: new Calendar section (24h time, week starts Sunday, default
  event duration/reminder lead, timeline hours, show tasks on timeline,
  event block opacity) and Map section (contact/custom pin toggles, pin
  emoji size), plus contact icon size, task-complete animation toggle,
  clear cache, and remove-all-contacts.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DaB8NfLY8nsxGAFk7XbCtw
@ecc-tools

ecc-tools Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

ECC bundle files are already tracked in this repository. Skipping generation of another bundle PR.

Normalize margin-bottom spacing across Home/More page blocks and
stacked settings toggles, and switch the event block color-intensity
slider to a true CSS opacity control gated behind Pro.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DaB8NfLY8nsxGAFk7XbCtw
@ecc-tools

ecc-tools Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

ECC bundle files are already tracked in this repository. Skipping generation of another bundle PR.

…heet

- Daily goals can now repeat on specific weekdays instead of every day;
  Home page's ring and reminders respect it.
- Page transitions are a bit longer with a fuller fade.
- Adding/editing a task now opens a full editor sheet (location, due
  date, notes, reminder) instead of a bare title field.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DaB8NfLY8nsxGAFk7XbCtw
@ecc-tools

ecc-tools Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

ECC bundle files are already tracked in this repository. Skipping generation of another bundle PR.

for (const e of state.events || []) {
const lead = Number(e.reminder) || 0;
if (!lead) continue;
if (!matchesRule(e, today) || (e.skipDates || []).includes(today)) continue;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Completed occurrences still notify

This filter excludes skipped dates but does not check whether today's recurring occurrence is complete. When today is present in doneDates, matchesRule remains true and the scan sends the reminder anyway. Suppress reminders when isOccurrenceDone(e, today) is true.

Planner:
- Dragging a single event left/right now pages the visible day exactly
  one at a time (was a proportional multi-day jump computed on release),
  live-navigating the whole timeline so the destination day's actual
  schedule is visible to drop into, matching the existing multi-select
  group-drag's day-offset semantics. The dragged event renders as a
  floating ghost decoupled from whichever day is currently showing, with
  window-level pointer tracking so paging (which drops the original DOM
  node from the day's event list) doesn't end the gesture.
- Removed the floating day/time bubble that appeared during a single-
  event drag; the live-paged header already shows the destination day.
- Added swipe-left/right to change day (Day view background) and change
  month (Month view grid), alongside the existing tap/pinch/long-press
  gestures, with click-suppression for the trailing tap after a swipe.
- Fixed a stale-closure bug in step(): it read `cursor` from render-time
  closure, so repeated calls from a long-lived listener (e.g. the drag
  gesture's window-level handlers, wired once at arm time) kept
  recomputing from the original day instead of compounding. Now uses the
  functional setCursor form so it's correct regardless of which closure
  invokes it.
- Fixed a swipe/click suppression flag that could leak past its own
  gesture: a swipe that crosses from one element to another never fires
  a trailing native click (the browser suppresses it), so the flag had
  no click to reset it and could swallow the next unrelated tap. Now
  cleared defensively at the start of each new gesture instead.
- Event detail sheet: added a bottom margin to the category tag so it
  no longer touches the details card below it, and aligned the
  done-row/section spacing to the same rhythm used elsewhere in the view.

Settings:
- Modal now supports a `fullPage` prop; applied to the profile, status,
  event-type, and feedback editors (the ones with a text input) so the
  Save button stays above the on-screen keyboard instead of being
  covered by it.
- Added interactive-widget=resizes-content to the viewport meta so
  fixed/full-height sheets actually resize above the keyboard.
- Fixed the gap between the color-scheme swatches and "Contact icon
  size" (was relying on the browser's default <p> margin, giving an
  inconsistent ~13px vs. the page's normal 14px rhythm), and normalized
  the profile row's margin to match.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DaB8NfLY8nsxGAFk7XbCtw
@ecc-tools

ecc-tools Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

ECC bundle files are already tracked in this repository. Skipping generation of another bundle PR.

Planner drag/swipe fixes:
- Fixed a stale-closure bug where the window-level pointer listeners
  (added for live day-paging) always saw dragDy/dragDx as their arm-time
  value of 0, so releasing a drag never actually saved the new time.
  Fixed with a ref-forwarding pattern so the listeners always call the
  latest onMoveP/onUp closure.
- Replaced the fixed 70px day-paging threshold with an edge-of-screen
  hysteresis: dragging an event now only pages a day once the pointer
  reaches the actual edge of the timeline, one page per approach
  (dragging back toward center re-arms it), applied to both the
  single-event drag and the multi-select group drag. The old threshold
  made it too easy to trigger a day change by accident during an
  ordinary vertical time-drag.
- A quick swipe that starts on an event block (released before the
  long-press arms) now changes the day too, same as swiping empty
  timeline space, without interfering with long-press-to-reschedule.
- Added a directional slide-in animation when the visible day changes
  (chevron, swipe, or drag-paging), via a keyed remount of the
  background/event layer that stays outside the drag ghost's render
  path so live-paging drags remain smooth.
- Added a gold "jump to today" button (Day/Week/Month, stays in mode).
- Removed the "Tap to add. Press and hold a block to move it." hint
  text — a first-time tutorial is the better home for it.

Haptics:
- The app-wide delegated tap listener fired on literally every button,
  tab, chip, and link; reserved it for primary/danger actions and
  toggle switches instead, which was the actual "overwhelming" source
  reported (most of the 39 call sites remain — they're custom gestures
  like drag-to-reschedule, not simple button clicks the delegated
  listener already covers).

Contacts:
- The People page and person detail page are no longer Pro-gated.
  Pro now gates person status groups (filter chips, status field, and
  the Settings management section) and the new contact timeline
  instead. Updated the pricing page's feature list and upsell copy to
  match.
- Removed the "Log today" quick action.
- Added a per-contact Timeline page: a chronological feed of linked
  calendar events and logged contacts, plus notes with a pin-to-top
  feature (pinned notes show in their own section above the feed). The
  feed sorts newest/future-first and lands on "today" when opened, so
  scrolling down moves toward the past and scrolling up toward the
  future. New interactions data model (state.interactions) backs the
  logged-contact entries; notes gained an optional contactId link.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DaB8NfLY8nsxGAFk7XbCtw
@ecc-tools

ecc-tools Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

ECC bundle files are already tracked in this repository. Skipping generation of another bundle PR.

Multi-step overlay covering all six pages (Home, Goals, Planner, People,
Map, More) and the key gestures — tap-to-add, hold-and-drag to reschedule,
drag-to-the-edge to change day, and swipe to navigate — that used to only
live in the timeline hint text removed earlier. Shows once automatically
(settings.tutorialSeen) and is replayable anytime from More via a new
"Replay the tour" button.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DaB8NfLY8nsxGAFk7XbCtw
@ecc-tools

ecc-tools Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

ECC bundle files are already tracked in this repository. Skipping generation of another bundle PR.

Backend:
- New UserData model (one JSON blob per user) with GET/PUT /api/data,
  mirroring the same shape the frontend already keeps in localStorage
  rather than modeling nine entity types relationally. Cascade-deletes
  with the user.
- Bumped the JSON body limit to 8mb (contact/profile photos are stored
  as inline data URLs, comfortably over Express's 100kb default).
- Documented the new endpoints and updated the "known gaps" note that
  used to say data sync didn't exist yet.

Frontend:
- New DataSync component (mounted alongside the existing
  SubscriptionSync, only when CLERK_ENABLED): on activation — signed in,
  Pro, and the existing "Cloud sync" toggle on — pulls whatever's saved
  server-side and replaces local state with it, or pushes local data to
  seed the cloud if nothing's there yet. After that, local changes push
  on a 2.5s debounce. This is intentionally simple last-write-wins sync
  across one person's own devices, not conflict-resolving multi-editor
  sync.
- The Cloud Sync section in Settings now reflects real state (signed
  in / syncing) once a real backend is configured, instead of always
  showing the "not connected yet" placeholder text.

Verified the backend model directly against the local dev database
(create/read/update/cascade-delete) since this environment only has a
placeholder Clerk key and can't complete a real sign-in to exercise the
HTTP layer end-to-end — the route itself follows the same requireUser
pattern already proven by /api/me.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DaB8NfLY8nsxGAFk7XbCtw
@ecc-tools

ecc-tools Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

ECC bundle files are already tracked in this repository. Skipping generation of another bundle PR.

- Suppress native text-selection/copy callout on event blocks, timeline
  background, and home bubble reorder rows so it no longer fights with
  the long-press-to-drag gesture.
- Use the app logo instead of an emoji on the tutorial's first slide.
- Fix unreadable goal/task badge text in Home's "Important reminders"
  (now uses the theme's --accent-ink instead of hardcoded white).
- Broaden that list to include all of today's events, including
  recurring occurrences, not just ones with an explicit reminder set.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DaB8NfLY8nsxGAFk7XbCtw
@ecc-tools

ecc-tools Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

ECC bundle files are already tracked in this repository. Skipping generation of another bundle PR.

- Event editor gets a "Link to goal / task" dropdown; checking an
  event's done box bumps the linked goal's progress for that day/week
  (or flips the linked task's done flag), and unchecking reverses it.
  Works from both the editor's own checkbox and the quick-toggle in
  the read-only detail view. The link and its target are shown in the
  detail view too.
- The event detail sheet's grip now supports the same swipe-down-to-
  close gesture as the editor sheet, so viewing an event can be
  dismissed without opening it for editing.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DaB8NfLY8nsxGAFk7XbCtw
@ecc-tools

ecc-tools Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

ECC bundle files are already tracked in this repository. Skipping generation of another bundle PR.

Modal and editor sheets sized themselves with static vh/100%, which
doesn't shrink when the on-screen keyboard opens, so fields near the
bottom of the event/task/goal/pin forms could end up trapped behind
it with no way to scroll them into view. Switch to dvh (matching the
pattern already used for the page/tabbar layout) so these sheets
actually shrink with the keyboard, and add extra bottom padding to
their scroll containers for headroom past the last field.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DaB8NfLY8nsxGAFk7XbCtw
@ecc-tools

ecc-tools Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

ECC bundle files are already tracked in this repository. Skipping generation of another bundle PR.

Settings gets a "Customize navigation tabs" section (same drag-to-
reorder + toggle UI as the existing home-bubble customization) that
lets Pro users reorder the tab bar and hide tabs they don't use. More
always stays on since it's the only way back to this screen, and a
lapsed subscriber's tab bar falls back to the full default set rather
than honoring a stale customization.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DaB8NfLY8nsxGAFk7XbCtw
@ecc-tools

ecc-tools Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

ECC bundle files are already tracked in this repository. Skipping generation of another bundle PR.

Home's three below-the-bubbles sections (Important reminders, Tasks,
Notes) were always shown in a fixed order. They're now a Pro-gated
"Customize home page sections" list in Settings, same drag-to-reorder
+ toggle UI as the existing bubble/tab customization — no new content,
just control over what already exists on the page.

Also collapsed the three near-identical reorder-list components
(home bubbles, home sections, nav tabs) into one generic
ReorderToggleList, since writing a third copy made the duplication
worth fixing.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DaB8NfLY8nsxGAFk7XbCtw
@ecc-tools

ecc-tools Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

ECC bundle files are already tracked in this repository. Skipping generation of another bundle PR.

- Tasks get a due-time field alongside due date. A task with both set
  now renders as a positioned, checkable block on the Planner
  timeline at that time, and can be marked done straight from the
  calendar without opening the task editor (same as the existing
  read-only-view/home-page toggle pattern).
- Task reminders are now a multi-select of lead times (15/30/60 min
  before due), replacing the old single on/off + absolute-time
  reminder, which was already unused since the notification scanner
  never actually checked it. Fixed the scanner to fire for tasks too.
- Undated/untimed tasks keep showing in the existing flat chip row
  above the timeline; only timed tasks move to the positioned layer,
  and only on their actual due day.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DaB8NfLY8nsxGAFk7XbCtw
@ecc-tools

ecc-tools Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

ECC bundle files are already tracked in this repository. Skipping generation of another bundle PR.

Replaces the "+" button's plain tap-to-open-a-sheet behavior on Home
and Planner with a shared ExpandableFab: tapping it now unstacks a
vertical row of labeled pills (Event/Task/Contact/Note) above the
button, which itself rotates into an "x" while open. Settings gets a
"Customize quick-add menu" Pro section (same drag-to-reorder + toggle
UI as the other customization lists) to reorder or hide individual
actions.

Planner's Task/Note pills hand off to Home via a location-state flag
(mirroring the existing quickNewEvent/quickNewContact pattern) since
those editors live there.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DaB8NfLY8nsxGAFk7XbCtw
@ecc-tools

ecc-tools Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

ECC bundle files are already tracked in this repository. Skipping generation of another bundle PR.

Import icon next to the People page's + Add button opens a file
picker; a minimal vCard parser (mirrors the existing .ics parser's
unfold/escape handling) reads FN/N, TEL, EMAIL, ADR, and NOTE fields
from one or more concatenated vCards in the file and adds each as a
new contact, geocoding the address pin the same way manual add does.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DaB8NfLY8nsxGAFk7XbCtw
@ecc-tools

ecc-tools Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

ECC bundle files are already tracked in this repository. Skipping generation of another bundle PR.

The Day view's background swipe used to be dead until release, then
snap straight to the next/previous day. It now tracks the finger in
real time (translateX matching drag distance, direction-locked so a
vertical scroll doesn't get tugged sideways), snapping back smoothly
if released short of the threshold and completing the day change via
the existing slide-in animation if released past it. Event-block
long-press-drag (a separate gesture path) is unaffected.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DaB8NfLY8nsxGAFk7XbCtw
@ecc-tools

ecc-tools Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

ECC bundle files are already tracked in this repository. Skipping generation of another bundle PR.

@haelyra

haelyra commented Jul 22, 2026

Copy link
Copy Markdown
Collaborator

Thank you for your contribution. We're closing this because the added calendar app is outside ECC's workflow/plugin surface and isn't a change we plan to merge into this repository.

@haelyra haelyra closed this Jul 22, 2026
@coderabbitai coderabbitai Bot mentioned this pull request Aug 11, 2026
26 tasks
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.

4 participants