feat(overview): scope the first range option to today, not the last 24h
The Insights page's first range option read "Last 24 hours" while the ledger it scopes is day-grained, so the rolling window and the day floor underneath it disagreed: past midnight the 24h window still admitted yesterday's ledger day whole, and before 1am it admitted only part of it. "Today" is the local calendar day from midnight — the window the day- granular heatmap beside it already draws. `rangeStartOf` opens the window on the local midnight by date-field arithmetic (the harness's own `setHours(0, 0, 0, 0)` idiom, as in `startOfLocalDay`), which stays exact across DST-short and DST-long days where an epoch-ms subtract drifts into the neighbouring day. The harness helper itself is only reachable through a deep `@deepseek-ai/dsh-client-ui-chat/src/*` path — a package this plugin does not depend on — so the idiom is mirrored at its one call site instead of taking the dependency. Both locales carry the shorter 今天 / Today; the deprecated `ov.range.24h` key is gone rather than aliased. The specs that anchored time on raw epoch offsets silently assumed UTC (`11 * 86_400_000`), and the panel fixture at `NOW - 1h` would have failed whenever the suite runs in the first hour after midnight; every boundary is now anchored on a local `Date`, so the day floor is proved identically in every timezone.
This commit is contained in:
1 parent
9d2c812638
commit
d3f2399440
5 files changed
+44
-26
No files matched your search
@@ -75,7 +75,7 @@ export interface OverviewPanelProps {
|
||||
useWorkspaces?: unknown
|
||||
}
|
||||
|
||||
const RANGES: readonly OverviewRange[] = ['24h', '7d', '30d', 'all']
|
||||
const RANGES: readonly OverviewRange[] = ['today', '7d', '30d', 'all']
|
||||
const SORTS: readonly OverviewSort[] = ['recent', 'tokens', 'context']
|
||||
/** The heatmap's depth metrics, in toggle order (steps is the default). */
|
||||
const METRICS: readonly HeatMetric[] = ['sessions', 'steps']
|
||||
|
||||
+2
-2
@@ -350,7 +350,7 @@ export const DICT_ZH: Record<string, string> = {
|
||||
'ov.title': '上下文洞察',
|
||||
'ov.unavailable': '会话列表暂不可用',
|
||||
'ov.range.label': '统计范围',
|
||||
'ov.range.24h': '近 24 小时',
|
||||
'ov.range.today': '今天',
|
||||
'ov.range.7d': '近 7 天',
|
||||
'ov.range.30d': '近 30 天',
|
||||
'ov.range.all': '全部',
|
||||
@@ -789,7 +789,7 @@ export const DICT_EN: Record<string, string> = {
|
||||
'ov.title': 'Context Insights',
|
||||
'ov.unavailable': 'The session list is unavailable',
|
||||
'ov.range.label': 'Range',
|
||||
'ov.range.24h': 'Last 24 hours',
|
||||
'ov.range.today': 'Today',
|
||||
'ov.range.7d': 'Last 7 days',
|
||||
'ov.range.30d': 'Last 30 days',
|
||||
'ov.range.all': 'All',
|
||||
|
||||
+15
-5
@@ -328,11 +328,20 @@ function mergeSkillTallies(a: ActivityDay['skills'], b: NonNullable<ActivityDay[
|
||||
|
||||
// ---- range / filter / sort -------------------------------------------------
|
||||
|
||||
export type OverviewRange = '24h' | '7d' | '30d' | 'all'
|
||||
export type OverviewRange = 'today' | '7d' | '30d' | 'all'
|
||||
|
||||
/** The range window's start instant (epoch ms), or null for "all". */
|
||||
/**
|
||||
* The range window's start instant (epoch ms), or null for "all". "Today"
|
||||
* opens on the local calendar day's midnight — date-field arithmetic (the
|
||||
* harness's own `setHours(0, 0, 0, 0)` idiom) keeps a DST-short or long day
|
||||
* exact, where an epoch-ms subtract would drift into the neighbouring day.
|
||||
*/
|
||||
export function rangeStartOf(range: OverviewRange, now: number): number | null {
|
||||
if (range === '24h') return now - 86_400_000
|
||||
if (range === 'today') {
|
||||
const midnight = new Date(now)
|
||||
midnight.setHours(0, 0, 0, 0)
|
||||
return midnight.getTime()
|
||||
}
|
||||
if (range === '7d') return now - 7 * 86_400_000
|
||||
if (range === '30d') return now - 30 * 86_400_000
|
||||
return null
|
||||
@@ -443,8 +452,9 @@ export type SkillSort = 'loads' | 'recent'
|
||||
/**
|
||||
* The scope's ledger-day predicate: the heatmap's pinned day admits exactly
|
||||
* that day, else the range's day floor admits its own and later days (the
|
||||
* ledger is day-grained: the 24h window's start day counts whole, the same
|
||||
* resolution the heatmap reads at); 'all' admits every day.
|
||||
* ledger is day-grained: "today" starts at this very day's key and the 7d/30d
|
||||
* windows admit their start day whole, the same resolution the heatmap reads
|
||||
* at); 'all' admits every day.
|
||||
*/
|
||||
function skillDayPredicate(opts: { range: OverviewRange; day: string | null }, now: number): (key: string) => boolean {
|
||||
if (opts.day !== null) return key => key === opts.day
|
||||
|
||||
@@ -20,6 +20,8 @@ const PROVIDERS = {
|
||||
const NOW = Date.now()
|
||||
const TODAY = dayKeyOf(NOW) ?? ''
|
||||
const YESTERDAY = dayKeyOf(NOW - 86_400_000) ?? ''
|
||||
/** Local midnight of NOW — the "today" range's floor, so today's session stays inside it whatever hour the spec runs. */
|
||||
const MIDNIGHT = new Date(new Date(NOW).setHours(0, 0, 0, 0)).getTime()
|
||||
|
||||
/** A wire-shaped timeline head. */
|
||||
function timeline(over: Record<string, unknown> = {}): Record<string, unknown> {
|
||||
@@ -47,7 +49,7 @@ function sessionsSnapshot(): { ids: string[]; byId: Record<string, Record<string
|
||||
a: {
|
||||
displayTitle: 'alpha session',
|
||||
cwd: '/repo/alpha',
|
||||
updatedAt: NOW - 3_600_000,
|
||||
updatedAt: MIDNIGHT,
|
||||
running: true,
|
||||
projectionValues: {
|
||||
contextTimeline: timeline(),
|
||||
@@ -207,8 +209,8 @@ describe('OverviewPanel', () => {
|
||||
await click(rangeButtons[3]) // All
|
||||
assert.equal(queryAll(m.container, '.lc-ov-grid > .lc-ov-session').length, 3, 'the stale session joins')
|
||||
assert.equal(queryAll(m.container, '.lc-stat-value')[0].textContent, '3')
|
||||
await click(rangeButtons[0]) // Last 24 hours
|
||||
assert.equal(queryAll(m.container, '.lc-ov-grid > .lc-ov-session').length, 1, 'only the hour-fresh session stays')
|
||||
await click(rangeButtons[0]) // Today
|
||||
assert.equal(queryAll(m.container, '.lc-ov-grid > .lc-ov-session').length, 1, "only today's session stays")
|
||||
await click(rangeButtons[1]) // Last 7 days
|
||||
assert.equal(queryAll(m.container, '.lc-ov-grid > .lc-ov-session').length, 1, 'only the freshest stays')
|
||||
await click(rangeButtons[2]) // back to 30d
|
||||
|
||||
@@ -195,8 +195,9 @@ describe('billedOf / turnsOf', () => {
|
||||
|
||||
describe('rangeStartOf', () => {
|
||||
test('each window’s start instant; all is unbounded', () => {
|
||||
const now = 31 * 86_400_000
|
||||
assert.equal(rangeStartOf('24h', now), now - 86_400_000)
|
||||
// Local anchors, so the calendar-day window reads the same in every zone.
|
||||
const now = new Date(2026, 8, 20, 12).getTime()
|
||||
assert.equal(rangeStartOf('today', now), new Date(2026, 8, 20).getTime(), 'today opens on the local midnight')
|
||||
assert.equal(rangeStartOf('7d', now), now - 7 * 86_400_000)
|
||||
assert.equal(rangeStartOf('30d', now), now - 30 * 86_400_000)
|
||||
assert.equal(rangeStartOf('all', now), null)
|
||||
@@ -204,28 +205,30 @@ describe('rangeStartOf', () => {
|
||||
})
|
||||
|
||||
describe('filterRows', () => {
|
||||
const midnight = new Date(2026, 8, 11).getTime()
|
||||
const now = midnight + 12 * 3_600_000
|
||||
const rows = [
|
||||
rowOf({ id: 'old', title: 'ancient logs', updatedAt: 100 }),
|
||||
rowOf({ id: 'new', title: 'fresh fix', cwd: '/repo/app', updatedAt: 10 * 86_400_000 }),
|
||||
rowOf({ id: 'old', title: 'ancient logs', updatedAt: midnight - 20 * 86_400_000 }),
|
||||
rowOf({ id: 'new', title: 'fresh fix', cwd: '/repo/app', updatedAt: now - 2 * 3_600_000 }),
|
||||
rowOf({
|
||||
id: 'active',
|
||||
title: 'busy bee',
|
||||
updatedAt: 10 * 86_400_000,
|
||||
updatedAt: now - 2 * 3_600_000,
|
||||
activity: { days: { '2026-09-16': { tokens: 5, requests: 1 }, '2026-09-10': { tokens: 0, requests: 0 } } },
|
||||
}),
|
||||
]
|
||||
const now = 11 * 86_400_000
|
||||
|
||||
test('the range window filters by last activity', () => {
|
||||
assert.deepEqual(filterRows(rows, { range: '24h', day: null, query: '' }, now).map(r => r.id), ['new', 'active'])
|
||||
assert.deepEqual(filterRows(rows, { range: 'today', day: null, query: '' }, now).map(r => r.id), ['new', 'active'])
|
||||
assert.deepEqual(filterRows(rows, { range: '7d', day: null, query: '' }, now).map(r => r.id), ['new', 'active'])
|
||||
assert.deepEqual(filterRows(rows, { range: 'all', day: null, query: '' }, now).map(r => r.id), ['old', 'new', 'active'])
|
||||
// The 24h window's own boundary: inside by minutes, outside by an hour.
|
||||
const hRows = [
|
||||
rowOf({ id: 'fresh', title: 'just now', updatedAt: now - 2 * 3_600_000 }),
|
||||
rowOf({ id: 'yesterday', title: 'a day ago', updatedAt: now - 25 * 3_600_000 }),
|
||||
// Today's own boundary: hours in, one minute before the local midnight out.
|
||||
const dRows = [
|
||||
rowOf({ id: 'in', title: 'this morning', updatedAt: now - 2 * 3_600_000 }),
|
||||
rowOf({ id: 'out', title: 'last night', updatedAt: midnight - 60_000 }),
|
||||
]
|
||||
assert.deepEqual(filterRows(hRows, { range: '24h', day: null, query: '' }, now).map(r => r.id), ['fresh'])
|
||||
assert.deepEqual(filterRows(dRows, { range: 'today', day: null, query: '' }, now).map(r => r.id), ['in'])
|
||||
assert.deepEqual(filterRows(dRows, { range: '7d', day: null, query: '' }, now).map(r => r.id), ['in', 'out'], 'last night is still inside the week')
|
||||
})
|
||||
|
||||
test('the day pin keeps only sessions contributing to that day', () => {
|
||||
@@ -744,10 +747,13 @@ describe('skillLoadsOf', () => {
|
||||
[day(1)]: { tokens: 1, requests: 1, skills: { tdd: { n: 3, last: NOW - 2000 }, grill: { n: 1, last: NOW - 500 } } },
|
||||
}),
|
||||
]
|
||||
assert.deepEqual(skillLoadsOf(rows, { range: '24h', day: null }, NOW), [
|
||||
assert.deepEqual(skillLoadsOf(rows, { range: '7d', day: null }, NOW), [
|
||||
{ name: 'tdd', loads: 5, sessions: 2, last: NOW - 1000 },
|
||||
{ name: 'grill', loads: 1, sessions: 1, last: NOW - 500 },
|
||||
], 'the 10-days-ago entries fall below the floor')
|
||||
assert.deepEqual(skillLoadsOf(rows, { range: 'today', day: null }, NOW), [
|
||||
{ name: 'tdd', loads: 2, sessions: 1, last: NOW - 1000 },
|
||||
], "today's floor admits this very day only")
|
||||
assert.deepEqual(skillLoadsOf(rows, { range: 'all', day: null }, NOW), [
|
||||
{ name: 'tdd', loads: 10, sessions: 2, last: NOW - 1000 },
|
||||
{ name: 'grill', loads: 1, sessions: 1, last: NOW - 500 },
|
||||
@@ -845,8 +851,8 @@ describe('rowLoadedSkill', () => {
|
||||
},
|
||||
},
|
||||
})
|
||||
assert.equal(rowLoadedSkill(row, 'tdd', { range: '24h', day: null }, NOW), true, 'loaded today — inside the 24h floor')
|
||||
assert.equal(rowLoadedSkill(row, 'grill', { range: '24h', day: null }, NOW), false, 'the 10-days-ago load falls below the floor')
|
||||
assert.equal(rowLoadedSkill(row, 'tdd', { range: 'today', day: null }, NOW), true, "loaded today — inside today's floor")
|
||||
assert.equal(rowLoadedSkill(row, 'grill', { range: 'today', day: null }, NOW), false, 'the 10-days-ago load falls below the floor')
|
||||
assert.equal(rowLoadedSkill(row, 'grill', { range: 'all', day: null }, NOW), true)
|
||||
assert.equal(rowLoadedSkill(row, 'tdd', { range: 'all', day: day(1) }, NOW), false, 'the pinned day admits only itself')
|
||||
assert.equal(rowLoadedSkill(row, 'grill', { range: 'all', day: day(10) }, NOW), true)
|
||||
|
||||
Reference in new issue
Block a user