Skip to content

fix(scheduled-task): preserve snoozed occurrence on edits - #5226

Merged
me2seeks merged 2 commits into
apache:mainfrom
Dante-dan:fix/5221-preserve-snoozed-anchor
Sep 12, 2026
Merged

me2seeks merged 2 commits into
apache:mainfrom
Dante-dan:fix/5221-preserve-snoozed-anchor

Conversation

@Dante-dan

Copy link
Copy Markdown
Contributor

Summary

Keep schedule edits distinct from metadata-only edits so renaming a snoozed recurring task preserves both its recurrence anchor and its pending snoozed occurrence.

The edit form now omits the schedule patch when its schedule fields are unchanged. Storage preserves a future pending occurrence for metadata-only updates, while explicit schedule changes and overdue occurrences continue to recompute normally.

Fixes #5221

Verification

  • npm run lint
  • npm run format:check
  • npm --workspace @maka/storage run typecheck
  • npm --workspace @maka/ui run typecheck
  • npm --workspace @maka/storage run build
  • npm --workspace @maka/ui run build
  • Scheduled-task storage regression suite: 13/13 passed
  • UI scheduled-task regression tests: 4/4 passed
  • Cleanly compiled UI suite: 422/422 passed

AI use

Select exactly one:

  • No generative tool made a substantive contribution
  • Generative tooling made a substantive contribution

Tool(s) and scope: Codex implemented the schedule-update fix and added the targeted regression coverage.

Checklist

  • Tests cover the change and fail without it
  • Lint, format, typecheck and the affected suites pass locally

Does this PR entail a change in behavior?

  • Yes — described under Summary above
  • No

Generated-by: Codex
Signed-off-by: Dante <duanjl.china@gmail.com>
@github-actions github-actions Bot added the effort/M Under 500 readable lines label Sep 12, 2026

@Astro-Han Astro-Han left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Review result: NO-GO (1 P2)

Reviewed commit 2b912f3d4199f0476fa00597217f86b8d7b7d529.

P2 — disabling and re-enabling still drops the snoozed occurrence

Issue #5221 explicitly includes this acceptance path: create a daily task at 09:00, snooze it to 09:10, edit only the title, then disable and re-enable it; the original 09:00 recurrence anchor should remain and the pending 09:10 occurrence should still be reflected.

This commit correctly fixes the title-only edit itself. packages/ui/src/scheduled-task-helpers.ts:295-315,372-387 detects unchanged schedule fields and retains the original schedule, packages/ui/src/scheduled-task-form-dialog.tsx:179-211 omits the schedule from the update patch, and packages/storage/src/scheduled-task-store.ts:287-297 preserves a future nextFireAt when no schedule patch is present. The new storage/UI tests cover that edit path at packages/storage/src/__tests__/scheduled-task-row-operations.test.ts:275-317 and packages/ui/src/__tests__/scheduled-task-form-schedule.test.ts:57-102.

The subsequent toggle path is unchanged: apps/desktop/src/preload/preload.ts:3279-3283 maps Disable/Enable to pause/resume; packages/core/src/scheduled-task.ts:319-326 clears nextFireAt during pause; and packages/core/src/scheduled-task.ts:329-357 recomputes the next occurrence from the persisted recurrence anchor during resume. Consequently, the 09:10 snoozed occurrence is lost after re-enabling, and the stated reproduction is still not closed. Please either persist the pending snooze across pause/resume or explicitly change the product contract and add a regression test for the chosen behavior.

Validation

The exact-head hosted test check is red because an existing, unchanged Storybook story (apps/desktop/stories/app-shell.stories.tsx:2714-2767) timed out; the label check passed. The merge tree and whitespace checks are clean. I could not run local TypeScript/build/test suites or a real Electron toggle smoke because this review worktree has no workspace dependencies.

Automated review notice: This review was generated by an AI agent and does not replace independent human review.

Generated-by: Codex
Signed-off-by: Dante <duanjl.china@gmail.com>
@Dante-dan

Copy link
Copy Markdown
Contributor Author

Addressed the pause/resume gap from the review in commit 1d1595ccadbaf7066eaa41a0dd54bcc1daebe255.

  • Pausing now retains the pending nextFireAt; the existing active-status check still prevents paused tasks from firing.
  • Resuming reuses that pending occurrence while it is still in the future, and recomputes from the recurrence after it has elapsed.
  • Metadata edits while paused preserve the pending occurrence; an explicit schedule edit still discards and recomputes it.

Validation passed on this commit: @maka/core and @maka/storage builds, the scheduled-task core suite (16/16), targeted Biome lint/format checks, and git diff --check.

@me2seeks me2seeks left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I found no blocking correctness issue at 1d1595ccadbaf7066eaa41a0dd54bcc1daebe255.

The follow-up commit closes the previously reported pause/resume gap. Pausing retains the pending occurrence without making it eligible to fire because due discovery still requires status === 'active'. Resuming reuses that occurrence only while it remains in the future; after it elapses, the next occurrence is recomputed from the persisted schedule. Metadata-only edits preserve the pending occurrence, while an explicit schedule edit replaces it and causes the next occurrence to be derived from the new schedule.

The UI also stops converting the displayed snoozed time into a new recurrence anchor when the schedule fields were left unchanged. Create, duplicate, interval, calendar, and cron paths remain distinguishable through the seed and submit contract.

Validation on the exact head: clean install; successful @maka/core, @maka/storage, and @maka/ui builds; 33/33 focused scheduled-task tests; Biome checks on all seven changed files; and git diff --check.

中文说明

我在精确提交 1d1595ccadbaf7066eaa41a0dd54bcc1daebe255 上没有发现阻塞问题。

此前指出的“停用再启用会丢失 snooze”已经修复:暂停任务会保留尚未到期的执行时间,但调度器仍只会执行 active 任务;恢复时只复用未来时间,若该时间已经过去,则按原始重复规则计算下一次。只改标题等元数据不会改 recurrence anchor;明确修改时间或重复规则时,才会使用新 schedule。

本地干净安装后,core、storage、UI 构建通过,相关测试 33/33 通过,七个改动文件的 Biome 与 diff 检查通过。

@me2seeks
me2seeks merged commit 8546829 into apache:main Sep 12, 2026
1 check passed
Shouly pushed a commit to Shouly/maka that referenced this pull request Sep 13, 2026
)

Thirty-one upstream commits. The one that reaches the new renderer is apache#5170,
which gives the Renderer the transcript window: Main keeps a tail cache and
answers page requests pass-through, `loadBefore` / `loadAfter` return a page,
`loadAround` / `loadLatest` a reset, `acknowledgeTail` is new, and a batch
carries `extends` / `coversFrom` / `navigation` instead of
`evictedDurableSequences` / `completedOverlayMessageIds`. Also in: apache#5217's
observation contract (`subscribeEvents` loses `onSeeded`; readiness follows
seed consumption as the `ready` phase, and the execution projection it offers
is not consumed here yet), the memory work across composer and stream
(apache#5153), interactions cleared per Turn on abort/complete (apache#4562), Session
bundles and external agents in main/preload (apache#5197, apache#5164), Code Mode
(apache#3615, apache#5219), and the scheduled-task snooze fix (apache#5226).

Resolution per the sync policy: conflicts under the old renderer's trees,
packages/ui's deleted components, their stories, e2e specs and the main tests
that import them stay deleted; upstream's new files in those trees are dropped
(`application/contracts/settings-presentation`, `features/external-agent-settings`,
`features/session-bundle`, `workhub/ui/return-button`, `model-wheel-picker`,
the prompt-rail and live-turn-buffer tests, `workhub-return-rail.spec.ts`).
The renderer side of apache#5217 (one live Turn per Session → a buffer keyed by
Turn, `liveTurn` → `liveTurns`, `phase` gone) stays out: `packages/ui`
`live-turn-projection.ts`, `transcript-projection.ts` and their tests keep
ours and `live-turn-buffer.ts` is dropped; `session-event-handlers.ts` keeps
ours plus upstream's display-frame scheduler. `packages/ui`
`conversation-copy.ts` keeps `transcriptGap` (our gap rows use it),
`transcript-row-projection.ts` is restored, `use-pending-selection.ts` goes.
Astryx stays out of package.json and the lockfile; `@ai-sdk/provider-utils`
moves to 5.0.40 and the `@ai-sdk/code-mode` override lands.

Re-implemented for the new contract:
- `lib/ported/desktop-transcript-range-store.ts` and
  `transcript-reading-position.ts` are re-ported from upstream head (the
  previous copies were format-only ports of the old versions);
  `TranscriptReadSupersededError` lives in the latter, and
  `display-frame-scheduler.ts` joins `lib/ported`.
- `store/active-session-store.ts`: the window is the store's — the display
  follows a store subscription rather than `accept`'s return; the paging gate
  and `loadTranscriptHistory` are gone (the controller refuses a read against
  an edge it already read), `loadHistory` keeps only the gap-row indicator;
  `prefetchHistory` and `retainWindow` serve `useChatScroll`'s geometry-driven
  filling and trimming; `setReadingAnchor` only moves the bookmark; the
  bookmark re-anchors after a replica generation change, by sequence within a
  Host epoch and by Turn through the landmark index across one; a read
  superseded by an epoch change is not an error.
- `SessionView` passes `onPrefetchHistory` / `onRetainWindow`; the gap rows
  and the return-to-latest button keep their explicit commands.
- `bridge/sessions.ts` drops `onSeeded`.
- Main tests for the range store, navigation race, overlay settlement and the
  two new probes are upstream's with paths under `lib/ported`; the
  reading-position test keeps upstream's pure-module cases (send pinning,
  overlay-only bookmark, superseded read) — the shell-shaped cases live with
  the store's tests.
- `settings-sections.ts` and the copy files name core's new `external-agents`
  section id as a deferred page.
- Ported apache#5226: an edit that leaves the schedule fields alone omits
  `schedule` from its patch, so the Host keeps a snoozed fire
  (`scheduled-task-form-payload.ts`, `ScheduleFormDialog.tsx`, a test in
  `scheduled-module.test.tsx`).

Also in this tree, found while verifying the sync and not caused by it: a live
Turn's finished steps vanished after switching to another task and back,
leaving only "Working on it…". The Host re-seeds only what is still incomplete
(the streaming text, pending interactions) and Main's transcript overlay is
bootstrapped once per replica, so the steps that finished while the Session
was on screen existed only in the renderer's live projection — which the
store wiped on every selection and reseed. The projection now survives the
switch (a reseed drops only the incomplete text and thinking it replays; a
Turn that ended meanwhile is retired by the transcript it left behind).
`test:streaming-switch` drives the real app through it with a new fake-backend
scenario that settles a text step and a tool call, then holds the Turn open.

The compatible-change declaration `base64-length-allocation.json` is re-pinned
from 143 to the epoch this branch carries (147, upstream's own): upstream left
it at the epoch of its commit and its per-commit hook never re-judged it, while
our merge stages it next to the epoch bump. Its reason (Base64 byte counting
in `artifact.ts` / `session-transcript.ts` without observable change) still
holds against the protocol as merged.

Gates: build:test + build:renderer, typecheck, biome lint and format, locale
hygiene, ASF headers, renderer architecture ledger (rewritten with `--write`;
the new range store's `window` local reads as environment capabilities to the
checker), e2e budget, third-party notices, knip (same findings as before the
merge), desktop dist tests (1599), renderer state (282), Electron smoke (44
checks, no renderer errors), core-dialogue smoke, streaming-switch smoke. `packages/storage`
`workspace-identity` (git worktree ENOTEMPTY) is a parallel-run flake that
passes in isolation, as is `packages/eval` `lifecycle-boundaries` (relay
cancellation timing); `packages/runtime` `model-adapter-onerror` fails on this
machine before and after the merge (asynchronous activity after the test
ended; the file is unchanged this round).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

effort/M Under 500 readable lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Renaming a snoozed daily reminder changes its recurrence time

3 participants