Change property to "" does not get saved in patches #45
Labels
No labels
Compat/Breaking
Kind/Bug
Kind/Documentation
Kind/Enhancement
Kind/Feature
Kind/Security
Kind/Testing
Priority
Critical
Priority
High
Priority
Low
Priority
Medium
Reviewed
Confirmed
Reviewed
Duplicate
Reviewed
Invalid
Reviewed
Won't Fix
Status
Abandoned
Status
Blocked
Status
Need More Info
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
gtfs.zone/coloring-book#45
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
schedule-controller.tscaptures thebeforevalue for trip property edits usinggetFileDataSync('trips')(missing.txtsuffix). Every other caller in the codebase uses'trips.txt'. The wrong key returns an empty array, socurrentTripis alwaysundefinedandbefore[field]is alwaysnull. When the user clears a field to"",processedValueconverts tonull, producingbefore = nullandafter = null—recordUpdatedetects no change and returns early without writing a patch. The notification fires unconditionally regardless, so the user sees a success message but nothing is recorded.This bug also silently corrupts undo for all trip property edits, not just the empty-string case: the inverse patch stores
nullas the original value rather than the real prior value, so undoing any trip property edit incorrectly nulls it out instead of restoring the previous string.The fix is a one-line change plus suppressing the spurious notification when nothing was actually changed.
Relevant context
src/modules/schedule-controller.ts:492— the brokengetFileDataSync('trips')call insideupdateTripProperty. Thebeforeobject is built here immediately before callingpatchUpdate.src/modules/patch-manager.ts:167–192—recordUpdatecomparesbefore[key] !== after[key]and returns early (line 181) if nothing changed. No exception is thrown — callers cannot tell whether a patch was created.src/utils/patch-utils.ts:42–45—patchUpdatealways callsdb.updateRowfirst, then callspm.recordUpdate. Becausedb.updateRowruns unconditionally, the DB value IS changed even when the patch is not recorded.src/modules/timetable-renderer.ts:341,357,373— the inlineonchangehandlers that invokeupdateTripPropertywiththis.value.src/utils/form-patch-bridge.ts:59–62— the correct pattern: capturesbeforeasString(row?.[field] ?? ''), compares strings, and early-returns if unchanged before callingrecordUpdate.getFileDataSynccallers (gtfs-relationships.ts, timetable-database.ts, info-display.ts) correctly use the.txtsuffix.Phase 1 — Fix the before-value capture and notification
Goal: correct the root cause so
beforereflects the actual stored value, and suppress the spurious notification when the value is unchanged.src/modules/schedule-controller.ts:492, changegetFileDataSync('trips')→getFileDataSync('trips.txt')beforeand computingprocessedValue, add an early-return guard: if the processed new value strictly equals the stored before value, show no notification and return (mirrors the pattern inform-patch-bridge.ts:61)direction_id,wheelchair_accessible,bikes_allowed): in both branches, compareprocessedValueagainstcurrentTrip?.[field] ?? nullbefore proceedingImplemented in commit
214ec73. The guard usesprocessedValue === storedValue(strict equality), which correctly handles both numeric enum fields and null/string comparisons. The stored value is captured ascurrentTrip?.[field] ?? nullmatching the same default used forbefore.Phase 2 — Fix stop time edits not appearing in Changes
Goal: make arrival/departure time edits in the timetable produce visible patches in the Changes panel, and correctly record the inverse so undo restores the prior value.
Root cause:
updateArrivalDepartureTimeandupdateLinkedTimeboth callrebuildStopTimesFromTablefollowed byrefreshCurrentTimetable, then try to find the updated row by readingafterInput.dataset.stopSequencefrom the re-rendered DOM.dataset.stopSequenceis always a string (e.g."2"), butrebuildStopTimesFromTablestoresstop_sequenceas a JavaScript number (index + 1). The virtual table's query filter uses strict equality (r['stop_sequence'] === v), so1 === "1"isfalse.getStopTimereturnsnull, theif (beforeRow && afterStopTime && this.patchManager)guard is never entered, and no patch is recorded.Relevant context:
src/modules/timetable-database.ts:422—getStopTime: whenstop_sequenceis provided, it passes it directly intoqueryRowswithout coercion; the string-vs-number mismatch silently returns no results.src/modules/timetable-database.ts:588—rebuildStopTimesFromTable: assignsstop_sequence: index + 1(number) on the new rows.src/modules/schedule-controller.ts:488–509—updateArrivalDepartureTime: readsafterInput.dataset.stopSequence(string) and passes it togetStopTime.src/modules/schedule-controller.ts:322–350—updateLinkedTime(set branch): same pattern.src/modules/gtfs-parser.ts:246–252— virtual tablequery: uses strict===comparison for filter values not covered by a fieldMap (stop_sequence has no fieldMap, onlytrip_idandstop_iddo).Steps:
src/modules/timetable-database.ts, ingetStopTime(line 435), whenstop_sequenceis provided, coerce it to a number before passing toqueryRows:stop_sequence: Number(stop_sequence). This matches the type stored byrebuildStopTimesFromTable.updateArrivalDepartureTimeandupdateLinkedTimenow produce entries in the Changes panel after editing a time.Implemented in commit
9e88350. One-line fix:stop_sequence: Number(stop_sequence)ingetStopTime. The strict-equality mismatch between dataset strings and stored numbers was the sole reason patches were never recorded for stop time edits.Phase 3 — Fix duplicate notifications on arrival/departure time edits
Goal: stop the user from seeing two notifications per time edit (one "Time updated" toast from
timetable-database.ts, one from thepatchManager'change'event viaindex.ts).Root cause:
timetable-database.ts:updateStopTimeInDatabasefiresnotifications.showSuccess("Time updated to …")(line 295) unconditionally, andupdateLinkedTimesfiresnotifications.showSuccess("Linked time updated to …")(line 407). After these calls return,schedule-controller.tscallspatchManager.recordUpdate(...), which emits'change', andindex.ts:171fires a second notification (notifications.showInfo(humanLabel(r?.patch), ...)). The result is two toasts per edit.Relevant context:
src/modules/timetable-database.ts:295—notifications.showSuccessat end ofupdateStopTimeInDatabase.src/modules/timetable-database.ts:407—notifications.showSuccessat end ofupdateLinkedTimes.src/index.ts:169–171— thepatchManager.on('change', ...)handler that firesnotifications.showInfo(humanLabel(r?.patch), ...)for every recorded patch. This is the canonical success notification for all patch-backed edits.Steps:
notifications.showSuccess(...)call fromtimetable-database.ts:updateStopTimeInDatabase(line 295). Success feedback is already provided by thepatchManager'change'event handler inindex.ts.notifications.showSuccess(...)call fromtimetable-database.ts:updateLinkedTimes(line 407) for the same reason.Implemented in commit
ea41124. Removed bothshowSuccesscalls; the canonicalpatchManager'change'handler inindex.tsnow provides the sole notification for time edits.Phase 4 — Record patch when toggling from unlinked to linked
Goal: when the user clicks the link button to switch from separate arrival/departure inputs to a single linked input (setting departure = arrival), record an undo-able patch for the departure time change.
Root cause:
swapToLinkedInput(schedule-controller.ts:597) callsthis.database.updateLinkedTimes(trip_id, stop_id, primaryTime)whenarrival_time !== departure_time, but never callspatchManager.recordUpdate(). The DB write happens with no patch entry, so the change is invisible in the Changes panel and undo cannot restore the previous departure time.Relevant context:
src/modules/schedule-controller.ts:597–641—swapToLinkedInput: capturesstopTimefrom DB (line 612), determinesprimaryTime(line 618), callsupdateLinkedTimes(line 635), but makes no patch call.src/modules/schedule-controller.ts:333–350—updateLinkedTime(set branch): the correct pattern — capturesbeforeRow, callsupdateLinkedTimes, then callspatchManager.recordUpdatewith{ arrival_time, departure_time }before/after.departure_time !== primaryTime(i.e. the DB was actually changed). If arrival and departure were already equal, there is nothing to record.Steps:
swapToLinkedInput, before callingupdateLinkedTimes, capturebeforeDepartureTime = stopTime?.departure_timeandbeforeArrivalTime = stopTime?.arrival_time.updateLinkedTimes, ifprimaryTimeis set andthis.patchManageris available and the stored times actually changed (i.e.beforeArrivalTime !== primaryTime || beforeDepartureTime !== primaryTime), look up the updated stop time and callpatchManager.recordUpdatewith the before/after{ arrival_time, departure_time }— mirroring the pattern inupdateLinkedTimeset branch (lines 333–350).Implemented in commit
fa80330. The existingarrival_timeanddeparture_timevariables captured at lines 614–615 serve as the before-state. The patch guard usesarrival_time !== primaryTime || departure_time !== primaryTimeto skip no-op toggles (when times were already equal).Original Issue
At least for trip shape and I think other properties