Skip to content

fix: keep edit modals open when a change is rejected - #18

Draft
dsnsgithub wants to merge 2 commits into
mainfrom
claude/mobile-app-friction-points-51n3yv
Draft

fix: keep edit modals open when a change is rejected#18
dsnsgithub wants to merge 2 commits into
mainfrom
claude/mobile-app-friction-points-51n3yv

Conversation

@dsnsgithub

@dsnsgithub dsnsgithub commented Aug 20, 2026

Copy link
Copy Markdown
Owner

Addresses three friction points reported from within the mobile app.

All three share one root cause on the event/routine side: the modals called setModalVisible(false) unconditionally, so a rejected write looked identical to a silent failure — the sheet closed, the user's input was gone, and nothing had changed.

Event overlaps and invalid times

AddEventModal and EventModal both dismissed themselves right after calling their save function, whether or not validation passed. The user had to reopen the modal and re-enter the name and both times from scratch, guided only by "This event overlaps with another event or has an invalid start/end time" — which never said which event it collided with.

EventModal was worse: it called props.setStartTime / props.setEndTime before attempting the save, so a rejected edit left the event row in the routine list displaying times that were never written to storage.

  • The duplicated areEventsValid / createCustomTime pair in both modals is replaced by a shared findEventConflict in src/utils/eventValidation.ts, which returns the offending pair ("Lunch" overlaps with "5th Period"…) instead of a bare boolean.
  • Both save functions now return success, and the modal only closes on success.
  • EventModal applies the new times to the parent only after the save is accepted.

The shared helper also drops the old events.length <= 1 early return, so a lone event's start/end is checked like any other. Both modals already blocked start >= end at the picker and on Finish, so nothing that used to save stops saving.

Routine missing after creation

createNewRoutine rejects a duplicate name, and the pre-filled name is always "New Routine!" — so creating a second routine without renaming it hit the duplicate branch, the modal closed anyway, and no routine appeared in the list.

  • Failure keeps the modal open; the message names the routine that already exists.
  • The form resets after a successful create, so the next routine doesn't start out pre-filled with a name that is now taken.
  • createNewRoutine deep copies the schedule before mutating. It previously used { ...schedule }, which shares the routines object with the existing state, so the pre-update object was mutated in place. The other writers here (addEvent, modifyEventTimes) already deep copy.

Dark mode toggle

app/_layout.tsx called Appearance.setColorScheme() and setColorScheme() in the render body, on every render. That re-entered render from render and remounted NativeTabs, which resets navigation state — the toggle would bounce the user out of the screen they were on, and the re-applied stored value could land back on the scheme they started from.

  • Root layout applies the stored scheme once, from an effect.
  • The toggle writes storage first, then sets Appearance and the nativewind scheme, so nothing re-reads a stale value mid-flight.
  • StatusBar reads the reactive colorScheme instead of storage.getString(...), so it flips with the rest of the UI.

Not addressed

The countdown-timer engagement note is an observation rather than a defect, so nothing changed there.

Testing

bun run lint, bun run format, and tsc --noEmit in apps/mobile all pass locally, and all CI checks are green on this branch.

The meaningful coverage comes from test-ios, which builds the app, boots an iPhone 17 Pro simulator, and runs the whole Maestro suite. Two of those flows exercise this diff directly:

  • darkMode.yaml toggles dark mode and then walks Settings → General → Back → Routines → Back — the navigation path the render-phase setColorScheme() was resetting.
  • editRoutine.yaml opens an event, renames it, and saves through the EventModal Finish button that now gates on the save succeeding.

Worth flagging for reviewers: test-android only builds the Android simulator app, it does not run Maestro. So the dark mode fix is verified on iOS but not on Android, where NativeTabs and Appearance are backed by a different native implementation. The manual repro for that one is: toggle dark mode from a nested settings screen and confirm you stay put and the theme actually flips.

🤖 Generated with Claude Code

https://claude.ai/code/session_01XN5J6bUa9anKhYUSfcnpiz

Event and routine modals dismissed themselves regardless of whether the
write succeeded, so a rejected change looked like it silently vanished.

- Add/edit event: report which two events overlap instead of a generic
  message, and keep the modal open so times can be corrected in place.
  Editing also no longer pushes the rejected times into the event row,
  which previously left the list showing times that were never saved.
- Add routine: a duplicate name (the pre-filled "New Routine!" being the
  common case) closed the modal without creating anything, so the routine
  never appeared in the list. Keep the modal open on failure and reset the
  form after a successful create. Deep copy the schedule before mutating
  it so the previous state object stays intact.
- Dark mode: the root layout applied the stored scheme during render on
  every pass, remounting the tab navigator and bouncing the user out of
  the screen they were on. Apply it once in an effect, set Appearance from
  the toggle itself, and drive the status bar from the reactive value.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XN5J6bUa9anKhYUSfcnpiz
@vercel

vercel Bot commented Aug 20, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
scheduli Ready Ready Preview Aug 20, 2026 11:29pm

@dsnsgithub
dsnsgithub marked this pull request as draft August 20, 2026 12:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants