Skip to content

feat(reminder): wire LocalReminderApplication with in-memory adapters - #238

Closed
gac0812 wants to merge 4 commits into
1024XEngineer:mainfrom
gac0812:feat/reminder-local-application-wiring
Closed

feat(reminder): wire LocalReminderApplication with in-memory adapters#238
gac0812 wants to merge 4 commits into
1024XEngineer:mainfrom
gac0812:feat/reminder-local-application-wiring

Conversation

@gac0812

@gac0812 gac0812 commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Summary

  • 新增 InMemoryLocalScheduleReaderLocalReminderAdapters(送达记账、NoopPopup、本地 recovery/disposition sync)
  • createAppServicesMockReminderApplication 换成 LocalReminderApplication,日程/送达/状态/恢复走内存适配器;device 保持 NativeDeviceCapability
  • LocalReminderApplication 订阅原生 fire/snooze/dismiss:原生已响铃时不再叠 JS 弹窗/TTS
  • AppProviders 挂上 useReminderPermissionsOnLaunch,并在回到前台时 rebuild

Merge Order

Scope

  • frontend/src/features/reminder/data/local/InMemoryLocalScheduleReader.ts
  • frontend/src/features/reminder/data/local/LocalReminderAdapters.ts
  • frontend/src/features/reminder/application/LocalReminderApplication.ts
  • frontend/src/features/reminder/application/interfaces/AlarmSchedulerPort.ts(可选 subscribe/stopRinging)
  • frontend/src/app/composition/createAppServices.ts
  • frontend/src/app/AppProviders.tsx
  • frontend/tests/unit/features/reminder/inMemoryLocalScheduleReader.test.ts
  • frontend/tests/unit/features/reminder/localReminderAdapters.test.ts
  • frontend/tests/unit/features/reminder/localReminderApplication.nativeAlarm.test.ts

Out of scope

Verification

  • npm run check
  • npm ci

Closes #233

Replace mock reminder composition with the real coordinator so native fire events no longer stack JS dialogs and permission returns can rebuild.
@vercel

vercel Bot commented Aug 13, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
timeflow Ready Ready Preview Aug 14, 2026 1:44am

@fennoai fennoai 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.

Found one high-confidence lifecycle issue in native disposition hydration.

Verification note: git diff --check passed. The narrow test command could not run because the checked-out node_modules/.bin/vitest executable returned Permission denied.

Comment thread frontend/src/features/reminder/application/LocalReminderApplication.ts Outdated
… queue

startInternal already owns enqueueOp, so calling public confirm/snooze during hydration deadlocked cold start for confirmed and snoozed rows.
@gac0812

gac0812 commented Aug 13, 2026

Copy link
Copy Markdown
Contributor Author

@fennoai /review

@fennoai

fennoai Bot commented Aug 13, 2026

Copy link
Copy Markdown

Review Complete

  • Prepared the pinned PR workspace
  • Inspected the fixed SHA diff
  • Completed code quality, performance, security, and documentation passes
  • Published one non-blocking PR review with 2 inline findings

Focused tests were not runnable because frontend/node_modules/.bin/vitest is not executable/available and TypeScript dependencies are missing.

View job run

@fennoai fennoai 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.

Review

Found two integration issues that prevent the new local reminder wiring from functioning in the production composition root. Focused verification could not run because the checked-in frontend/node_modules/.bin/vitest is not executable/available (vitest: Permission denied) and TypeScript dependencies are missing.

View job run

Comment thread frontend/src/app/composition/createAppServices.ts
Comment thread frontend/src/app/composition/createAppServices.ts
Load active SQLite rows before start/rebuild, and map native fired/disposition callbacks onto NativeAlarmScheduler.
@gac0812

gac0812 commented Aug 14, 2026

Copy link
Copy Markdown
Contributor Author

@fennoai /review

@fennoai

fennoai Bot commented Aug 14, 2026

Copy link
Copy Markdown

Review Complete

  • Reviewed the pinned diff through head 36f24849eac8ccbcc9d905e6e3c7dee9447b01c0.
  • Published one non-blocking PR review with 4 inline findings.
  • Verified with npm ci, 40 focused Jest tests, and full npm run check.
  • Full verification passed: lint, formatting, typecheck, 47 Vitest tests, and 251 Jest tests.

View job run

@fennoai fennoai 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.

Updated Review

The two findings from the previous review are partially addressed: schedules are now hydrated from SQLite and the native event APIs are exposed. I found four remaining integration/lifecycle issues in the new wiring.

Verification passed: npm ci, focused Jest tests (40 tests), and full npm run check (298 tests across Vitest/Jest, lint, formatting, and typecheck).

View job run

let cancelled = false;
void (async () => {
if (isHydratableScheduleReader(reminderPorts.schedules)) {
await hydrateInMemorySchedulesFromLocalDb(reminderPorts.schedules, accountId);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[P1] Prevent an obsolete account hydration from mutating the shared reader. The cleanup flag is checked only after hydrateInMemorySchedulesFromLocalDb has already called reader.replaceAll(...). If account A's query is in flight while the user signs out and authenticates as B, A can resolve last and overwrite the reader with A's schedules; B's running reminder application is subscribed to that reader and can then register A's reminders. Make hydration return a snapshot and commit it only if the effect generation/account is still current, or otherwise make the write account-scoped/cancellable.

if (isHydratableScheduleReader(reminderPorts.schedules)) {
await hydrateInMemorySchedulesFromLocalDb(reminderPorts.schedules, accountId);
}
await reminder.rebuild();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[P2] Avoid rebuilding twice on every foreground transition. For the in-memory reader, hydrateInMemorySchedulesFromLocalDb calls replaceAll, which synchronously notifies LocalReminderApplication and enqueues a rebuild; this explicit reminder.rebuild() immediately enqueues a second rebuild. Each pass drops registrations and cancels/reschedules native alarms, causing unnecessary alarm churn and a possible scheduling gap. Only call this explicitly for readers that do not notify, or suppress one of the rebuild paths.

reader.replaceAll(schedules);
return schedules.length;
} catch {
reader.replaceAll([]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[P1] Do not erase the last valid reminder snapshot on a transient SQLite failure. This hydration runs whenever the app returns to the foreground; any temporary open/query error executes replaceAll([]), whose notification makes the running application drop registrations and cancel all scheduled alarms. The error is swallowed, so the user receives no indication and can miss reminders until a later successful foreground refresh. Preserve the current same-account snapshot on refresh failure while clearing explicitly during account teardown/switch.

return subscribeNativeAlarmEvents((payload) => {
listener({
type: payload.type,
schedule_id: payload.scheduleId,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[P1] Pass request.schedule_id into the native scheduling call so this field can be populated. The native bridge's schedule API still accepts only (triggerAtMillis, title), and NativeAlarmScheduler.schedule never sends the schedule id, yet events and consumed dispositions are now expected to return payload.scheduleId. The native layer therefore has no schedule identifier to persist/emit for the alarm, so these events cannot be correlated and LocalReminderApplication will ignore or mis-handle them. The current tests inject a pre-correlated payload directly and do not cover the schedule-to-event round trip.

@gac0812 gac0812 closed this Aug 14, 2026
@gac0812
gac0812 deleted the feat/reminder-local-application-wiring branch August 14, 2026 06:05
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.

feat(reminder): 用内存适配器替换 mock 并接入 LocalReminderApplication

1 participant