feat: 集成 Inspiration 与 Schedule 插件 - #36
Conversation
This reverts commit 6e42f20.
# Conflicts: # README.md # docs/PLUGIN_API.md # package.json # pnpm-lock.yaml # src/cli/index.ts # src/core/plugins/registry.ts
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b751338a0a
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if (delivery.status !== "sent") { | ||
| throw new FlowStoreError( | ||
| "INVALID_STATE", | ||
| `delivery ${deliveryId} is ${delivery.status}, not sent`, |
There was a problem hiding this comment.
Allow outcomes for manually surfaced failed deliveries
When both notification channels are disabled or fail, FlowService.deliver still returns the manually surfaced candidate after changing its delivery to failed, and the Web UI renders the outcome buttons for that candidate. This guard rejects every one of those actions with 409, so users in notification-disabled or temporarily failing environments can see an inspiration but cannot mark it viewed, kept, later, or archived. Permit outcomes for a manually surfaced failed delivery, or avoid presenting it as actionable.
Useful? React with 👍 / 👎.
| return provider()( | ||
| { | ||
| title: "Inspiration", | ||
| message: candidate.inspiration.content, | ||
| }, | ||
| signal |
There was a problem hiding this comment.
Pass the delivery dedupe key to the notification transport
If the daemon exits or the Host times out after macOS/ntfy accepts this send but before finalizeNotification commits, the stale reserved row is intentionally recovered and sent again. Because this request carries only title and message, the downstream transport cannot recognize the retry's existing delivery dedupe key, so restart recovery can produce duplicate user notifications despite the durable ledger. The same stable idempotency key must reach the delivery boundary.
Useful? React with 👍 / 👎.
| SELECT * FROM inspiration_flow_deliveries | ||
| WHERE surfaced_at < ${before} | ||
| ORDER BY surfaced_at DESC, id DESC |
There was a problem hiding this comment.
Include the delivery id in the pagination boundary
When more than one delivery has the same surfaced_at and a page boundary splits that group, requesting the next page with the last row's timestamp uses a strict < predicate and skips every remaining row at that timestamp, even though ordering also uses id. This can occur with concurrent reservations or an injected/fixed clock; use a composite timestamp-and-id cursor matching the ordering.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d384adb328
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const claimed = await store.claimReminder( | ||
| reminder.item.id, | ||
| reminder.reminderAt, | ||
| now | ||
| ); |
There was a problem hiding this comment.
Stop timed-out polls before claiming a reminder
When claimReminder is blocked on its FOR UPDATE longer than the Schedule job's 25-second timeout, the Host aborts the job and releases its non-reentry guard, but this call does not receive the abort signal. Once the lock clears, it can still insert the claim; the subsequent throwIfAborted() is caught and finalizes that claim as failed, so the exact reminder is permanently excluded without ever being sent. Pass the signal into the claim transaction and recheck it after acquiring the row lock and before inserting the ledger row.
Useful? React with 👍 / 👎.
|
Schedule Abort/迟到写 P1 已在源 Worktree |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d384adb328
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| return { | ||
| id: "schedule", | ||
| async load() { |
There was a problem hiding this comment.
Refresh Schedule data during live polling
When a schedule changes outside this page's own actions—for example through el schedule, another API client, or simply by reaching its start time—the contribution never reloads it because it defines only load(), while the host's five-second loop invokes only loadLive(). The calendar and awaiting presentation therefore remain stale until a page reload or an unrelated full-book rebuild; provide a live loader and arrange a rebuild when its snapshot changes.
Useful? React with 👍 / 👎.
| latestInspirations = Array.isArray(list?.items) ? list.items : []; | ||
| latestDeliveries = Array.isArray(ledger?.deliveries) ? ledger.deliveries : []; | ||
| return { | ||
| inspirationList: list, | ||
| inspirationFlowDeliveries: ledger, |
There was a problem hiding this comment.
Re-render Inspiration faces after live updates
When a scheduled Flow run changes the ledger or another client edits an inspiration, this live loader updates latestInspirations and latestDeliveries, but the host's liveSignature() contains only Core record state and patchLiveDom() does not re-render plugin faces. Consequently the returned data is not displayed unless an unrelated Core change triggers a rebuild; signal the host when this snapshot changes or otherwise refresh the affected faces.
Useful? React with 👍 / 👎.
| const item = reminder.item; | ||
| const description = item.description?.trim(); | ||
| return [ | ||
| `Scheduled for ${item.scheduledStartAt} (${item.timezone}).`, |
There was a problem hiding this comment.
Render reminder times in the item's timezone
For items created with a non-UTC offset, scheduledStartAt is serialized from PostgreSQL as a UTC ISO string, so an item planned for 09:00+08:00 produces notification text such as 01:00:00Z (Asia/Shanghai). This makes the user-facing reminder appear eight hours off despite preserving the IANA display timezone; format the instant with item.timezone before constructing the message.
Useful? React with 👍 / 👎.
|
@codex review |
|
Codex Review: Didn't find any major issues. 👍 Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5e3dea89dc
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| latestDeliveryNextCursor = typeof page?.nextCursor === "string" | ||
| ? page.nextCursor | ||
| : null; | ||
| return { handled: true, message: "已加载更多 Flow 投递" }; |
There was a problem hiding this comment.
Preserve loaded Flow history across host refreshes
When a user clicks “加载更多投递”, this return value causes web/app.js to immediately call refreshBook(). That full refresh runs loadSnapshot(), which fetches only /flow/deliveries?limit=20 and replaces latestDeliveries, undoing the rows appended above before they can remain rendered; live polling would replace them again as well. Consequently, users cannot view beyond the first 20 deliveries, so the accumulated pages need to survive the refresh or the face must be rerendered without resetting to page one.
Useful? React with 👍 / 👎.
|
@codex review |
|
Codex Review: Didn't find any major issues. More of your lovely PRs please. Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
|
@codex review |
|
Codex Review: Didn't find any major issues. Keep them coming! Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
结果
一次性集成 EchoLog 的两条独立产品线及其共享通知基础:
notifications:send权限保护的notifications.sendCore service;关键语义
组合验证
pnpm test:170 passed,1 个既有 macOS helper 条件测试 skipped,0 failed;pnpm typecheck:通过;pnpm build:通过,包含 SDK、screen-time、tmux-status、Inspiration、Schedule 与 Core;pnpm test:integration:6/6 通过;ready;el schedule list --json与el inspiration list --json通过;/plugins/schedule/index.js与/plugins/inspiration/index.js返回 200;git diff --check:通过。追踪
关联 #31、#32、#33、#34、#35。Trellis PRD、design、implement、check 与归档记录随对应组件一并纳入。
已知边界
外部通知传输无法提供严格 exactly-once:极窄的“外部已发送、ledger 尚未 finalize”崩溃窗口仍可能产生 at-least-once 行为;普通重复轮询、重启恢复和并发领取已由唯一键、reservation/claim 与测试覆盖。