feat(website): support multiple saved timetables per semester - #4460
feat(website): support multiple saved timetables per semester#4460eugenewong22 wants to merge 1 commit into
Conversation
|
@eugenewong22 is attempting to deploy a commit to the modsbot's projects Team on Vercel. A member of the Team first needs to authorize it. |
Add "save slots" per semester (nusmodifications#4455): a tab switcher lets users keep several timetable arrangements and switch between them instantly, create blank or duplicated slots, and rename/delete via undoable actions. Shared timetable links can now import into a new slot instead of overwriting the saved one. The active timetable stays in the existing lessons/colors/hidden/ta maps, so existing timetable actions, selectors, undo, share and export are unchanged. Inactive arrangements live in a new `slots` map keyed by semester; switching snapshots the outgoing slot and loads the target's data. A redux-persist v3 migration adds the new (empty) maps for existing users, who implicitly keep their current timetable as "Timetable 1". Slot modules are pinned against module bank LRU eviction and prefetched before a switch loads them.
b3df192 to
cca1f2c
Compare
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## master #4460 +/- ##
==========================================
+ Coverage 54.52% 59.79% +5.26%
==========================================
Files 274 317 +43
Lines 6076 7486 +1410
Branches 1455 1846 +391
==========================================
+ Hits 3313 4476 +1163
- Misses 2763 3010 +247 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
| dispatch(addTimetableSlot(semester, { title: 'Imported' })); | ||
| importTimetable(); |
There was a problem hiding this comment.
Undo leaves an extra slot
addTimetableSlot is dispatched before the watched SET_TIMETABLE import action. Undo therefore restores the state after the slot was created: the imported lessons are removed but the new empty, active Imported slot remains. This makes the import notification's Undo action fail to restore the prior saved-timetable state; make the combined import atomic for undo, or remove the new slot when its import is undone.
Knowledge Base Used:
Artifacts
Shared timetable Undo flow test
- Focused React and Redux test that loads a shared timetable, imports it into a new slot, invokes the real notification Undo handler, and compares the resulting slot state with the saved pre-import state — the equality assertion is expected to fail.
Undo retains Imported timetable slot
- Verbose Vitest output for the shared-timetable flow showing the state before import, after import, and after Undo — Undo leaves active Imported slot 2 while clearing its live lessons.
Ran code and verified through T-Rex
Prompt To Fix With AI
This is a comment left during a code review.
Path: website/src/views/timetable/TimetableContainer.tsx
Line: 112-113
Comment:
**Undo leaves an extra slot**
`addTimetableSlot` is dispatched before the watched `SET_TIMETABLE` import action. Undo therefore restores the state after the slot was created: the imported lessons are removed but the new empty, active `Imported` slot remains. This makes the import notification's `Undo` action fail to restore the prior saved-timetable state; make the combined import atomic for undo, or remove the new slot when its import is undone.
**Knowledge Base Used:**
- [Client state and persistence](https://app.greptile.com/nusmods/-/custom-context/knowledge-base/nusmodifications/nusmods/-/docs/client-state-and-persistence.md)
- [Timetable experience](https://app.greptile.com/nusmods/-/custom-context/knowledge-base/nusmodifications/nusmods/-/docs/timetable-experience.md)
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.
Context
Closes the "save slot" half of #4455: users want to keep multiple timetable arrangements per semester and switch between them without screenshotting before every change. (A follow-up PR adds the side-by-side comparison view — see the issue for the full plan.)
What this does
+menu adds a new blank timetable or duplicates the current one.⋯menu with modal dialogs (followingResetTimetable's pattern). Deleting is undoable via the existing undo notification; the last remaining slot can't be deleted.Implementation
TimetablesState.{lessons,colors,hidden,ta}maps, so the ~15 existing timetable actions, all selectors, undo, share and export code are untouched. A newslots: { [semester]: TimetableSlot[] }array stores arrangements;SWITCH_TIMETABLE_SLOTsnapshots the live maps into the outgoing slot and loads the target's data (reducers/timetables.ts).datais authoritative for inactive slots only — the active slot's data lives in the live maps and its snapshot is refreshed on switch-away. All reads go through the newgetSlotTimetableDataselector.slotsentry implicitly have one active slot ("Timetable 1"). The redux-persist migration (migrateV2toV3, following the planner's exported-migration pattern) only adds two empty maps — no user data is rewritten.DELETE_TIMETABLE_SLOTjoinsactionsToWatch. Switching/adding is deliberately not watched — switching back is lossless, and withlimit: 1it would clobber the single undo step.getPinnedLessonConfigs), and the switch/delete thunks fetch the incoming slot's modules before loading it into the live timetable.Testing
reducers/timetables.test.ts).pnpm run cipasses (lint, typecheck, 800+ tests, build).