feat(schedule): add durable after reminders
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
.root {
|
||||
display: grid;
|
||||
min-width: 0;
|
||||
gap: 8px;
|
||||
padding: 12px 14px;
|
||||
border: 1px solid var(--dsw-alias-border-l2);
|
||||
border-radius: 10px;
|
||||
background: var(--dsw-alias-bg-module-platform);
|
||||
color: var(--dsw-alias-label-primary);
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
align-items: center;
|
||||
gap: 7px;
|
||||
}
|
||||
|
||||
.icon {
|
||||
display: inline-flex;
|
||||
flex: none;
|
||||
color: var(--dsw-alias-brand-text);
|
||||
}
|
||||
|
||||
.title {
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
font: 600 13px/18px var(--ds-font-family);
|
||||
}
|
||||
|
||||
.delivery {
|
||||
flex: none;
|
||||
color: var(--dsw-alias-label-tertiary);
|
||||
font: 400 11px/16px var(--ds-font-family);
|
||||
}
|
||||
|
||||
.prompt {
|
||||
margin: 0;
|
||||
color: var(--dsw-alias-label-primary);
|
||||
font: 400 14px/21px var(--ds-font-family);
|
||||
overflow-wrap: anywhere;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.meta {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
flex-wrap: wrap;
|
||||
gap: 4px 12px;
|
||||
color: var(--dsw-alias-label-tertiary);
|
||||
font: 400 11px/16px var(--ds-font-family);
|
||||
}
|
||||
|
||||
.id {
|
||||
font-family: var(--ds-font-family-code);
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.invalid {
|
||||
margin: 0;
|
||||
color: var(--dsw-alias-label-secondary);
|
||||
font: 400 13px/18px var(--ds-font-family);
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
import { IconSparkle16 } from '@deepseek-ai/dsh-client-ui-primitives'
|
||||
import type { EventRowProps } from '@deepseek-ai/dsh-client-ui-conversation/client'
|
||||
import type { PropsLocale } from '@deepseek-ai/dsh-client-ui-slots'
|
||||
import css from './ReminderRow.module.css'
|
||||
|
||||
interface ReminderPresentation {
|
||||
scheduleId: string
|
||||
prompt: string
|
||||
occurrenceAt: string
|
||||
deliveryMode: 'session-local'
|
||||
}
|
||||
|
||||
/** Full Schedule row props: event owner/runtime share plus the locale seat. */
|
||||
export type ReminderRowProps = EventRowProps & PropsLocale<'schedule'>
|
||||
|
||||
/** Narrow the domain-owned JSON sidecar without trusting its unknown carrier type. */
|
||||
function reminderPresentation(value: unknown): ReminderPresentation | null {
|
||||
if (typeof value !== 'object' || value === null || Array.isArray(value)) return null
|
||||
const record = value as Record<string, unknown>
|
||||
if (typeof record['scheduleId'] !== 'string' || record['scheduleId'].length === 0) return null
|
||||
if (typeof record['prompt'] !== 'string') return null
|
||||
if (typeof record['occurrenceAt'] !== 'string' || record['occurrenceAt'].length === 0) return null
|
||||
if (record['deliveryMode'] !== 'session-local') return null
|
||||
return {
|
||||
scheduleId: record['scheduleId'],
|
||||
prompt: record['prompt'],
|
||||
occurrenceAt: record['occurrenceAt'],
|
||||
deliveryMode: record['deliveryMode'],
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render one durable reminder dispatch carried by the generic event sidecar.
|
||||
* @param props - Keyed event owner payload and the Schedule translator.
|
||||
* @returns A visible reminder receipt, or a contained invalid-payload row.
|
||||
*/
|
||||
export function ReminderRow({ node, t }: ReminderRowProps) {
|
||||
const reminder = reminderPresentation(node.view)
|
||||
return (
|
||||
<section className={css.root} role="note" data-schedule-reminder>
|
||||
<header className={css.header}>
|
||||
<span className={css.icon} aria-hidden><IconSparkle16 size={14} /></span>
|
||||
<span className={css.title}>{t('reminder.title')}</span>
|
||||
{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.prompt}>{reminder.prompt}</p>
|
||||
<footer className={css.meta}>
|
||||
<span className={css.id}>{t('reminder.id', { id: reminder.scheduleId })}</span>
|
||||
<time dateTime={reminder.occurrenceAt}>
|
||||
{t('reminder.occurrence', { time: reminder.occurrenceAt })}
|
||||
</time>
|
||||
</footer>
|
||||
</>
|
||||
)}
|
||||
</section>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/** Register the Schedule durable-reminder renderer into the conversation event slot. */
|
||||
import type { ClientContext } from '@deepseek-ai/dsh-client-runtime/client'
|
||||
// Type-only: pulls the locale plugin's Context merge (ctx.locale).
|
||||
import type {} from '@deepseek-ai/dsh-client-locale/client'
|
||||
import { ReminderRow } from './ReminderRow.tsx'
|
||||
import { en, NS, zh, type ScheduleKey } from './locales.ts'
|
||||
|
||||
export type { ReminderRowProps } from './ReminderRow.tsx'
|
||||
export type { ScheduleKey } from './locales.ts'
|
||||
|
||||
declare module '@deepseek-ai/dsh-client-ui-slots' {
|
||||
interface LocaleNamespaceMap {
|
||||
/** Copy for durable Schedule reminder receipts. */
|
||||
schedule: ScheduleKey
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* `conversation` is an ordering edge: its service is published after the chat
|
||||
* entry has declared `conversation.chat.eventview`.
|
||||
*/
|
||||
export const inject = ['slots', 'conversation', 'locale']
|
||||
|
||||
/**
|
||||
* Register bilingual copy and the Schedule reminder keyed row.
|
||||
* @param ctx - Client root context.
|
||||
*/
|
||||
export function apply(ctx: ClientContext): void {
|
||||
ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'ui-schedule: dictionaries')
|
||||
ctx.effect(
|
||||
() => ctx.slots.register({
|
||||
name: 'conversation.chat.eventview',
|
||||
key: 'schedule/reminder',
|
||||
locale: NS,
|
||||
}, ReminderRow),
|
||||
'ui-schedule: reminder row registration',
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/** `schedule` namespace dictionaries. */
|
||||
|
||||
/** Dictionary namespace owned by this plugin. */
|
||||
export const NS = 'schedule'
|
||||
|
||||
/** Simplified Chinese dictionary (the key-set source of truth). */
|
||||
export const zh = {
|
||||
'reminder.title': '定时提醒',
|
||||
'reminder.delivery': '仅在当前会话中交付',
|
||||
'reminder.invalid': '提醒回执不可用',
|
||||
'reminder.id': '编号 {id}',
|
||||
'reminder.occurrence': '触发时间 {time}',
|
||||
} satisfies Record<string, string>
|
||||
|
||||
/** The Schedule namespace key union. */
|
||||
export type ScheduleKey = keyof typeof zh
|
||||
|
||||
/** English dictionary, checked complete against the Chinese key set. */
|
||||
export const en = {
|
||||
'reminder.title': 'Scheduled reminder',
|
||||
'reminder.delivery': 'Delivered in this session only',
|
||||
'reminder.invalid': 'Reminder receipt unavailable',
|
||||
'reminder.id': 'ID {id}',
|
||||
'reminder.occurrence': 'Due at {time}',
|
||||
} satisfies Record<ScheduleKey, string>
|
||||
@@ -0,0 +1,4 @@
|
||||
declare module '*.module.css' {
|
||||
const classes: Readonly<Record<string, string>>
|
||||
export default classes
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
/** Host loader entry for the browser-only Schedule receipt renderer. */
|
||||
|
||||
/** Provides no host-side behavior. */
|
||||
export function apply(): void {}
|
||||
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Package-owned invariant companion for `@deepseek-ai/dsh-client-ui-schedule`.
|
||||
* @module @deepseek-ai/dsh-client-ui-schedule/invariant
|
||||
*/
|
||||
|
||||
/* jscpd:ignore-start */
|
||||
import type { Context } from 'cordis'
|
||||
import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants'
|
||||
|
||||
const PACKAGE_NAME = '@deepseek-ai/dsh-client-ui-schedule'
|
||||
|
||||
/** Cordis companion plugin name. */
|
||||
export const name = 'client-ui-schedule-invariant'
|
||||
/** Service required before the companion can reserve package ownership. */
|
||||
export const inject = ['invariants']
|
||||
|
||||
/**
|
||||
* No runtime invariant: the keyed slot registry owns contribution lifecycle,
|
||||
* and the component has no state outside its immutable owner payload.
|
||||
*/
|
||||
const install: InvariantInstaller = () => {}
|
||||
|
||||
/**
|
||||
* Register this package's invariant companion.
|
||||
* @param ctx - Cordis context carrying the invariant service.
|
||||
* @returns The installed registration's disposer after setup succeeds.
|
||||
*/
|
||||
export const apply = (ctx: Context): Promise<() => void> =>
|
||||
Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install))
|
||||
/* jscpd:ignore-end */
|
||||
Reference in New Issue
Block a user