feat(schedule): add durable after reminders

This commit is contained in:
pku-xht
2026-08-06 03:47:37 +08:00
committed by Tianyi Cui
parent f7e7851e3f
commit 8b69252664
41 changed files with 354 additions and 146 deletions

View File

@@ -2,7 +2,7 @@
English | [中文](README.zh.md)
Browser-only renderer for durable Schedule reminder receipts. The plugin registers the `schedule/reminder` key in the conversation-owned `conversation.chat.eventview` slot. The generic runtime continues to carry the durable event identity and its Host-computed JSON sidecar; this package owns only the Schedule card.
Browser-only renderer for durable Schedule reminder receipts. The plugin registers the durable `schedule/change` event type in the conversation-owned `conversation.chat.eventview` slot. The generic runtime continues to carry the durable event identity and its Host-computed JSON sidecar; this package owns only the Schedule card.
The card displays the reminder prompt, Session-local Schedule ID, exact UTC occurrence, and the `session-local` delivery boundary. A malformed or incompatible sidecar remains visible as a contained unavailable receipt instead of crashing the conversation. Unloading the plugin removes only the keyed renderer; `ui-conversation` then shows its generic visible JSON fallback for the same durable event.

View File

@@ -2,7 +2,7 @@
[English](README.md) | 中文
用于渲染持久 Schedule 提醒回执的纯浏览器插件。插件在会话拥有的 `conversation.chat.eventview` slot 中注册 `schedule/reminder` key。通用 runtime 继续携带持久事件身份与 Host 计算的 JSON sidecar;本包只拥有 Schedule 卡片。
用于渲染持久 Schedule 提醒回执的纯浏览器插件。插件在会话拥有的 `conversation.chat.eventview` slot 中注册持久事件类型 `schedule/change`。通用 runtime 继续携带持久事件身份与 Host 计算的 JSON sidecar;本包只拥有 Schedule 卡片。
卡片显示提醒原文、Session 内的 Schedule ID、精确 UTC 发生时刻,以及 `session-local` 交付边界。若 sidecar 损坏或版本不兼容,组件会显示受控的不可用回执,而不会让会话崩溃。卸载插件只会移除该键控 renderer;`ui-conversation` 随后仍会为同一个持久事件显示通用且可见的 JSON fallback。

View File

@@ -44,7 +44,7 @@ export function ReminderRow({ node, t }: ReminderRowProps) {
{reminder !== null && <span className={css.delivery}>{t('reminder.delivery')}</span>}
</header>
{reminder === null
? <p className={css.invalid}>{t('reminder.invalid')} · {node.presentationKey}</p>
? <p className={css.invalid}>{t('reminder.invalid')} · {node.eventType}</p>
: (
<>
<p className={css.prompt}>{reminder.prompt}</p>

View File

@@ -30,7 +30,7 @@ export function apply(ctx: ClientContext): void {
ctx.effect(
() => ctx.slots.register({
name: 'conversation.chat.eventview',
key: 'schedule/reminder',
key: 'schedule/change',
locale: NS,
}, ReminderRow),
'ui-schedule: reminder row registration',

View File

@@ -38,7 +38,7 @@ describe('ui-schedule browser plugin', () => {
await b.fiber.await()
expect(b.entry()).toEqual({
name: 'conversation.chat.eventview',
key: 'schedule/reminder',
key: 'schedule/change',
locale: 'schedule',
component: ReminderRow,
})

View File

@@ -16,7 +16,7 @@ function props(view: unknown): ReminderRowProps {
kind: 'presented-event',
seq: 4,
time: Date.parse('2026-08-05T08:00:00.000Z'),
presentationKey: 'schedule/reminder',
eventType: 'schedule/change',
view,
}
return { node, t } as ReminderRowProps
@@ -48,7 +48,7 @@ describe('ReminderRow', () => {
deliveryMode: 'external',
})} />)
expect(screen.getByText('提醒回执不可用 · schedule/reminder')).toBeTruthy()
expect(screen.getByText('提醒回执不可用 · schedule/change')).toBeTruthy()
expect(screen.queryByText('not trusted')).toBeNull()
expect(screen.queryByText('仅在当前会话中交付')).toBeNull()
})