Claude/missionary calendar app ti6dr6 - #2545
Conversation
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
|
ECC bundle files are already tracked in this repository. Skipping generation of another bundle PR. |
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughIntroduces Stewardly, a React/Vite personal planner PWA with goals, recurring events, contacts, map pins, reminders, local persistence, offline support, and GitHub Pages deployment. ChangesStewardly application
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
Suggested reviewers: 🚥 Pre-merge checks | ✅ 2 | ❌ 3❌ Failed checks (1 warning, 2 inconclusive)
✅ Passed checks (2 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
|
| 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 |
There was a problem hiding this comment.
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!
| branches: | ||
| - main | ||
| - claude/missionary-calendar-app-ti6dr6 |
There was a problem hiding this comment.
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.
| branches: | |
| - main | |
| - claude/missionary-calendar-app-ti6dr6 | |
| branches: | |
| - main |
There was a problem hiding this comment.
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
⛔ Files ignored due to path filters (2)
schedule-app/package-lock.jsonis excluded by!**/package-lock.jsonschedule-app/public/icon.svgis excluded by!**/*.svg
📒 Files selected for processing (23)
.github/workflows/deploy-schedule-app.ymlschedule-app/README.mdschedule-app/index.htmlschedule-app/package.jsonschedule-app/public/manifest.webmanifestschedule-app/public/sw.jsschedule-app/src/App.jsxschedule-app/src/components/Logo.jsxschedule-app/src/components/Modal.jsxschedule-app/src/components/TabBar.jsxschedule-app/src/data/helpers.jsschedule-app/src/data/notifications.jsschedule-app/src/data/seed.jsschedule-app/src/data/store.jsxschedule-app/src/main.jsxschedule-app/src/pages/ContactDetailPage.jsxschedule-app/src/pages/ContactsPage.jsxschedule-app/src/pages/GoalsPage.jsxschedule-app/src/pages/MapPage.jsxschedule-app/src/pages/MorePage.jsxschedule-app/src/pages/PlannerPage.jsxschedule-app/src/styles.cssschedule-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.jsschedule-app/src/components/Modal.jsxschedule-app/public/sw.jsschedule-app/src/main.jsxschedule-app/src/components/Logo.jsxschedule-app/src/App.jsxschedule-app/src/components/TabBar.jsxschedule-app/src/data/seed.jsschedule-app/src/data/store.jsxschedule-app/src/pages/MorePage.jsxschedule-app/src/data/notifications.jsschedule-app/src/pages/ContactDetailPage.jsxschedule-app/src/pages/ContactsPage.jsxschedule-app/src/pages/PlannerPage.jsxschedule-app/src/pages/MapPage.jsxschedule-app/src/pages/GoalsPage.jsxschedule-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.jsschedule-app/src/components/Modal.jsxschedule-app/public/sw.jsschedule-app/src/main.jsxschedule-app/src/components/Logo.jsxschedule-app/src/App.jsxschedule-app/src/components/TabBar.jsxschedule-app/src/data/seed.jsschedule-app/src/data/store.jsxschedule-app/src/pages/MorePage.jsxschedule-app/src/data/notifications.jsschedule-app/src/pages/ContactDetailPage.jsxschedule-app/src/pages/ContactsPage.jsxschedule-app/src/pages/PlannerPage.jsxschedule-app/src/pages/MapPage.jsxschedule-app/src/pages/GoalsPage.jsxschedule-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.jsschedule-app/src/components/Modal.jsxschedule-app/public/sw.jsschedule-app/src/main.jsxschedule-app/src/components/Logo.jsxschedule-app/src/App.jsxschedule-app/src/components/TabBar.jsxschedule-app/src/data/seed.jsschedule-app/src/data/store.jsxschedule-app/src/pages/MorePage.jsxschedule-app/src/data/notifications.jsschedule-app/src/pages/ContactDetailPage.jsxschedule-app/src/pages/ContactsPage.jsxschedule-app/src/pages/PlannerPage.jsxschedule-app/src/pages/MapPage.jsxschedule-app/src/pages/GoalsPage.jsxschedule-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.jsschedule-app/src/components/Modal.jsxschedule-app/public/sw.jsschedule-app/src/main.jsxschedule-app/src/components/Logo.jsxschedule-app/src/App.jsxschedule-app/src/components/TabBar.jsxschedule-app/src/data/seed.jsschedule-app/src/data/store.jsxschedule-app/src/pages/MorePage.jsxschedule-app/src/data/notifications.jsschedule-app/src/pages/ContactDetailPage.jsxschedule-app/src/pages/ContactsPage.jsxschedule-app/src/pages/PlannerPage.jsxschedule-app/src/pages/MapPage.jsxschedule-app/src/pages/GoalsPage.jsxschedule-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.jsschedule-app/src/components/Modal.jsxschedule-app/public/sw.jsschedule-app/index.htmlschedule-app/src/main.jsxschedule-app/src/components/Logo.jsxschedule-app/src/App.jsxschedule-app/src/components/TabBar.jsxschedule-app/src/data/seed.jsschedule-app/src/data/store.jsxschedule-app/src/pages/MorePage.jsxschedule-app/src/data/notifications.jsschedule-app/src/pages/ContactDetailPage.jsxschedule-app/src/pages/ContactsPage.jsxschedule-app/src/pages/PlannerPage.jsxschedule-app/src/pages/MapPage.jsxschedule-app/src/pages/GoalsPage.jsxschedule-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.jsschedule-app/src/components/Modal.jsxschedule-app/public/sw.jsschedule-app/package.jsonschedule-app/src/main.jsxschedule-app/src/components/Logo.jsxschedule-app/src/App.jsxschedule-app/src/components/TabBar.jsxschedule-app/src/data/seed.jsschedule-app/src/data/store.jsxschedule-app/src/pages/MorePage.jsxschedule-app/src/data/notifications.jsschedule-app/src/pages/ContactDetailPage.jsxschedule-app/src/pages/ContactsPage.jsxschedule-app/src/pages/PlannerPage.jsxschedule-app/src/pages/MapPage.jsxschedule-app/src/pages/GoalsPage.jsxschedule-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 aboutconsole.logstatements in edited files
Check all modified files forconsole.logstatements 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 metUse Playwright as the E2E testing framework for critical user flows in TypeScript/JavaScript
Files:
schedule-app/vite.config.jsschedule-app/src/components/Modal.jsxschedule-app/public/sw.jsschedule-app/src/main.jsxschedule-app/src/components/Logo.jsxschedule-app/src/App.jsxschedule-app/src/components/TabBar.jsxschedule-app/src/data/seed.jsschedule-app/src/data/store.jsxschedule-app/src/pages/MorePage.jsxschedule-app/src/data/notifications.jsschedule-app/src/pages/ContactDetailPage.jsxschedule-app/src/pages/ContactsPage.jsxschedule-app/src/pages/PlannerPage.jsxschedule-app/src/pages/MapPage.jsxschedule-app/src/pages/GoalsPage.jsxschedule-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.jsschedule-app/src/components/Modal.jsxschedule-app/public/sw.jsschedule-app/src/main.jsxschedule-app/src/components/Logo.jsxschedule-app/src/App.jsxschedule-app/src/components/TabBar.jsxschedule-app/src/data/seed.jsschedule-app/src/data/store.jsxschedule-app/src/pages/MorePage.jsxschedule-app/src/data/notifications.jsschedule-app/src/pages/ContactDetailPage.jsxschedule-app/src/pages/ContactsPage.jsxschedule-app/src/pages/PlannerPage.jsxschedule-app/src/pages/MapPage.jsxschedule-app/src/pages/GoalsPage.jsxschedule-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.jsschedule-app/src/components/Modal.jsxschedule-app/public/sw.jsschedule-app/package.jsonschedule-app/src/main.jsxschedule-app/src/components/Logo.jsxschedule-app/src/App.jsxschedule-app/src/components/TabBar.jsxschedule-app/src/data/seed.jsschedule-app/src/data/store.jsxschedule-app/src/pages/MorePage.jsxschedule-app/src/data/notifications.jsschedule-app/src/pages/ContactDetailPage.jsxschedule-app/src/pages/ContactsPage.jsxschedule-app/src/pages/PlannerPage.jsxschedule-app/src/pages/MapPage.jsxschedule-app/src/pages/GoalsPage.jsxschedule-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.jsschedule-app/public/sw.jsschedule-app/src/data/seed.jsschedule-app/src/data/notifications.jsschedule-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.jsschedule-app/src/components/Modal.jsxschedule-app/public/sw.jsschedule-app/src/main.jsxschedule-app/src/components/Logo.jsxschedule-app/src/App.jsxschedule-app/src/components/TabBar.jsxschedule-app/src/data/seed.jsschedule-app/src/data/store.jsxschedule-app/src/pages/MorePage.jsxschedule-app/src/data/notifications.jsschedule-app/src/pages/ContactDetailPage.jsxschedule-app/src/pages/ContactsPage.jsxschedule-app/src/pages/PlannerPage.jsxschedule-app/src/pages/MapPage.jsxschedule-app/src/pages/GoalsPage.jsxschedule-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.jsschedule-app/public/sw.jsschedule-app/src/data/seed.jsschedule-app/src/data/notifications.jsschedule-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.jsxschedule-app/src/main.jsxschedule-app/src/components/Logo.jsxschedule-app/src/App.jsxschedule-app/src/components/TabBar.jsxschedule-app/src/data/store.jsxschedule-app/src/pages/MorePage.jsxschedule-app/src/pages/ContactDetailPage.jsxschedule-app/src/pages/ContactsPage.jsxschedule-app/src/pages/PlannerPage.jsxschedule-app/src/pages/MapPage.jsxschedule-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-testcommands for React-specific work.
Files:
schedule-app/src/components/Modal.jsxschedule-app/src/main.jsxschedule-app/src/components/Logo.jsxschedule-app/src/App.jsxschedule-app/src/components/TabBar.jsxschedule-app/src/data/store.jsxschedule-app/src/pages/MorePage.jsxschedule-app/src/pages/ContactDetailPage.jsxschedule-app/src/pages/ContactsPage.jsxschedule-app/src/pages/PlannerPage.jsxschedule-app/src/pages/MapPage.jsxschedule-app/src/pages/GoalsPage.jsx
.github/workflows/*.yml
📄 CodeRabbit inference engine (CLAUDE.md)
When working on GitHub workflow files, use the
/ci-workflowskill.
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.jsschedule-app/src/data/notifications.jsschedule-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) => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[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 CorrectnessNavigation-intent effect only runs on mount.
[]deps meanlocation.state(placeForContact/selectPin) is only consumed once. If MapPage stays mounted while anotherLink/navigate()targets/mapagain with a newstate(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
/mapdepends on the router setup inschedule-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 onlocation.state/location.key(using a ref to avoid re-triggeringsetPlacing/setSelectedIdloops).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 QualityConfirm the render-phase
setDraftcalls 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 akeyprop 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-varhint on Line 24 is a false positive — this is astateRef/empty-deps pattern to keep a single interval reading the latest state, not asetStatecall.)
35-50: LGTM!schedule-app/src/components/TabBar.jsx (1)
11-26: LGTM!(The ast-grep
list-component-needs-keyhints on Lines 19–20 are false positives — thekeyis correctly on the mappedNavLink;<Icon />and<span>are not list siblings.)schedule-app/src/components/Logo.jsx (1)
8-13: 📐 Maintainability & Code QualityDuplicate SVG id is not a current issue. The app renders one route at a time, and the only
Logo/Brandusages are page-local, soid="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!
| branches: | ||
| - main | ||
| - claude/missionary-calendar-app-ti6dr6 |
There was a problem hiding this comment.
🩺 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.
| # Least-privilege token, plus the scopes the Pages deploy needs. | ||
| permissions: | ||
| contents: read | ||
| pages: write | ||
| id-token: write | ||
|
|
There was a problem hiding this comment.
🔒 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.
| # 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 |
There was a problem hiding this comment.
🔒 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
| 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; | ||
| } |
There was a problem hiding this comment.
🩺 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.
| 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.
| @keyframes slideUp { | ||
| from { | ||
| transform: translateY(100%); | ||
| } | ||
| to { | ||
| transform: none; | ||
| } | ||
| } |
There was a problem hiding this comment.
📐 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
- 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 bundle files are already tracked in this repository. Skipping generation of another bundle PR. |
There was a problem hiding this comment.
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 winDefault reconnect cadence (30) is hardcoded independently in two files. Both derive
reconnectDaysfromstate.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: extract30into an exported constant (e.g.DEFAULT_RECONNECT_DAYSinhelpers.js) and use it here.schedule-app/src/pages/ContactDetailPage.jsx#L28: import and use the same shared constant instead of repeating the literal30.🤖 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 winAdd 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 winValidate imported backup data before merging into state.
importDataparses arbitrary file content and passes it straight toactions.importData(data). A malformed or hand-edited backup (e.g., missing arrays foreventsorcontacts) will propagate into the application state, leading to crashes in downstream components. Additionally, theFileReaderlacks anonerrorhandler 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 winGuard 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
createdAtstill uses UTC date, inconsistent with local-date helpers.
new Date().toISOString().slice(0, 10)is UTC-based whiledaysSince/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 winRecurrence-matching duplication between
occursOnandmatchesRulestill present.Per the change-detail description,
occursOnandmatchesRulestill carry the same daily/weekly/biweekly/monthly switch, differing only in theskipDatescheck — the exact duplication flagged previously. Any future frequency-semantics change risks touching one and not the other (e.g., planner display vs.notifications.jsreminder 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
⛔ Files ignored due to path filters (1)
schedule-app/public/icon.svgis excluded by!**/*.svg
📒 Files selected for processing (19)
schedule-app/README.mdschedule-app/index.htmlschedule-app/package.jsonschedule-app/public/manifest.webmanifestschedule-app/public/sw.jsschedule-app/src/App.jsxschedule-app/src/components/Avatar.jsxschedule-app/src/components/Logo.jsxschedule-app/src/components/Select.jsxschedule-app/src/components/TabBar.jsxschedule-app/src/data/haptics.jsschedule-app/src/data/helpers.jsschedule-app/src/data/image.jsschedule-app/src/data/notifications.jsschedule-app/src/pages/ContactDetailPage.jsxschedule-app/src/pages/ContactsPage.jsxschedule-app/src/pages/MapPage.jsxschedule-app/src/pages/MorePage.jsxschedule-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.htmlschedule-app/src/data/image.jsschedule-app/src/components/Avatar.jsxschedule-app/src/data/haptics.jsschedule-app/src/components/Select.jsxschedule-app/src/components/Logo.jsxschedule-app/src/components/TabBar.jsxschedule-app/public/sw.jsschedule-app/src/App.jsxschedule-app/src/pages/ContactsPage.jsxschedule-app/src/pages/MorePage.jsxschedule-app/src/data/notifications.jsschedule-app/src/pages/MapPage.jsxschedule-app/src/pages/ContactDetailPage.jsxschedule-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.jsonschedule-app/src/data/image.jsschedule-app/src/components/Avatar.jsxschedule-app/src/data/haptics.jsschedule-app/src/components/Select.jsxschedule-app/src/components/Logo.jsxschedule-app/src/components/TabBar.jsxschedule-app/public/sw.jsschedule-app/src/App.jsxschedule-app/src/pages/ContactsPage.jsxschedule-app/src/pages/MorePage.jsxschedule-app/src/data/notifications.jsschedule-app/src/pages/MapPage.jsxschedule-app/src/pages/ContactDetailPage.jsxschedule-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.jsonschedule-app/src/data/image.jsschedule-app/src/components/Avatar.jsxschedule-app/src/data/haptics.jsschedule-app/src/components/Select.jsxschedule-app/src/components/Logo.jsxschedule-app/src/components/TabBar.jsxschedule-app/public/sw.jsschedule-app/src/App.jsxschedule-app/src/pages/ContactsPage.jsxschedule-app/src/pages/MorePage.jsxschedule-app/src/data/notifications.jsschedule-app/src/pages/MapPage.jsxschedule-app/src/pages/ContactDetailPage.jsxschedule-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.jsschedule-app/src/components/Avatar.jsxschedule-app/src/data/haptics.jsschedule-app/src/components/Select.jsxschedule-app/src/components/Logo.jsxschedule-app/src/components/TabBar.jsxschedule-app/public/sw.jsschedule-app/src/App.jsxschedule-app/src/pages/ContactsPage.jsxschedule-app/src/pages/MorePage.jsxschedule-app/src/data/notifications.jsschedule-app/src/pages/MapPage.jsxschedule-app/src/pages/ContactDetailPage.jsxschedule-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.jsschedule-app/src/data/haptics.jsschedule-app/src/data/notifications.jsschedule-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.jsschedule-app/src/components/Avatar.jsxschedule-app/src/data/haptics.jsschedule-app/src/components/Select.jsxschedule-app/src/components/Logo.jsxschedule-app/src/components/TabBar.jsxschedule-app/public/sw.jsschedule-app/src/App.jsxschedule-app/src/pages/ContactsPage.jsxschedule-app/src/pages/MorePage.jsxschedule-app/src/data/notifications.jsschedule-app/src/pages/MapPage.jsxschedule-app/src/pages/ContactDetailPage.jsxschedule-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.jsschedule-app/src/components/Avatar.jsxschedule-app/src/data/haptics.jsschedule-app/src/components/Select.jsxschedule-app/src/components/Logo.jsxschedule-app/src/components/TabBar.jsxschedule-app/public/sw.jsschedule-app/src/App.jsxschedule-app/src/pages/ContactsPage.jsxschedule-app/src/pages/MorePage.jsxschedule-app/src/data/notifications.jsschedule-app/src/pages/MapPage.jsxschedule-app/src/pages/ContactDetailPage.jsxschedule-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.jsschedule-app/src/components/Avatar.jsxschedule-app/src/data/haptics.jsschedule-app/src/components/Select.jsxschedule-app/src/components/Logo.jsxschedule-app/src/components/TabBar.jsxschedule-app/public/sw.jsschedule-app/src/App.jsxschedule-app/src/pages/ContactsPage.jsxschedule-app/src/pages/MorePage.jsxschedule-app/src/data/notifications.jsschedule-app/src/pages/MapPage.jsxschedule-app/src/pages/ContactDetailPage.jsxschedule-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 aboutconsole.logstatements in edited files
Check all modified files forconsole.logstatements 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 metUse Playwright as the E2E testing framework for critical user flows in TypeScript/JavaScript
Files:
schedule-app/src/data/image.jsschedule-app/src/components/Avatar.jsxschedule-app/src/data/haptics.jsschedule-app/src/components/Select.jsxschedule-app/src/components/Logo.jsxschedule-app/src/components/TabBar.jsxschedule-app/public/sw.jsschedule-app/src/App.jsxschedule-app/src/pages/ContactsPage.jsxschedule-app/src/pages/MorePage.jsxschedule-app/src/data/notifications.jsschedule-app/src/pages/MapPage.jsxschedule-app/src/pages/ContactDetailPage.jsxschedule-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.jsschedule-app/src/components/Avatar.jsxschedule-app/src/data/haptics.jsschedule-app/src/components/Select.jsxschedule-app/src/components/Logo.jsxschedule-app/src/components/TabBar.jsxschedule-app/public/sw.jsschedule-app/src/App.jsxschedule-app/src/pages/ContactsPage.jsxschedule-app/src/pages/MorePage.jsxschedule-app/src/data/notifications.jsschedule-app/src/pages/MapPage.jsxschedule-app/src/pages/ContactDetailPage.jsxschedule-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.jsschedule-app/src/data/haptics.jsschedule-app/public/sw.jsschedule-app/src/data/notifications.jsschedule-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.jsschedule-app/src/components/Avatar.jsxschedule-app/src/data/haptics.jsschedule-app/src/components/Select.jsxschedule-app/src/components/Logo.jsxschedule-app/src/components/TabBar.jsxschedule-app/public/sw.jsschedule-app/src/App.jsxschedule-app/src/pages/ContactsPage.jsxschedule-app/src/pages/MorePage.jsxschedule-app/src/data/notifications.jsschedule-app/src/pages/MapPage.jsxschedule-app/src/pages/ContactDetailPage.jsxschedule-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.jsschedule-app/src/data/haptics.jsschedule-app/public/sw.jsschedule-app/src/data/notifications.jsschedule-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.jsxschedule-app/src/components/Select.jsxschedule-app/src/components/Logo.jsxschedule-app/src/components/TabBar.jsxschedule-app/src/App.jsxschedule-app/src/pages/ContactsPage.jsxschedule-app/src/pages/MorePage.jsxschedule-app/src/pages/MapPage.jsxschedule-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-testcommands for React-specific work.
Files:
schedule-app/src/components/Avatar.jsxschedule-app/src/components/Select.jsxschedule-app/src/components/Logo.jsxschedule-app/src/components/TabBar.jsxschedule-app/src/App.jsxschedule-app/src/pages/ContactsPage.jsxschedule-app/src/pages/MorePage.jsxschedule-app/src/pages/MapPage.jsxschedule-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
| // 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); | ||
| }, []); | ||
|
|
There was a problem hiding this comment.
🎯 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.
| // 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.
| <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> |
There was a problem hiding this comment.
🩺 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.
| 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); |
There was a problem hiding this comment.
📐 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 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 bundle files are already tracked in this repository. Skipping generation of another bundle PR. |
There was a problem hiding this comment.
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 valueExtract 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 winAllow explicit
0to disable reconnect reminders.Currently, explicitly setting a cadence of
0to disable reminders is lost during save and read because0evaluates 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: UpdatereconnectDaysOfto 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 tocadenceText: contact.cadenceDays != null ? String(contact.cadenceDays) : ''to preserve0.schedule-app/src/pages/ContactDetailPage.jsx#L92-L92: Change the save payload tocadenceDays: editing.cadenceText === '' ? null : Number(editing.cadenceText)to persist the distinction between an unset value (null) and an explicit0.🤖 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 tradeoffComponent 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: ExtractEditorSheetforms,EmptyState, or list sections into separate components to reduce function size.schedule-app/src/pages/ContactsPage.jsx#L22-L286: Extract theEditorSheetor the main list rendering logic.schedule-app/src/pages/ContactDetailPage.jsx#L19-L346: Extract theEditorSheetand 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 winGate notifications on permission
setRemindersetssettings.notificationstotrueeven whenrequestNotificationPermission()returnsdenied. That leaves the app showing notifications as enabled while reminders still won’t fire; only persist the setting when permission isgranted.🤖 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 winReset the missing series fields in
applyScope('all').editing.baseonly restorestitle/start/end/contactId/location/notes/date, sotypeId,color,reminder,locLat, andlocLngstay on the draft anddoSavecan 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
createdAtuses UTC while the rest of the app uses local dates.
new Date().toISOString().slice(0, 10)is UTC-based, butdaysSince/todayISOoperate on local dates. Near midnight in negative-offset timezones this stores tomorrow's date, skewing the overdue calculation by a day.todayISOis 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 winHonor the resolved notification permission before enabling reminders.
Both call sites await
requestNotificationPermission()(which can return'denied') but ignore the result — they flipnotifications: trueand 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
📒 Files selected for processing (10)
schedule-app/src/components/EditorSheet.jsxschedule-app/src/components/MiniMapPicker.jsxschedule-app/src/components/Select.jsxschedule-app/src/data/helpers.jsschedule-app/src/pages/ContactDetailPage.jsxschedule-app/src/pages/ContactsPage.jsxschedule-app/src/pages/GoalsPage.jsxschedule-app/src/pages/MapPage.jsxschedule-app/src/pages/PlannerPage.jsxschedule-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.jsxschedule-app/src/components/EditorSheet.jsxschedule-app/src/components/Select.jsxschedule-app/src/pages/MapPage.jsxschedule-app/src/pages/ContactDetailPage.jsxschedule-app/src/pages/ContactsPage.jsxschedule-app/src/pages/GoalsPage.jsxschedule-app/src/pages/PlannerPage.jsxschedule-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.jsxschedule-app/src/components/EditorSheet.jsxschedule-app/src/components/Select.jsxschedule-app/src/pages/MapPage.jsxschedule-app/src/pages/ContactDetailPage.jsxschedule-app/src/pages/ContactsPage.jsxschedule-app/src/pages/GoalsPage.jsxschedule-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.jsxschedule-app/src/components/EditorSheet.jsxschedule-app/src/components/Select.jsxschedule-app/src/pages/MapPage.jsxschedule-app/src/pages/ContactDetailPage.jsxschedule-app/src/pages/ContactsPage.jsxschedule-app/src/pages/GoalsPage.jsxschedule-app/src/pages/PlannerPage.jsxschedule-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.jsxschedule-app/src/components/EditorSheet.jsxschedule-app/src/components/Select.jsxschedule-app/src/pages/MapPage.jsxschedule-app/src/pages/ContactDetailPage.jsxschedule-app/src/pages/ContactsPage.jsxschedule-app/src/pages/GoalsPage.jsxschedule-app/src/pages/PlannerPage.jsxschedule-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.jsxschedule-app/src/components/EditorSheet.jsxschedule-app/src/components/Select.jsxschedule-app/src/pages/MapPage.jsxschedule-app/src/pages/ContactDetailPage.jsxschedule-app/src/pages/ContactsPage.jsxschedule-app/src/pages/GoalsPage.jsxschedule-app/src/pages/PlannerPage.jsxschedule-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.jsxschedule-app/src/components/EditorSheet.jsxschedule-app/src/components/Select.jsxschedule-app/src/pages/MapPage.jsxschedule-app/src/pages/ContactDetailPage.jsxschedule-app/src/pages/ContactsPage.jsxschedule-app/src/pages/GoalsPage.jsxschedule-app/src/pages/PlannerPage.jsxschedule-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.jsxschedule-app/src/components/EditorSheet.jsxschedule-app/src/components/Select.jsxschedule-app/src/pages/MapPage.jsxschedule-app/src/pages/ContactDetailPage.jsxschedule-app/src/pages/ContactsPage.jsxschedule-app/src/pages/GoalsPage.jsxschedule-app/src/pages/PlannerPage.jsxschedule-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 aboutconsole.logstatements in edited files
Check all modified files forconsole.logstatements 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 metUse Playwright as the E2E testing framework for critical user flows in TypeScript/JavaScript
Files:
schedule-app/src/components/MiniMapPicker.jsxschedule-app/src/components/EditorSheet.jsxschedule-app/src/components/Select.jsxschedule-app/src/pages/MapPage.jsxschedule-app/src/pages/ContactDetailPage.jsxschedule-app/src/pages/ContactsPage.jsxschedule-app/src/pages/GoalsPage.jsxschedule-app/src/pages/PlannerPage.jsxschedule-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-testcommands for React-specific work.
Files:
schedule-app/src/components/MiniMapPicker.jsxschedule-app/src/components/EditorSheet.jsxschedule-app/src/components/Select.jsxschedule-app/src/pages/MapPage.jsxschedule-app/src/pages/ContactDetailPage.jsxschedule-app/src/pages/ContactsPage.jsxschedule-app/src/pages/GoalsPage.jsxschedule-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.jsxschedule-app/src/components/EditorSheet.jsxschedule-app/src/components/Select.jsxschedule-app/src/pages/MapPage.jsxschedule-app/src/pages/ContactDetailPage.jsxschedule-app/src/pages/ContactsPage.jsxschedule-app/src/pages/GoalsPage.jsxschedule-app/src/pages/PlannerPage.jsxschedule-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.jsxschedule-app/src/components/EditorSheet.jsxschedule-app/src/components/Select.jsxschedule-app/src/pages/MapPage.jsxschedule-app/src/pages/ContactDetailPage.jsxschedule-app/src/pages/ContactsPage.jsxschedule-app/src/pages/GoalsPage.jsxschedule-app/src/pages/PlannerPage.jsxschedule-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.jsxschedule-app/src/components/EditorSheet.jsxschedule-app/src/components/Select.jsxschedule-app/src/pages/MapPage.jsxschedule-app/src/pages/ContactDetailPage.jsxschedule-app/src/pages/ContactsPage.jsxschedule-app/src/pages/GoalsPage.jsxschedule-app/src/pages/PlannerPage.jsxschedule-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 winDuplicated recurrence logic gets a third identical branch.
The
occursOn/matchesRuleswitch 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
matchesRuledefinition 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 lacksaria-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 winFix visual freeze when dragging back up.
If the user drags the sheet down and then drags back up above the starting point,
dybecomes negative or zero. Because of theif (dy > 0)check,dragYis not updated to0, 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 to0and 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 dropstypeId/color/reminder— and alsolocLat/locLng.
save()'s single-occurrence path persists the full field set (ov = { ...pl.fields }, which includestypeId,color,reminder,locLat,locLng). This rebuilt override only carriestitle/start/end/contactId/location/notes/date, so dragging a customized occurrence silently reverts those keys to the master's values. The previously flagged loss oftypeId/color/reminderremains, 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!
| setTimeout(() => map.invalidateSize(), 60); | ||
| return () => { | ||
| map.remove(); | ||
| mapRef.current = null; | ||
| }; | ||
| }, []); // eslint-disable-line react-hooks/exhaustive-deps |
There was a problem hiding this comment.
🩺 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.
| 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.
| <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 | ||
| } | ||
| > |
There was a problem hiding this comment.
📐 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: PasssaveDisabled={!editing?.title?.trim()}toEditorSheet.schedule-app/src/pages/ContactsPage.jsx#L222-L223: PasssaveDisabled={!adding?.name?.trim()}toEditorSheet.schedule-app/src/pages/ContactDetailPage.jsx#L242-L255: PasssaveDisabled={!editing?.name?.trim()}toEditorSheet.
📍 Affects 3 files
schedule-app/src/pages/GoalsPage.jsx#L218-L235(this comment)schedule-app/src/pages/ContactsPage.jsx#L222-L223schedule-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
| 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); | ||
| } | ||
| } |
There was a problem hiding this comment.
📐 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
There was a problem hiding this comment.
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 winDo not silently swallow errors.
The
catchblock 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 winData loss when moving multiple occurrences of the same recurring event.
The loop sequentially reads the master event from
state.eventsand 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 fromstate.eventsbefore the store has processed the first update. The second iteration uses a stalemasterobject (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 winPrevent 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 to1455.minutesToTimewill 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
📒 Files selected for processing (9)
schedule-app/src/App.jsxschedule-app/src/components/TabBar.jsxschedule-app/src/data/seed.jsschedule-app/src/data/store.jsxschedule-app/src/pages/ContactsPage.jsxschedule-app/src/pages/HomePage.jsxschedule-app/src/pages/PlannerPage.jsxschedule-app/src/pages/PricingPage.jsxschedule-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.jsxschedule-app/src/pages/PricingPage.jsxschedule-app/src/data/seed.jsschedule-app/src/pages/HomePage.jsxschedule-app/src/App.jsxschedule-app/src/data/store.jsxschedule-app/src/pages/ContactsPage.jsxschedule-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.jsxschedule-app/src/pages/PricingPage.jsxschedule-app/src/pages/HomePage.jsxschedule-app/src/App.jsxschedule-app/src/data/store.jsxschedule-app/src/pages/ContactsPage.jsxschedule-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.jsxschedule-app/src/pages/PricingPage.jsxschedule-app/src/data/seed.jsschedule-app/src/pages/HomePage.jsxschedule-app/src/App.jsxschedule-app/src/data/store.jsxschedule-app/src/pages/ContactsPage.jsxschedule-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.jsxschedule-app/src/pages/PricingPage.jsxschedule-app/src/data/seed.jsschedule-app/src/pages/HomePage.jsxschedule-app/src/App.jsxschedule-app/src/data/store.jsxschedule-app/src/pages/ContactsPage.jsxschedule-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.jsxschedule-app/src/pages/PricingPage.jsxschedule-app/src/data/seed.jsschedule-app/src/pages/HomePage.jsxschedule-app/src/App.jsxschedule-app/src/data/store.jsxschedule-app/src/pages/ContactsPage.jsxschedule-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.jsxschedule-app/src/pages/PricingPage.jsxschedule-app/src/data/seed.jsschedule-app/src/pages/HomePage.jsxschedule-app/src/App.jsxschedule-app/src/data/store.jsxschedule-app/src/pages/ContactsPage.jsxschedule-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.jsxschedule-app/src/pages/PricingPage.jsxschedule-app/src/data/seed.jsschedule-app/src/pages/HomePage.jsxschedule-app/src/App.jsxschedule-app/src/data/store.jsxschedule-app/src/pages/ContactsPage.jsxschedule-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 aboutconsole.logstatements in edited files
Check all modified files forconsole.logstatements 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 metUse Playwright as the E2E testing framework for critical user flows in TypeScript/JavaScript
Files:
schedule-app/src/components/TabBar.jsxschedule-app/src/pages/PricingPage.jsxschedule-app/src/data/seed.jsschedule-app/src/pages/HomePage.jsxschedule-app/src/App.jsxschedule-app/src/data/store.jsxschedule-app/src/pages/ContactsPage.jsxschedule-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-testcommands for React-specific work.
Files:
schedule-app/src/components/TabBar.jsxschedule-app/src/pages/PricingPage.jsxschedule-app/src/pages/HomePage.jsxschedule-app/src/App.jsxschedule-app/src/data/store.jsxschedule-app/src/pages/ContactsPage.jsxschedule-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.jsxschedule-app/src/pages/PricingPage.jsxschedule-app/src/data/seed.jsschedule-app/src/pages/HomePage.jsxschedule-app/src/App.jsxschedule-app/src/data/store.jsxschedule-app/src/pages/ContactsPage.jsxschedule-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.jsxschedule-app/src/pages/PricingPage.jsxschedule-app/src/data/seed.jsschedule-app/src/pages/HomePage.jsxschedule-app/src/App.jsxschedule-app/src/data/store.jsxschedule-app/src/pages/ContactsPage.jsxschedule-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.jsxschedule-app/src/pages/PricingPage.jsxschedule-app/src/data/seed.jsschedule-app/src/pages/HomePage.jsxschedule-app/src/App.jsxschedule-app/src/data/store.jsxschedule-app/src/pages/ContactsPage.jsxschedule-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_DATAtrusts imported JSON unvalidated and skipsloadState's migration.
{ ...makeSeed(), ...action.data }merges the imported file straight into state with no schema check, and — unlikeloadState(lines 9-37) — doesn't run the goalperiod/progressmigration oreventTypes/settingsdefaulting. 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 sharednormalizeState(parsed)used by bothloadStateandIMPORT_DATA, and validateaction.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
pointerdownlistener inApp.jsxthat automatically triggers haptic feedback for all<a>elements (whichNavLinkrenders). LeavingonClick={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 witharia-disabled="true".The delegated haptic listener correctly ignores elements with the native
disabledproperty, but links (<a>) and custom ARIA controls ([role="button"]) use thearia-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
createdAtuses UTC while the rest of the app uses local dates.
new Date().toISOString().slice(0,10)is UTC-based, butdaysSince/todayISOoperate on local dates. Near midnight in negative-offset timezones this stores tomorrow's date, skewing the overdue calculation by a day.todayISOis 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 winDrag-to-reschedule silently drops per-occurrence
typeId,color, andreminder.The override written here only carries
title/start/end/contactId/location/notes/date. Butsave()'s single-occurrence path (ov = { ...draft.fields }) storestypeId,color, andreminderin 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 } : {}), };
| // 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 |
There was a problem hiding this comment.
🩺 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: Replacewindow.history.replaceState({}, '')withnavigate(location.pathname, { replace: true, state: {} })(using the already-instantiatednavigate).schedule-app/src/pages/PlannerPage.jsx#L106-L117: Import and instantiateuseNavigate, then replacewindow.history.replaceState({}, '')withnavigate(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.
| const addChecklistItem = () => { | ||
| setEditingNote((n) => ({ ...n, checklist: [...(n.checklist || []), { text: '', done: false }] })); | ||
| }; |
There was a problem hiding this comment.
🎯 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
| <button className="select-option" onClick={() => { setQuickAddOpen(false); document.querySelector('.task-add-row input')?.focus(); }}> | ||
| ✅ New task |
There was a problem hiding this comment.
📐 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.
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 bundle files are already tracked in this repository. Skipping generation of another bundle PR. |
There was a problem hiding this comment.
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 winData loss in bulk-shift when multiple occurrences of the same event are selected.
The
moveSelectedloop reads themasterevent from thestate.eventsclosure on every iteration. Becausestate.eventsdoesn't update until the next render, shifting multiple occurrences of the same recurring event causes successiveactions.updateEventdispatches 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
MorePageis 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 underpages/more/, withMorePagecomposing them and passing downstate/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 winAvoid mutating refs during the render phase.
Writing to
ref.currentduring 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
createdAtuses UTC while the rest of the app uses local dates.
new Date().toISOString().slice(0, 10)is UTC-based, butdaysSince/todayISOoperate on local dates. Near midnight in negative-offset timezones this stores tomorrow's date, skewing the overdue calculation by a day. Use the importedtodayISO()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 winReplace
window.history.replaceStatewith React Router'snavigate.Bypassing React Router to mutate history state leaves the router's internal
location.staterepresentation 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: Replacewindow.history.replaceState({}, '')withnavigate(location.pathname, { replace: true, state: {} })(using the already-instantiatednavigate).schedule-app/src/pages/PlannerPage.jsx#L108-L117: Make the samenavigate(location.pathname, { replace: true, state: {} })replacements in both quick-add branches (using the already-instantiatednavigate).🤖 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 winDrag-to-reschedule silently drops per-occurrence
typeId,color, andreminder.The override constructed here only preserves
title/start/end/contactId/location/notes/date. If an occurrence previously had customizedtypeId,color, orreminderfields (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 winConsider ignoring elements with
aria-disabled="true".The delegated haptic listener correctly ignores elements with the native
disabledproperty, but links (<a>) and custom ARIA controls ([role="button"]) use thearia-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
📒 Files selected for processing (8)
schedule-app/README.mdschedule-app/src/App.jsxschedule-app/src/data/ics.jsschedule-app/src/pages/ContactDetailPage.jsxschedule-app/src/pages/ContactsPage.jsxschedule-app/src/pages/MorePage.jsxschedule-app/src/pages/PlannerPage.jsxschedule-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.jsschedule-app/src/App.jsxschedule-app/src/pages/MorePage.jsxschedule-app/src/pages/ContactsPage.jsxschedule-app/src/pages/ContactDetailPage.jsxschedule-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.jsschedule-app/src/App.jsxschedule-app/src/pages/MorePage.jsxschedule-app/src/pages/ContactsPage.jsxschedule-app/src/pages/ContactDetailPage.jsxschedule-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.jsschedule-app/src/App.jsxschedule-app/src/pages/MorePage.jsxschedule-app/src/pages/ContactsPage.jsxschedule-app/src/pages/ContactDetailPage.jsxschedule-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.jsschedule-app/src/App.jsxschedule-app/src/pages/MorePage.jsxschedule-app/src/pages/ContactsPage.jsxschedule-app/src/pages/ContactDetailPage.jsxschedule-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.jsschedule-app/src/App.jsxschedule-app/src/pages/MorePage.jsxschedule-app/src/pages/ContactsPage.jsxschedule-app/src/pages/ContactDetailPage.jsxschedule-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.jsschedule-app/src/App.jsxschedule-app/src/pages/MorePage.jsxschedule-app/src/pages/ContactsPage.jsxschedule-app/src/pages/ContactDetailPage.jsxschedule-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 aboutconsole.logstatements in edited files
Check all modified files forconsole.logstatements 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 metUse Playwright as the E2E testing framework for critical user flows in TypeScript/JavaScript
Files:
schedule-app/src/data/ics.jsschedule-app/src/App.jsxschedule-app/src/pages/MorePage.jsxschedule-app/src/pages/ContactsPage.jsxschedule-app/src/pages/ContactDetailPage.jsxschedule-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.jsschedule-app/src/App.jsxschedule-app/src/pages/MorePage.jsxschedule-app/src/pages/ContactsPage.jsxschedule-app/src/pages/ContactDetailPage.jsxschedule-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.jsschedule-app/src/App.jsxschedule-app/src/pages/MorePage.jsxschedule-app/src/pages/ContactsPage.jsxschedule-app/src/pages/ContactDetailPage.jsxschedule-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.jsschedule-app/src/App.jsxschedule-app/src/pages/MorePage.jsxschedule-app/src/pages/ContactsPage.jsxschedule-app/src/pages/ContactDetailPage.jsxschedule-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.jsxschedule-app/src/pages/MorePage.jsxschedule-app/src/pages/ContactsPage.jsxschedule-app/src/pages/ContactDetailPage.jsxschedule-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-testcommands for React-specific work.
Files:
schedule-app/src/App.jsxschedule-app/src/pages/MorePage.jsxschedule-app/src/pages/ContactsPage.jsxschedule-app/src/pages/ContactDetailPage.jsxschedule-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.
importDatastill parses arbitrary file content and passes it straight toactions.importData(data)with no schema check, andreaderstill has noonerrorhandler. 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 winAdd a
reader.onerrorhandler for the .ics import.Same gap as the JSON backup import:
readerhas noonerror, 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 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 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 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; |
There was a problem hiding this comment.
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 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 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 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 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 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 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 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 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 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 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 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 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 bundle files are already tracked in this repository. Skipping generation of another bundle PR. |
|
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. |
What Changed
Why This Change
Testing Done
node tests/run-all.js)Type of Change
fix:Bug fixfeat:New featurerefactor:Code refactoringdocs:Documentationtest:Testschore:Maintenance/toolingci:CI/CD changesSecurity & Quality Checklist
If you changed dependencies or
package.json(bin/files/ deps)yarn install --mode=update-lockfileand committed theyarn.lockchange. CI runs Yarn in hardened mode on public PRs and fails if the lockfile would be modified, so an out of dateyarn.lockbreaks the build even when nothing else is wrong.If you added a skill, command, agent, hook, or CLI tool
package.json(binandfiles),manifests/install-components.json,manifests/install-modules.json, andagent.yamlnpm run catalog:sync) and command registry (npm run command-registry:write)README.md,COMMANDS-QUICK-REF.md,docs/COMMAND-AGENT-MAP.md)tests/scripts/npm-publish-surface.test.js).agents/skills/<name>/plusagents/openai.yaml; the Codex frontmatter validator allows onlyname,description,metadata,license,allowed-tools, so drop keys likeversionfrom that copy)npm test)Documentation