mirror of
https://github.com/deepseek-ai/deepseek-harness.git
synced 2026-09-11 04:00:38 +00:00
fix(web): tighten schedule catalog evidence
This commit is contained in:
@@ -66,9 +66,6 @@ const CATALOG_SESSION_ID = SessionId('schedule-catalog-web-e2e')
|
||||
const CATALOG_TITLE = 'Active schedule catalog'
|
||||
const REMINDER_TRIGGER_NAME = /^\d+ reminders?$/
|
||||
const ACTIVE_SCHEDULE_LABEL = 'Has active scheduled task'
|
||||
const LARGE_INTERVAL_SECONDS = 200_000_000_001
|
||||
const LARGE_INTERVAL_PROMPT = 'Keep every large-interval metadata field visible'
|
||||
const LARGE_INTERVAL_ID = ScheduleId('catalog-large-interval')
|
||||
const CATALOG_IDS = {
|
||||
after: ScheduleId('catalog-after'),
|
||||
at: ScheduleId('catalog-at'),
|
||||
@@ -725,8 +722,9 @@ describe.skipIf(MODE === 'record')('web e2e: active Schedule catalog', () => {
|
||||
expect(lightLayout.right).toBeLessThanOrEqual(lightLayout.viewport)
|
||||
expect(lightLayout.scrollWidth).toBeLessThanOrEqual(lightLayout.viewport)
|
||||
expect(lightLayout.background).not.toBe('rgba(0, 0, 0, 0)')
|
||||
const longPrompt = catalog.getByRole('listitem').filter({ hasText: 'Join release review' })
|
||||
.locator(':scope > span').nth(1)
|
||||
const longRow = catalog.getByRole('listitem').filter({ hasText: 'Join release review' })
|
||||
const cadenceRow = catalog.getByRole('listitem').filter({ hasText: 'Check exact cadence' })
|
||||
const longPrompt = longRow.locator(':scope > span').nth(1)
|
||||
const promptLayout = await longPrompt.evaluate(element => ({
|
||||
height: element.getBoundingClientRect().height,
|
||||
clientWidth: element.clientWidth,
|
||||
@@ -734,6 +732,33 @@ describe.skipIf(MODE === 'record')('web e2e: active Schedule catalog', () => {
|
||||
}))
|
||||
expect(promptLayout.height).toBeGreaterThan(18)
|
||||
expect(promptLayout.scrollWidth).toBeLessThanOrEqual(promptLayout.clientWidth)
|
||||
const [longRowLayout, cadenceRowLayout] = await Promise.all([
|
||||
longRow.evaluate((element) => {
|
||||
const box = element.getBoundingClientRect()
|
||||
return {
|
||||
bottom: box.bottom,
|
||||
clientHeight: element.clientHeight,
|
||||
scrollHeight: element.scrollHeight,
|
||||
childBottoms: [...element.children].map(child => child.getBoundingClientRect().bottom),
|
||||
}
|
||||
}),
|
||||
cadenceRow.evaluate((element) => {
|
||||
const box = element.getBoundingClientRect()
|
||||
return { top: box.top, bottom: box.bottom }
|
||||
}),
|
||||
])
|
||||
expect(longRowLayout.scrollHeight).toBeLessThanOrEqual(longRowLayout.clientHeight)
|
||||
expect(longRowLayout.childBottoms).not.toHaveLength(0)
|
||||
for (const childBottom of longRowLayout.childBottoms) {
|
||||
expect(childBottom).toBeLessThanOrEqual(longRowLayout.bottom)
|
||||
}
|
||||
expect(longRowLayout.bottom).toBeLessThanOrEqual(cadenceRowLayout.top)
|
||||
expect(cadenceRowLayout.bottom).toBeGreaterThan(cadenceRowLayout.top)
|
||||
const scrollLayout = await catalog.evaluate(element => ({
|
||||
clientHeight: element.clientHeight,
|
||||
scrollHeight: element.scrollHeight,
|
||||
}))
|
||||
expect(scrollLayout.scrollHeight).toBeGreaterThan(scrollLayout.clientHeight)
|
||||
|
||||
await page.evaluate(() => { document.body.setAttribute('data-ds-dark-theme', '') })
|
||||
const darkBackground = await catalog.evaluate(element => getComputedStyle(element).backgroundColor)
|
||||
@@ -745,53 +770,9 @@ describe.skipIf(MODE === 'record')('web e2e: active Schedule catalog', () => {
|
||||
MODE,
|
||||
)
|
||||
|
||||
parentAgent.session.append('schedule/change', {
|
||||
version: 1,
|
||||
operation: 'create',
|
||||
schedule: createEveryScheduleRecord(
|
||||
LARGE_INTERVAL_ID,
|
||||
LARGE_INTERVAL_PROMPT,
|
||||
LARGE_INTERVAL_SECONDS,
|
||||
CATALOG_NOW,
|
||||
),
|
||||
})
|
||||
await expect(scaffold.ctx.sessions.flush(parentAgent.session)).resolves.toBe(true)
|
||||
await page.getByRole('button', { name: '4 reminders' }).waitFor({ timeout: 15_000 })
|
||||
const largeRow = catalog.getByRole('listitem').filter({ hasText: LARGE_INTERVAL_PROMPT })
|
||||
await largeRow.waitFor({ timeout: 15_000 })
|
||||
const metadataLayout = await largeRow.locator(':scope > span').nth(2).evaluate((element) => {
|
||||
const box = element.getBoundingClientRect()
|
||||
return {
|
||||
text: element.textContent,
|
||||
height: box.height,
|
||||
left: box.left,
|
||||
right: box.right,
|
||||
clientWidth: element.clientWidth,
|
||||
scrollWidth: element.scrollWidth,
|
||||
fields: [...element.children].map((child) => {
|
||||
const field = child.getBoundingClientRect()
|
||||
return { width: field.width, height: field.height, left: field.left, right: field.right }
|
||||
}),
|
||||
}
|
||||
})
|
||||
expect(metadataLayout.text).toContain(`Every ${LARGE_INTERVAL_SECONDS} seconds`)
|
||||
expect(metadataLayout.height).toBeGreaterThan(16)
|
||||
expect(metadataLayout.scrollWidth).toBeLessThanOrEqual(metadataLayout.clientWidth)
|
||||
for (const field of metadataLayout.fields) {
|
||||
expect(field.width).toBeGreaterThan(0)
|
||||
expect(field.height).toBeGreaterThan(0)
|
||||
expect(field.left).toBeGreaterThanOrEqual(metadataLayout.left)
|
||||
expect(field.right).toBeLessThanOrEqual(metadataLayout.right)
|
||||
}
|
||||
const scrollLayout = await catalog.evaluate(element => ({
|
||||
clientHeight: element.clientHeight,
|
||||
scrollHeight: element.scrollHeight,
|
||||
}))
|
||||
expect(scrollLayout.scrollHeight).toBeGreaterThan(scrollLayout.clientHeight)
|
||||
|
||||
const sessionRow = page.getByRole('treeitem', { name: new RegExp(CATALOG_TITLE) })
|
||||
expect(await sessionRow.getByRole('img', { name: ACTIVE_SCHEDULE_LABEL }).count()).toBe(1)
|
||||
for (const id of [...Object.values(CATALOG_IDS), LARGE_INTERVAL_ID]) {
|
||||
for (const id of Object.values(CATALOG_IDS)) {
|
||||
parentAgent.session.append('schedule/change', { version: 1, operation: 'delete', id })
|
||||
}
|
||||
await expect(scaffold.ctx.sessions.flush(parentAgent.session)).resolves.toBe(true)
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write docs/config-catalog.md
|
||||
config-catalog.md: b2d8379dd799ab0e836a2ab0572d6af08ababc5d
|
||||
config-catalog.zh.md: d768b40c54d27aeeb1294d4fee7417f54c11afb0
|
||||
config-catalog.zh.md: 6c411df61a200b1491024d0ee42d084176570af7
|
||||
|
||||
@@ -1933,7 +1933,7 @@ export interface Config {
|
||||
}
|
||||
```
|
||||
|
||||
来源:[`packages/session/session-projection-cache/src/index.ts:45`](../packages/session/session-projection-cache/src/index.ts)
|
||||
来源:[`packages/session/session-projection-cache/src/index.ts:48`](../packages/session/session-projection-cache/src/index.ts)
|
||||
|
||||
<a id="deepseek-aidsh-session-query-sqlite"></a>
|
||||
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write packages/client/ui-schedule/README.md
|
||||
README.md: 83e9bfd3cd6005636f04090e1b7fa26917a70b84
|
||||
README.zh.md: 629c83ecb55989eb0f96660b02a148b9ce1c6ea3
|
||||
README.md: 2d75746876daf453b01ff2e84b6e62ed018c205c
|
||||
README.zh.md: 32925e73aed252189764c4eafc0f5ecc58bb2a08
|
||||
|
||||
@@ -37,7 +37,7 @@ The shipped Web graph already resolves `@deepseek-ai/dsh-client-ui-schedule` thr
|
||||
|
||||
Each row shows the complete wrapping prompt, a separate Scheduled or Overdue status, localized Once or the largest exact whole unit for a repeating interval, browser-local target time, and browser-clock-relative time. Intervals are never rounded, and the three metadata fields wrap across lines instead of clipping valid large values. The 336px popover scrolls vertically when needed and exposes no Schedule id, raw UTC value, details, or action controls.
|
||||
|
||||
Only the native trigger button enters the tab order. Enter and Space use normal button activation; while the popover is open, Escape closes it and restores trigger focus even after native Tab moves focus to another header action; an outside pointer press dismisses it. If a live update removes the final record, the component closes and unmounts without moving focus to another header action. A failed Session open hides the trigger even when a tentative cached projection exists.
|
||||
Only the native trigger button enters the tab order. Enter and Space use normal button activation; while focus remains on the trigger or catalog, Escape closes the popover and restores trigger focus; an outside pointer press dismisses it. If a live update removes the final record, the component closes and unmounts without moving focus to another header action. A failed Session open hides the trigger even when a tentative cached projection exists.
|
||||
|
||||
-----
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ dsh web --patch apps/cli/config/examples/schedule/cordis.yml
|
||||
|
||||
每一行显示可完整换行的 prompt、独立的「等待中」或「已逾期」状态、本地化的「单次」或重复间隔可整除的最大完整单位、浏览器本地目标时间,以及按浏览器时钟派生的相对时间。间隔绝不舍入,三项元数据会按行换行,不会裁剪合法的大数值。336px 宽的弹层在需要时纵向滚动,不显示 Schedule id、原始 UTC 值、详情或操作控件。
|
||||
|
||||
只有原生触发按钮进入 Tab 顺序。Enter 与 Space 使用按钮的正常激活行为;弹层打开期间,即使原生 Tab 已把焦点移到另一个会话头部动作,Escape 仍会关闭弹层并把焦点交还触发器;在外部按下指针也会关闭。若 live 更新移除最后一条记录,组件会关闭并卸载,但不会把焦点移到另一个会话头部动作。Session 打开失败时,即使存在暂定的缓存 projection,也会隐藏触发器。
|
||||
只有原生触发按钮进入 Tab 顺序。Enter 与 Space 使用按钮的正常激活行为;焦点仍在触发器或目录内时,Escape 会关闭弹层并把焦点交还触发器;在外部按下指针也会关闭。若 live 更新移除最后一条记录,组件会关闭并卸载,但不会把焦点移到另一个会话头部动作。Session 打开失败时,即使存在暂定的缓存 projection,也会隐藏触发器。
|
||||
|
||||
-----
|
||||
|
||||
|
||||
@@ -65,6 +65,7 @@
|
||||
.row {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-shrink: 0;
|
||||
gap: 3px;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
|
||||
@@ -163,7 +163,6 @@ describe('ScheduleCatalogAction rows', () => {
|
||||
[7_200, 'Every 2 hours', '2小时一次'],
|
||||
[300, 'Every 5 minutes', '5分钟一次'],
|
||||
[301, 'Every 301 seconds', '301秒一次'],
|
||||
[200_000_000_001, 'Every 200000000001 seconds', '200000000001秒一次'],
|
||||
] as const
|
||||
for (const [seconds, english, chinese] of samples) {
|
||||
const item = record(String(seconds), 'every', START + 1_000, { everySeconds: seconds })
|
||||
@@ -175,20 +174,6 @@ describe('ScheduleCatalogAction rows', () => {
|
||||
expect(tZh('status.overdue')).toBe('已逾期')
|
||||
})
|
||||
|
||||
it('renders every required metadata value for a valid large recurrence', () => {
|
||||
const item = record('large', 'every', START + 200_000_000_001_000, {
|
||||
everySeconds: 200_000_000_001,
|
||||
})
|
||||
const t = makeTranslate(en)
|
||||
render(<ScheduleCatalogAction {...props([item])} />)
|
||||
fireEvent.click(screen.getByRole('button'))
|
||||
|
||||
const row = screen.getByRole('listitem')
|
||||
expect(row.textContent).toContain(formatScheduleFrequency(item, t))
|
||||
expect(row.textContent).toContain(formatScheduleLocalTime(item.scheduledAt, 'en'))
|
||||
expect(row.textContent).toContain(formatScheduleRelative(item.scheduledAt, START, t))
|
||||
})
|
||||
|
||||
it('formats absolute time with the active document locale instead of the runtime default', () => {
|
||||
document.documentElement.lang = 'de-DE'
|
||||
const item = record('localized', 'at', START + 3_600_000)
|
||||
|
||||
Reference in New Issue
Block a user