Skip to content

Fix issue row swipe actions and scrollbars - #66

Merged
robin-liquidium merged 4 commits into
mainfrom
codex/fix-issue-row-scrollers-reminder-swipe
Aug 5, 2026
Merged

robin-liquidium merged 4 commits into
mainfrom
codex/fix-issue-row-scrollers-reminder-swipe

Conversation

@robin-liquidium

@robin-liquidium robin-liquidium commented Aug 5, 2026

Copy link
Copy Markdown
Owner

Summary\n- replace per-issue native horizontal scroll views with a bounded reveal row so horizontal scrollbars cannot appear\n- keep the trash action on the right while using the manually verified natural trackpad reveal direction\n- add the same confirmation-gated delete action to writable Apple Reminders\n- add scoped diagnostics for unexpected horizontal scrollers\n- cover Linear and Reminder swipe reveal, actual trash clicks, confirmation, deletion, and scrollbar absence in UI tests\n\n## Validation\n- Building for debugging...

Build complete! (0.40 sec)\n- Test Suite 'All tests' started at 2026-08-06 01:06:15.570.
Test Suite 'All tests' passed at 2026-08-06 01:06:15.571.
Executed 0 tests, with 0 failures (0 unexpected) in 0.000 (0.001) seconds
◇ Test run started.
↳ Testing Library Version: 2070
↳ Target Platform: arm64e-apple-macos14.0
◇ Suite AppleReminderModelsTests started.
◇ Suite "Linear team estimate options" started.
◇ Suite GlobalShortcutTests started.
◇ Suite UpdateServiceTests started.
◇ Suite AppleCalendarServiceTests started.
◇ Suite MenuKeyboardMonitorTests started.
◇ Suite OAuthSessionTests started.
◇ Suite DiagnosticsServiceTests started.
◇ Suite LinearAccountRepositoryTests started.
◇ Suite GoogleAccountRepositoryTests started.
◇ Suite MockDataTests started.
◇ Suite LocalNoteItemTests started.
◇ Suite LocalNotesServiceTests started.
◇ Suite "Linear issue create draft" started.
◇ Suite AppleReminderStatusStoreTests started.
◇ Suite AppleRemindersServiceTests started.
◇ Test "Teams without estimation offer no estimate options" started.
◇ Suite GoogleAccountTests started.
◇ Test newlyDiscoveredCalendarsStayEnabled() started.
◇ Test displayStringShowsModifiersAndKey() started.
◇ Test "Zero estimates are prepended when allowed" started.
◇ Suite MeetingAlertTests started.
◇ Test "Extended scales add two larger values" started.
◇ Test "T-shirt scales use size labels on fibonacci values" started.
◇ Suite AppRuntimeConfigTests started.
◇ Test disabledInjectedUpdaterDoesNotInvokeConfiguredCheckAction() started.
◇ Test canceledPendingInstallNoLongerKeepsReminder() started.
◇ Test enabledInjectedUpdaterInvokesConfiguredCheckAction() started.
◇ Test accountIDIsStableForEventIdentity() started.
◇ Test unauthorizedAccessReturnsEmptyCalendarsAndEvents() started.
◇ Test allDefaultsAndFallbacksArePairwiseUnique() started.
◇ Test "Estimation scales match Linear's documented ranges" started.
◇ Suite GitHubAccountRepositoryTests started.
◇ Test "Zero estimates keep t-shirt labels aligned" started.
◇ Suite MarkdownRenderingIntegrationTests started.
◇ Test restoresExplicitlyDisabledCalendarsIncludingAllDisabled() started.
◇ Test cancelingAnotherVersionDoesNotClearPendingInstall() started.
◇ Test defaultsUseControlOptionCommand() started.
◇ Test defaultMockUpdaterRemainsUnavailable() started.
◇ Test emptyHoverShortcutInputNeverMatchesAnAction() started.
◇ Test finishingSessionClearsReminderWithoutPendingInstall() started.
◇ Test preservesSpaceForKeyboardFocusedActionableControls() started.
◇ Test defaultsDoNotConflictWithEachOther() started.
◇ Test ignoresKeyWhenMenuWindowIsGone() started.
◇ Test resumeWithoutStartDoesNotInstall() started.
◇ Test handlesPlainKeyWhenMenuWindowIsVisible() started.
◇ Test installedCredentialsCanBeTransferredAndDiscarded() started.
◇ Test ignoresKeyWhileEditingText() started.
◇ Test eventWithoutCommandControlOrOptionIsRejected() started.
◇ Test ignoresKeyWithDisallowedModifiers() started.
◇ Suite CalendarEventItemTests started.
◇ Test doesNotPreserveHoverShortcutsWithoutActionableFocus() started.
◇ Test diagnosticLogCopiesTheStableRingNames() started.
◇ Test crashReportStagingSkipsTruncatedAndUnreadableCandidates() started.
◇ Test codableRoundTrip() started.
◇ Test finishingSessionKeepsReminderForPendingInstallOnQuit() started.
◇ Test UItestRunIDAllowsOnlyShortOpaqueTokens() started.
◇ Test skippedReportsNeverRemoveAPreexistingCrashDirectory() started.
◇ Test resumeReinstallsTheMonitorAfterStop() started.
◇ Test allowsShiftForUppercaseShortcuts() started.
◇ Test temporarilyUndiscoveredListSelectionsRemainPersisted() started.
◇ Test dateOnlyReminderAlwaysUsesGregorianCalendarInUserTimeZone() started.
◇ Test feedbackAttachmentDisclosesThePublicUpload() started.
◇ Test feedbackArchiveTrimsOldestReportsWhileManualExportKeepsAll() started.
◇ Test changingTimedReminderDayPreservesTimeAndTimeZone() started.
◇ Test ignoresKeyWhenMenuWindowIsHidden() started.
◇ Test legacyMigrationReturnsCommittedAccountWhenCleanupFails() started.
◇ Test fallbackSelectionKeyDoesNotCollideWhenNamesContainDelimiters() started.
◇ Test dateOnlyReminderDueYesterdayIsOverdue() started.
◇ Test reconciliationPreservesSelectionsDropsMissingAndEnablesNewTeams() started.
◇ Test eventCaptureKeepsKeyCodeAndModifiers() started.
◇ Test diagnosticLogRotatesAndKeepsTheNewestEntry() started.
◇ Test persistedHoverShortcutCollisionIsRepairedWithoutChangingExistingActions() started.
◇ Test dateOnlyCreationUsesGregorianDayFromNonGregorianUserCalendar() started.
◇ Test legacyMigrationWritesScopedCredentialsBeforeDeletingLegacyAndIsIdempotent() started.
◇ Test completedLegacyMigrationDoesNotDuplicateAnAccountAfterIdentityDiscovery() started.
◇ Test eventKitPrioritiesNormalizeAcrossTheFullSupportedRange() started.
◇ Test timedDayReplacementUsesGregorianPickerDayInUserTimeZone() started.
◇ Test markdownOnlyFirstLineRemainsUntitled() started.
◇ Test crashReportStagingUsesActualBytesAndKeepsOlderEligibleReports() started.
◇ Test everyIssueProviderCombinationHasStableTabOrder() started.
◇ Test crashReportDiscoveryUsesBundleIdentifierAndRecency() started.
◇ Test mockCanRepresentRemindersAsTheOnlyIssueProvider() started.
◇ Test dateOnlyReminderDueTodayIsNotOverdue() started.
◇ Test emptyFirstLineRemainsUntitled() started.
◇ Test titleUsesOnlyTheFirstLine() started.
◇ Test repositoryRoundTripPreservesAnExplicitEmptySelection() started.
◇ Test "Due dates use Gregorian calendar days in the current-style time zone" started.
◇ Test staleDisconnectedLoadCannotClearNewerReminderSnapshot() started.
◇ Test titleUsesRenderedMarkdownText() started.
◇ Test timedReminderEarlierTodayIsOverdue() started.
◇ Test listSelectionsSurviveIdentifierChangesOnlyWhenFallbackIsUnique() started.
◇ Test complexMarkdownRoundTripsWithoutModification() started.
◇ Test tomorrowEventsStayWithinTomorrowLateInTheDay() started.
◇ Test timedReminderLaterTodayIsNotOverdue() started.
◇ Test "Due dates are omitted until selected and encoded without a timestamp" started.
◇ Test unauthorizedStoreDegradesWithoutReadingReminderData() started.
◇ Test nonPositiveCrashReportCountProducesNoReportsOrStagingDirectory() started.
◇ Test replacingTimedDueDayPreservesFloatingTimeZoneAndVisibleDay() started.
◇ Test reminderPreviewOnlyAllowsWebURLs() started.
◇ Test inertServiceNeverExposesEventKitData() started.
◇ Test replacingTimedDueDayPreservesExplicitTimeZone() started.
◇ Test catalogReconciliationPreservesSelectionsAndUsesGoogleDefaultsForNewCalendars() started.
◇ Test snoozeDurationUsesFiveMinutesAndClampsStoredValues() started.
◇ Test explicitSnoozeExtendsEligibilityWithoutOutlivingTheMeeting() started.
◇ Test configuredStorageNamespaceUsesNonEmptyBundleValue() started.
◇ Test authoritativeOAuthConfigurationIgnoresInheritedEnvironment() started.
◇ Test configuredStorageNamespacePreservesShippedFallback() started.
◇ Test productionOAuthConfigurationPreservesEnvironmentOverride() started.
◇ Test reconciliationPreservesSelectionsDropsMissingAndEnablesNewRepositories() started.
◇ Test repositoryRoundTripPreservesAnExplicitEmptySelection() started.
◇ Test complexMarkdownUsesEngineRenderedSemanticsWithoutLosingUnicode() started.
◇ Test noteEditorUsesProductionFont() started.
✔ Test accountIDIsStableForEventIdentity() passed after 0.010 seconds.
✔ Test allDefaultsAndFallbacksArePairwiseUnique() passed after 0.010 seconds.
✔ Test defaultsUseControlOptionCommand() passed after 0.010 seconds.
◇ Test mergedAgendaKeepsUsefulFieldsAndDistinctSourcesFromDuplicateCopies() started.
◇ Test agendaSectionsIncludesAnOvernightEventInTomorrow() started.
◇ Test mergedAgendaKeepsRecurringOccurrencesSeparate() started.
◇ Test menuBarCandidateIncludesUpcomingEventAtLeadTimeBoundary() started.
◇ Test mergedAgendaCollapsesSharedMeetingOccurrenceAndCombinesSources() started.
✔ Test "Teams without estimation offer no estimate options" passed after 0.010 seconds.
◇ Test compositeIDsRemainUniqueAcrossAccountsAndCalendars() started.
◇ Test partialSourceFailureRetainsSuccessfulEvents() started.
✔ Test defaultsDoNotConflictWithEachOther() passed after 0.010 seconds.
◇ Test menuBarCandidateReturnsNothingOutsideDisplayWindow() started.
◇ Test agendaSectionsGloballySortsAndAppliesDayLimits() started.
◇ Test totalSourceFailurePreservesThePreviousAgenda() started.
◇ Test appleSuccessDoesNotReplaceFailedGoogleEvents() started.
◇ Test mergedAgendaDoesNotGuessDuplicatesWithoutICalendarUID() started.
◇ Test rebuildingAfterSourceRemovalUsesTheRemainingSourcePayload() started.
◇ Test menuBarCandidateKeepsActiveEventAheadOfUpcomingEvent() started.
✔ Test "T-shirt scales use size labels on fibonacci values" passed after 0.011 seconds.
✔ Test restoresExplicitlyDisabledCalendarsIncludingAllDisabled() passed after 0.010 seconds.
✔ Test "Estimation scales match Linear's documented ranges" passed after 0.010 seconds.
✔ Test newlyDiscoveredCalendarsStayEnabled() passed after 0.011 seconds.
✔ Test "Extended scales add two larger values" passed after 0.011 seconds.
✔ Test "Zero estimates are prepended when allowed" passed after 0.011 seconds.
✔ Test "Zero estimates keep t-shirt labels aligned" passed after 0.010 seconds.
✔ Test codableRoundTrip() passed after 0.010 seconds.
✔ Test installedCredentialsCanBeTransferredAndDiscarded() passed after 0.010 seconds.
✔ Test UItestRunIDAllowsOnlyShortOpaqueTokens() passed after 0.010 seconds.
✔ Test dateOnlyReminderDueYesterdayIsOverdue() passed after 0.010 seconds.
✔ Test changingTimedReminderDayPreservesTimeAndTimeZone() passed after 0.010 seconds.
✔ Test dateOnlyReminderAlwaysUsesGregorianCalendarInUserTimeZone() passed after 0.010 seconds.
✔ Test fallbackSelectionKeyDoesNotCollideWhenNamesContainDelimiters() passed after 0.010 seconds.
✔ Test reconciliationPreservesSelectionsDropsMissingAndEnablesNewTeams() passed after 0.010 seconds.
✔ Test eventKitPrioritiesNormalizeAcrossTheFullSupportedRange() passed after 0.010 seconds.
✔ Test timedDayReplacementUsesGregorianPickerDayInUserTimeZone() passed after 0.010 seconds.
✔ Test dateOnlyCreationUsesGregorianDayFromNonGregorianUserCalendar() passed after 0.010 seconds.
✔ Test eventWithoutCommandControlOrOptionIsRejected() passed after 0.011 seconds.
✔ Test skippedReportsNeverRemoveAPreexistingCrashDirectory() passed after 0.010 seconds.
✔ Test eventCaptureKeepsKeyCodeAndModifiers() passed after 0.011 seconds.
✔ Test everyIssueProviderCombinationHasStableTabOrder() passed after 0.010 seconds.
✔ Test diagnosticLogRotatesAndKeepsTheNewestEntry() passed after 0.010 seconds.
✔ Test emptyFirstLineRemainsUntitled() passed after 0.010 seconds.
✔ Test mockCanRepresentRemindersAsTheOnlyIssueProvider() passed after 0.010 seconds.
✔ Test legacyMigrationReturnsCommittedAccountWhenCleanupFails() passed after 0.010 seconds.
✔ Test dateOnlyReminderDueTodayIsNotOverdue() passed after 0.010 seconds.
✔ Test completedLegacyMigrationDoesNotDuplicateAnAccountAfterIdentityDiscovery() passed after 0.010 seconds.
✔ Test legacyMigrationWritesScopedCredentialsBeforeDeletingLegacyAndIsIdempotent() passed after 0.010 seconds.
✔ Test timedReminderEarlierTodayIsOverdue() passed after 0.010 seconds.
✔ Test "Due dates use Gregorian calendar days in the current-style time zone" passed after 0.010 seconds.
✔ Test listSelectionsSurviveIdentifierChangesOnlyWhenFallbackIsUnique() passed after 0.010 seconds.
✔ Test tomorrowEventsStayWithinTomorrowLateInTheDay() passed after 0.010 seconds.
✔ Test timedReminderLaterTodayIsNotOverdue() passed after 0.011 seconds.
✔ Test "Due dates are omitted until selected and encoded without a timestamp" passed after 0.010 seconds.
✔ Test crashReportStagingSkipsTruncatedAndUnreadableCandidates() passed after 0.011 seconds.
✔ Test diagnosticLogCopiesTheStableRingNames() passed after 0.011 seconds.
✔ Test complexMarkdownRoundTripsWithoutModification() passed after 0.011 seconds.
✔ Test temporarilyUndiscoveredListSelectionsRemainPersisted() passed after 0.011 seconds.
✔ Test emptyHoverShortcutInputNeverMatchesAnAction() passed after 0.011 seconds.
✔ Test persistedHoverShortcutCollisionIsRepairedWithoutChangingExistingActions() passed after 0.011 seconds.
✔ Test catalogReconciliationPreservesSelectionsAndUsesGoogleDefaultsForNewCalendars() passed after 0.003 seconds.
✔ Test crashReportDiscoveryUsesBundleIdentifierAndRecency() passed after 0.011 seconds.
✔ Test configuredStorageNamespaceUsesNonEmptyBundleValue() passed after 0.002 seconds.
✔ Test authoritativeOAuthConfigurationIgnoresInheritedEnvironment() passed after 0.002 seconds.
✔ Test productionOAuthConfigurationPreservesEnvironmentOverride() passed after 0.002 seconds.
✔ Test reconciliationPreservesSelectionsDropsMissingAndEnablesNewRepositories() passed after 0.002 seconds.
✔ Test repositoryRoundTripPreservesAnExplicitEmptySelection() passed after 0.011 seconds.
✔ Test mergedAgendaKeepsUsefulFieldsAndDistinctSourcesFromDuplicateCopies() passed after 0.001 seconds.
✔ Test configuredStorageNamespacePreservesShippedFallback() passed after 0.002 seconds.
✔ Test mergedAgendaKeepsRecurringOccurrencesSeparate() passed after 0.001 seconds.
✔ Test menuBarCandidateIncludesUpcomingEventAtLeadTimeBoundary() passed after 0.001 seconds.
✔ Suite "Linear team estimate options" passed after 0.012 seconds.
✔ Test agendaSectionsGloballySortsAndAppliesDayLimits() passed after 0.001 seconds.
✔ Test compositeIDsRemainUniqueAcrossAccountsAndCalendars() passed after 0.001 seconds.
✔ Test menuBarCandidateReturnsNothingOutsideDisplayWindow() passed after 0.001 seconds.
✔ Test mergedAgendaCollapsesSharedMeetingOccurrenceAndCombinesSources() passed after 0.001 seconds.
✔ Test agendaSectionsIncludesAnOvernightEventInTomorrow() passed after 0.001 seconds.
✔ Test menuBarCandidateKeepsActiveEventAheadOfUpcomingEvent() passed after 0.001 seconds.
✔ Test mergedAgendaDoesNotGuessDuplicatesWithoutICalendarUID() passed after 0.001 seconds.
✔ Suite OAuthSessionTests passed after 0.012 seconds.
✔ Test rebuildingAfterSourceRemovalUsesTheRemainingSourcePayload() passed after 0.001 seconds.
✔ Suite MockDataTests passed after 0.012 seconds.
✔ Suite "Linear issue create draft" passed after 0.012 seconds.
✔ Suite LocalNotesServiceTests passed after 0.012 seconds.
✔ Suite GoogleAccountTests passed after 0.012 seconds.
✔ Suite AppRuntimeConfigTests passed after 0.012 seconds.
✔ Suite AppleReminderModelsTests passed after 0.012 seconds.
✔ Test nonPositiveCrashReportCountProducesNoReportsOrStagingDirectory() passed after 0.011 seconds.
✔ Suite GoogleAccountRepositoryTests passed after 0.012 seconds.
✔ Suite LinearAccountRepositoryTests passed after 0.012 seconds.
✔ Test markdownOnlyFirstLineRemainsUntitled() passed after 0.011 seconds.
✔ Test repositoryRoundTripPreservesAnExplicitEmptySelection() passed after 0.003 seconds.
✔ Suite GitHubAccountRepositoryTests passed after 0.013 seconds.
✔ Test titleUsesOnlyTheFirstLine() passed after 0.012 seconds.
✔ Test crashReportStagingUsesActualBytesAndKeepsOlderEligibleReports() passed after 0.013 seconds.
✔ Test titleUsesRenderedMarkdownText() passed after 0.014 seconds.
✔ Suite LocalNoteItemTests passed after 0.015 seconds.
✔ Test feedbackAttachmentDisclosesThePublicUpload() passed after 0.020 seconds.
✔ Test unauthorizedStoreDegradesWithoutReadingReminderData() passed after 0.049 seconds.
✔ Test inertServiceNeverExposesEventKitData() passed after 0.049 seconds.
✔ Test snoozeDurationUsesFiveMinutesAndClampsStoredValues() passed after 0.041 seconds.
✔ Test explicitSnoozeExtendsEligibilityWithoutOutlivingTheMeeting() passed after 0.041 seconds.
✔ Suite MeetingAlertTests passed after 0.050 seconds.
✔ Test partialSourceFailureRetainsSuccessfulEvents() passed after 0.040 seconds.
✔ Test totalSourceFailurePreservesThePreviousAgenda() passed after 0.040 seconds.
✔ Test appleSuccessDoesNotReplaceFailedGoogleEvents() passed after 0.040 seconds.
✔ Suite CalendarEventItemTests passed after 0.051 seconds.
✔ Test displayStringShowsModifiersAndKey() passed after 0.056 seconds.
✔ Suite GlobalShortcutTests passed after 0.056 seconds.
✔ Test unauthorizedAccessReturnsEmptyCalendarsAndEvents() passed after 0.082 seconds.
✔ Suite AppleCalendarServiceTests passed after 0.083 seconds.
✔ Test canceledPendingInstallNoLongerKeepsReminder() passed after 0.150 seconds.
✔ Test enabledInjectedUpdaterInvokesConfiguredCheckAction() passed after 0.151 seconds.
✔ Test disabledInjectedUpdaterDoesNotInvokeConfiguredCheckAction() passed after 0.151 seconds.
✔ Test defaultMockUpdaterRemainsUnavailable() passed after 0.153 seconds.
✔ Test cancelingAnotherVersionDoesNotClearPendingInstall() passed after 0.153 seconds.
✔ Test preservesSpaceForKeyboardFocusedActionableControls() passed after 0.153 seconds.
✔ Test ignoresKeyWhileEditingText() passed after 0.153 seconds.
✔ Test ignoresKeyWhenMenuWindowIsGone() passed after 0.153 seconds.
✔ Test finishingSessionClearsReminderWithoutPendingInstall() passed after 0.153 seconds.
✔ Test handlesPlainKeyWhenMenuWindowIsVisible() passed after 0.153 seconds.
✔ Test resumeWithoutStartDoesNotInstall() passed after 0.153 seconds.
✔ Test ignoresKeyWithDisallowedModifiers() passed after 0.153 seconds.
✔ Test resumeReinstallsTheMonitorAfterStop() passed after 0.153 seconds.
✔ Test finishingSessionKeepsReminderForPendingInstallOnQuit() passed after 0.154 seconds.
✔ Test doesNotPreserveHoverShortcutsWithoutActionableFocus() passed after 0.153 seconds.
✔ Test allowsShiftForUppercaseShortcuts() passed after 0.153 seconds.
✔ Test staleDisconnectedLoadCannotClearNewerReminderSnapshot() passed after 0.153 seconds.
✔ Test ignoresKeyWhenMenuWindowIsHidden() passed after 0.153 seconds.
✔ Suite UpdateServiceTests passed after 0.154 seconds.
✔ Suite MenuKeyboardMonitorTests passed after 0.154 seconds.
◇ Test creatorRequestStillAdvancesWithoutWritableEnabledLists() started.
✔ Test feedbackArchiveTrimsOldestReportsWhileManualExportKeepsAll() passed after 0.324 seconds.
✔ Suite DiagnosticsServiceTests passed after 0.325 seconds.
✔ Test replacingTimedDueDayPreservesFloatingTimeZoneAndVisibleDay() passed after 0.364 seconds.
✔ Test reminderPreviewOnlyAllowsWebURLs() passed after 0.364 seconds.
✔ Test replacingTimedDueDayPreservesExplicitTimeZone() passed after 0.364 seconds.
✔ Suite AppleRemindersServiceTests passed after 0.365 seconds.
✔ Test complexMarkdownUsesEngineRenderedSemanticsWithoutLosingUnicode() passed after 0.355 seconds.
✔ Test noteEditorUsesProductionFont() passed after 0.355 seconds.
✔ Suite MarkdownRenderingIntegrationTests passed after 0.365 seconds.
✔ Test creatorRequestStillAdvancesWithoutWritableEnabledLists() passed after 0.212 seconds.
◇ Test disconnectDoesNotErasePreferredCreationList() started.
✔ Test disconnectDoesNotErasePreferredCreationList() passed after 0.001 seconds.
◇ Test createdReminderExpandsVisibleSliceWhenSortPlacesItLater() started.
✔ Test createdReminderExpandsVisibleSliceWhenSortPlacesItLater() passed after 0.001 seconds.
◇ Test createdReminderPreservesSupportedDraftFields() started.
✔ Test createdReminderPreservesSupportedDraftFields() passed after 0.001 seconds.
◇ Test deletingWritableReminderRemovesItFromTheFeed() started.
✔ Test deletingWritableReminderRemovesItFromTheFeed() passed after 0.001 seconds.
✔ Suite AppleReminderStatusStoreTests passed after 0.368 seconds.
✔ Test run with 110 tests in 22 suites passed after 0.369 seconds. — 110 tests passed\n- focused XCUITest swipe flow passed with actual Linear and Reminder deletion\n- focused destructive-action cancel/confirm XCUITest passed\n- Codex full-branch review: clean\n- Kimi full-branch review: clean\n- TruffleHog: clean\n\n## Notes\n- Robin manually verified the production interaction in Dayline Dev before release.\n- CodeRabbit's final retry was rate-limited; its earlier full-review findings were addressed.

Summary by CodeRabbit

  • New Features

    • Apple Reminders can now be permanently deleted from the app.
    • Delete actions are available through swipe gestures, context menus, and VoiceOver actions.
    • Destructive actions for reminders, notes, and issues use a consistent reveal interaction.
  • Bug Fixes

    • Improved menu interactions by preventing unexpected horizontal scroll bars.
    • Added safeguards and clearer error handling when reminder deletion is unavailable or fails.

@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
dayline-website 53eff88 Aug 05 2026, 11:06 PM

@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The PR adds Apple Reminder deletion across the service, store, menu, and tests. It also replaces native horizontal row scrolling with shared gesture-based reveal behavior and adds scroller diagnostics.

Changes

Reminder actions and row reveals

Layer / File(s) Summary
Reminder deletion pipeline
Sources/Dayline/Services/AppleRemindersService.swift, Sources/Dayline/Stores/StatusStore.swift, Tests/DaylineTests/AppleReminderModelsTests.swift
The service deletes writable reminders. StatusStore updates local state, ordering, refresh metadata, and error state. Tests verify removal from the feed.
Custom horizontal reveal infrastructure
Sources/Dayline/Views/StatusMenuView.swift, UITests/DaylineUITests/DaylineUITests.swift
The menu detects native horizontal scrollers and uses bounded gesture-based reveal state. UI tests check horizontal scroller visibility and reveal input.
Destructive row actions
Sources/Dayline/Views/StatusMenuView.swift, UITests/DaylineUITests/DaylineUITests.swift
Reminder, Linear issue, and note rows expose destructive actions through reveals, context menus, and accessibility actions. Tests cover cancellation and confirmation flows.

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

Sequence Diagram(s)

sequenceDiagram
  participant MenuWindow
  participant StatusMenuView
  participant StatusStore
  participant AppleRemindersService
  participant EventKit
  MenuWindow->>StatusMenuView: reveal row with horizontal input
  StatusMenuView->>StatusMenuView: show confirmed delete action
  StatusMenuView->>StatusStore: deleteAppleReminder(id)
  StatusStore->>AppleRemindersService: deleteReminder(id)
  AppleRemindersService->>EventKit: remove writable reminder
  EventKit-->>AppleRemindersService: result
  AppleRemindersService-->>StatusStore: result or error
  StatusStore-->>StatusMenuView: refresh feed and error state
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.59% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the primary changes to issue-row swipe actions and horizontal scrollbars.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 2
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/fix-issue-row-scrollers-reminder-swipe

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

❤️ Share

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

@opencode-agent

opencode-agent Bot commented Aug 5, 2026

Copy link
Copy Markdown

Review: PR #66 — Fix issue row swipe actions and scrollbars

Overall this is a well-structured change: the HorizontalRevealRow cleanly eliminates the per-row NSScrollView root cause, deletion is properly confirmation-gated everywhere (swipe, context menu, accessibility action), mock-mode is handled in deleteAppleReminder, and the revision bump protects against stale in-flight fetches resurrecting a deleted reminder. A few findings:

Potential bugs / issues

  1. auditHorizontalScrollers runs a full recursive view-tree walk on every layout() (StatusMenuView.swift:391). The reveal animation (0.16s) and hover state changes trigger layout per frame, so this walks the entire menu tree repeatedly. It only logs on change, but consider throttling to live-scroll/window-change events only.

  2. Diagnostic false positives: hasHorizontalScroller is true whenever horizontal scrolling is enabled, even with hidden/overlay-style scrollers (StatusMenuView.swift:413). Any remaining horizontal-enabled scroll view with overlay style will spam "Unexpected horizontal menu scrollers detected". Consider checking scroller visibility (!scroller.isHidden) instead.

  3. Drag direction is inverted vs. platform convention: in revealGesture, state = -value.translation.width means a rightward drag reveals the trailing action and leftward closes it — the opposite of iOS swipe-to-reveal. It's self-consistent and the PR says the direction was manually verified, but if that verification only covered the trackpad scroll path, the mouse-drag path may surprise users.

  4. Per-row NSEvent local monitors: every row's HorizontalScrollRevealReader installs its own app-local scroll-wheel monitor. With many issues + reminders + notes, that's dozens of monitors, each filtering every scroll event. A single monitor hoisted to the menu level with hit-testing would scale better.

Minor

  • deleteAppleReminder silently no-ops when updatingIssueTarget != nil (StatusStore.swift:2656) — consistent with sibling methods, but a delete that does nothing with no feedback could confuse users.
  • Multiple rows can be revealed simultaneously; no mutual exclusion. Minor UX nit.
  • The scroll reader's accumulation is only reset on phase == .began; devices that don't report phases (wheel mice) share accumulation across gestures. Low impact given the horizontal-dominance guard.
  • eventStore.reset() on failure matches the existing pattern (lines 51/128/283) — good consistency.

Tests

Good coverage: actual swipe reveal + click + confirm + removal for both Linear and Reminders, scrollbar-absence assertions now correctly scoped to horizontal bars within the menu frame. The context-menu-based cancel/confirm test is more robust than the old swipe-based one. The unit test for mock deletion is appropriately minimal.

Verdict: approve with nits — items 1–3 are worth a follow-up, none block merge.

New%20session%20-%202026-08-05T23%3A06%3A36.817Z
opencode session  |  github run

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 8

Caution

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

⚠️ Outside diff range comments (1)
UITests/DaylineUITests/DaylineUITests.swift (1)

737-754: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

The assertion can pass without proving anything. Add a positive control.

assertNoVisibleHorizontalScrollBars asserts that the filtered set is empty. The set is empty both when no horizontal scroll bar exists and when the app.scrollBars query returns nothing at all. In the second case the assertion passes without testing the PR's central claim.

The suite launches with -AppleShowScrollBars Always (Line 33), and scrollIntoView depends on a vertical scroll view in the menu (Line 757). A vertical scroll bar should therefore be present. The width > height filter removes it.

Assert that the query found at least one scroll bar before asserting that none is horizontal. That converts a silent no-op into a real check.

💚 Proposed fix to add a positive control
   private func assertNoVisibleHorizontalScrollBars(
     file: StaticString = `#filePath`,
     line: UInt = `#line`
   ) {
     let menuFrame = app.windows.firstMatch.exists ? app.windows.firstMatch.frame : app.frame
-    let visibleScrollBars = app.scrollBars.allElementsBoundByAccessibilityElement.filter {
+    let candidates = app.scrollBars.allElementsBoundByAccessibilityElement.filter {
       $0.exists
         && !$0.frame.isEmpty
-        && $0.frame.width > $0.frame.height
         && $0.frame.intersects(menuFrame)
     }
+    XCTAssertFalse(
+      candidates.isEmpty,
+      "Expected at least one visible scroll bar with AppleShowScrollBars=Always, so the horizontal check is meaningful",
+      file: file,
+      line: line
+    )
+    let visibleScrollBars = candidates.filter { $0.frame.width > $0.frame.height }
     XCTAssertTrue(
       visibleScrollBars.isEmpty,
       "Expected no visible horizontal scroll bars with AppleShowScrollBars=Always, found \(visibleScrollBars.count)",
       file: file,
       line: line
     )
   }

If the menu does not always show a vertical scroll bar, scope the positive control to the tests that scroll the list.

🤖 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 `@UITests/DaylineUITests/DaylineUITests.swift` around lines 737 - 754, Update
assertNoVisibleHorizontalScrollBars to add a positive control that verifies
app.scrollBars found at least one existing, non-empty scroll bar before
filtering for horizontal bars, then retain the existing empty-horizontal-bars
assertion. If the menu does not guarantee a vertical scroll bar for every
caller, apply this positive check only in the scroll-list tests that rely on the
menu’s vertical scrolling behavior.
🧹 Nitpick comments (3)
Tests/DaylineTests/AppleReminderModelsTests.swift (1)

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

Add a negative test for the read-only guard.

This test covers the success branch. deleteAppleReminder also refuses reminders whose list is read-only, and no test in this suite covers that guard. A read-only reminder must stay in the feed after the call.

Note that mockData is non-nil here, so the store bypasses appleRemindersService.deleteReminder. This test verifies store state only, which matches the sibling creation tests.

💚 Proposed additional test
  `@Test` func deletingReadOnlyReminderKeepsItInTheFeed() async throws {
    let store = StatusStore(mockData: MockData.make(issueSources: [.reminders]))
    if store.appleReminderHasMoreToShow { store.showMoreAppleReminders() }
    let reminder = try `#require`(store.appleReminders.first(where: { !$0.allowsModifications }))

    await store.deleteAppleReminder(id: reminder.id)

    `#expect`(store.appleReminders.contains(where: { $0.id == reminder.id }))
  }

Adjust the pagination call to the store's actual API if the read-only mock reminder is outside the default visible slice.

🤖 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 `@Tests/DaylineTests/AppleReminderModelsTests.swift` around lines 364 - 371,
Add a negative test alongside deletingWritableReminderRemovesItFromTheFeed that
selects a reminder with allowsModifications false, expands the visible reminder
list through the store’s pagination API if needed, calls deleteAppleReminder,
and verifies the reminder remains in store.appleReminders.
Sources/Dayline/Stores/StatusStore.swift (1)

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

Logic is correct. Consider sharing the body with completeAppleReminder.

The ordering is right: the service call runs before the local removal, so a failure leaves the feed unchanged and reports through appleRemindersError. The defer clears updatingIssueTarget on every path. The diagnostics string interpolates no reminder title or identifier, so no user data reaches the public log.

deleteAppleReminder and completeAppleReminder (Lines 2626-2651) now share the same guard, target bookkeeping, mock bypass, revision bump, local removal, reorder, error clear, timestamp, and diagnostics sequence. Only the service call, the diagnostic message, and the recurring refresh differ. One private helper would remove the duplication.

♻️ Proposed refactor to share the mutation pipeline
  /// Applies one terminal Apple Reminder mutation and removes the reminder from the feed.
  private func removeAppleReminder(
    id: String,
    diagnosticMessage: String,
    mutate: (String) throws -> Void,
    afterSuccess: (AppleReminderItem) -> Void = { _ in }
  ) async {
    let target = IssueActionTarget.reminder(id)
    guard updatingIssueTarget == nil,
          let reminder = allAppleReminders.first(where: { $0.id == id }),
          reminder.allowsModifications else { return }
    updatingIssueTarget = target
    statusPickerTarget = nil
    defer { updatingIssueTarget = nil }

    do {
      if mockData == nil {
        try mutate(id)
      }
      appleRemindersRevision += 1
      allAppleReminders.removeAll { $0.id == id }
      applyAppleReminderOrder()
      appleRemindersError = nil
      lastUpdatedAt = Date()
      DaylineDiagnostics.record(diagnosticMessage, category: .interaction)
      afterSuccess(reminder)
    } catch {
      appleRemindersError = error.localizedDescription
    }
  }

deleteAppleReminder then becomes:

  /// Permanently deletes an Apple Reminder and removes it from the incomplete feed.
  func deleteAppleReminder(id: String) async {
    await removeAppleReminder(
      id: id,
      diagnosticMessage: "Apple Reminder deleted",
      mutate: appleRemindersService.deleteReminder(id:)
    )
  }
🤖 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 `@Sources/Dayline/Stores/StatusStore.swift` around lines 2653 - 2676, Extract
the duplicated mutation pipeline from deleteAppleReminder and
completeAppleReminder into one private removeAppleReminder helper. Keep the
shared guard, bookkeeping, mock bypass, service mutation, local removal,
ordering, error reset, timestamp, diagnostics, and error handling there;
parameterize the service call and diagnostic message, and use an optional
success callback for completeAppleReminder’s recurring refresh. Update both
public methods to delegate to the helper while preserving their existing
behavior.
Sources/Dayline/Services/AppleRemindersService.swift (1)

141-151: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Deletion path is correct. Consider extracting the shared commit-and-reset handling.

The authorization, type, and writability checks all run in writableReminder(with:) before removal. The reset-then-rethrow behavior matches saveModifiedReminder.

The do/catch body now duplicates saveModifiedReminder (Lines 279-286). One small helper removes the duplication.

♻️ Proposed refactor to share the commit failure handling
   /// Permanently deletes a reminder from its writable list.
   func deleteReminder(id: String) throws {
     let reminder = try writableReminder(with: id)
-    do {
-      try eventStore.remove(reminder, commit: true)
-    } catch {
-      eventStore.reset()
-      throw error
-    }
+    try resettingStoreOnFailure {
+      try eventStore.remove(reminder, commit: true)
+    }
   }

Then add the helper next to saveModifiedReminder:

  /// Runs an EventKit mutation and discards cached changes if it fails.
  private func resettingStoreOnFailure(_ mutate: () throws -> Void) throws {
    do {
      try mutate()
    } catch {
      eventStore.reset()
      throw error
    }
  }

  private func saveModifiedReminder(_ reminder: EKReminder) throws {
    try resettingStoreOnFailure {
      try eventStore.save(reminder, commit: true)
    }
  }
🤖 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 `@Sources/Dayline/Services/AppleRemindersService.swift` around lines 141 - 151,
Extract the duplicated commit-failure handling from deleteReminder and
saveModifiedReminder into a private resettingStoreOnFailure helper that executes
a throwing mutation, resets eventStore, and rethrows on failure. Update both
methods to use the helper while preserving their existing EventKit operations
and behavior.
🤖 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 `@Sources/Dayline/Views/StatusMenuView.swift`:
- Around line 508-516: Replace the per-instance scroll monitor created by
HorizontalScrollRevealView.viewDidMoveToWindow with a shared
HorizontalRevealMonitor that maintains weak registrations and one
application-wide NSEvent scroll-wheel monitor. Register each view when it enters
a window, unregister it when it leaves or is deallocated, and route events by
testing registered views until one handles the event; preserve propagation for
unhandled events and remove the shared monitor when no views remain.
- Around line 2816-2825: Expose the hidden destructive actions through named
accessibility actions: in
Sources/Dayline/Views/StatusMenuView.swift#L2816-L2825, update IssueRow.body
with a “Cancel issue” action invoking confirmDestructiveAction using the
existing cancel title, message, and cancel action; in
Sources/Dayline/Views/StatusMenuView.swift#L3103-L3112, update NoteRow.body with
a “Delete note” action invoking confirmDestructiveAction using the existing
delete title, message, and delete action.
- Around line 2074-2084: Update the reminder deletion confirmation flow to
distinguish recurring reminders: use a shared confirmation-message helper near
the row’s existing accessibility properties, returning text that explicitly
includes all future occurrences when reminder.isRecurring is true and preserving
the current message otherwise. Replace the inline confirmation message in
CompactDestructiveActionButton and reuse the helper in the context-menu deletion
action and AppleReminderAccessibilityActions.deleteReminder.
- Around line 585-599: Update the isRevealed assignment in the revealGesture
onEnded handler so the non-revealed drag path uses value.translation.width >
-threshold, matching the scroll-path direction; preserve the existing
revealed-state condition and threshold calculation.
- Around line 379-390: Coalesce calls to auditHorizontalScrollers instead of
executing the full window-tree traversal synchronously from layout() and
scrollViewDidLiveScroll(_:). Add a pending-audit guard and schedule at most one
deferred main-queue audit, clearing the guard when it runs; route both existing
hot call sites through this scheduler while preserving the current audit
behavior.
- Around line 518-538: Update handleScrollWheel to reset accumulatedDeltaX when
a new legacy scroll session begins after a gesture timeout, using
event.timestamp and the existing scroll timing state or a suitable timestamp
field. Preserve accumulation within an active gesture, while treating empty
phase and momentumPhase events separated beyond the timeout as a fresh gesture
before applying the threshold logic.
- Around line 556-565: Update the gesture attachment in the row’s body around
revealGesture so the horizontal reveal interaction does not take priority over
the enclosing ScrollView. Replace highPriorityGesture with a normal
direction-filtered gesture, a longer minimumDistance, or simultaneous gesture
composition, while preserving horizontal reveal behavior and allowing vertical
drags on HorizontalRevealRow rows to scroll normally.
- Around line 411-418: Update horizontalScrollerCount(in:) to count only an
actually displayed horizontal scroller: require the NSScrollView’s horizontal
scroller to be visible, use the horizontal scroll axis, and apply the same
frame-based visibility checks as the UI test. Preserve the recursive traversal
of subviews while excluding configured-but-hidden or non-horizontal scrollers.

---

Outside diff comments:
In `@UITests/DaylineUITests/DaylineUITests.swift`:
- Around line 737-754: Update assertNoVisibleHorizontalScrollBars to add a
positive control that verifies app.scrollBars found at least one existing,
non-empty scroll bar before filtering for horizontal bars, then retain the
existing empty-horizontal-bars assertion. If the menu does not guarantee a
vertical scroll bar for every caller, apply this positive check only in the
scroll-list tests that rely on the menu’s vertical scrolling behavior.

---

Nitpick comments:
In `@Sources/Dayline/Services/AppleRemindersService.swift`:
- Around line 141-151: Extract the duplicated commit-failure handling from
deleteReminder and saveModifiedReminder into a private resettingStoreOnFailure
helper that executes a throwing mutation, resets eventStore, and rethrows on
failure. Update both methods to use the helper while preserving their existing
EventKit operations and behavior.

In `@Sources/Dayline/Stores/StatusStore.swift`:
- Around line 2653-2676: Extract the duplicated mutation pipeline from
deleteAppleReminder and completeAppleReminder into one private
removeAppleReminder helper. Keep the shared guard, bookkeeping, mock bypass,
service mutation, local removal, ordering, error reset, timestamp, diagnostics,
and error handling there; parameterize the service call and diagnostic message,
and use an optional success callback for completeAppleReminder’s recurring
refresh. Update both public methods to delegate to the helper while preserving
their existing behavior.

In `@Tests/DaylineTests/AppleReminderModelsTests.swift`:
- Around line 364-371: Add a negative test alongside
deletingWritableReminderRemovesItFromTheFeed that selects a reminder with
allowsModifications false, expands the visible reminder list through the store’s
pagination API if needed, calls deleteAppleReminder, and verifies the reminder
remains in store.appleReminders.
🪄 Autofix

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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 1fe2eb5c-3ad1-436f-8e11-462e19d9d10d

📥 Commits

Reviewing files that changed from the base of the PR and between af21a8f and 53eff88.

📒 Files selected for processing (5)
  • Sources/Dayline/Services/AppleRemindersService.swift
  • Sources/Dayline/Stores/StatusStore.swift
  • Sources/Dayline/Views/StatusMenuView.swift
  • Tests/DaylineTests/AppleReminderModelsTests.swift
  • UITests/DaylineUITests/DaylineUITests.swift

Comment thread Sources/Dayline/Views/StatusMenuView.swift
Comment thread Sources/Dayline/Views/StatusMenuView.swift
Comment thread Sources/Dayline/Views/StatusMenuView.swift
Comment thread Sources/Dayline/Views/StatusMenuView.swift
Comment thread Sources/Dayline/Views/StatusMenuView.swift
Comment thread Sources/Dayline/Views/StatusMenuView.swift
Comment thread Sources/Dayline/Views/StatusMenuView.swift
Comment thread Sources/Dayline/Views/StatusMenuView.swift
@robin-liquidium
robin-liquidium merged commit 47e7d82 into main Aug 5, 2026
49 of 50 checks passed
@robin-liquidium
robin-liquidium deleted the codex/fix-issue-row-scrollers-reminder-swipe branch August 5, 2026 23:27
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.

1 participant