Skip to content

Commit f721ecf

Browse files
committed
fix(web-ui): align usage today to trailing edge and stop config tab overflow on mobile
- order sessionUsageDaily ascending so the last row is today - auto-scroll the active day label to the container trailing edge on range switch, initial load, and selection - scroll session usage day into view with context-aware alignment (end for today reset, nearest for mid-range selection) - make mobile config segmented control scroll horizontally instead of overflowing the last segment
1 parent 3166e74 commit f721ecf

6 files changed

Lines changed: 87 additions & 4 deletions

File tree

tests/unit/session-usage.test.mjs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,3 +151,28 @@ test('buildUsageHourlyHeatmap returns empty grid for no sessions', () => {
151151
assert.strictEqual(result.grid[0].length, 24);
152152
assert.strictEqual(result.maxSessionCount, 1);
153153
});
154+
155+
test('sessionUsageDaily orders days ascending so the last row is today', async () => {
156+
const computedModule = await import(pathToFileURL(path.join(__dirname, '..', '..', 'web-ui', 'modules', 'app.computed.session.mjs')));
157+
const computed = computedModule.createSessionComputed();
158+
const now = Date.UTC(2026, 3, 10, 12, 0, 0);
159+
const sessions = [
160+
{ source: 'codex', updatedAt: '2026-04-10T08:00:00.000Z', messageCount: 5, totalTokens: 120, contextWindow: 1000, cwd: '/a' },
161+
{ source: 'claude', updatedAt: '2026-04-09T08:00:00.000Z', messageCount: 7, totalTokens: 230, contextWindow: 1000, cwd: '/a' },
162+
{ source: 'codex', updatedAt: '2026-04-04T08:00:00.000Z', messageCount: 3, totalTokens: 90, contextWindow: 1000, cwd: '/b' }
163+
];
164+
const charts = buildUsageChartGroups(sessions, { range: '7d', now });
165+
const vm = {
166+
sessionUsageCharts: charts,
167+
sessionsUsageList: sessions,
168+
sessionsUsageTimeRange: '7d',
169+
sessionsUsageCompareEnabled: false
170+
};
171+
const daily = computed.sessionUsageDaily.call(vm);
172+
assert.ok(Array.isArray(daily.rows) && daily.rows.length > 0, 'sessionUsageDaily should emit rows');
173+
for (let i = 1; i < daily.rows.length; i += 1) {
174+
assert.ok(daily.rows[i - 1].key <= daily.rows[i].key,
175+
`rows should be ascending by dayKey (got ${daily.rows[i - 1].key} before ${daily.rows[i].key})`);
176+
}
177+
assert.strictEqual(daily.rows[daily.rows.length - 1].key, '2026-04-10', 'last row should be today');
178+
});

tests/unit/web-ui-behavior-parity.test.mjs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -729,7 +729,8 @@ test('captured bundled app skeleton only exposes expected data key drift versus
729729
'applyOpencodeSelection',
730730
'selectProjectClaudeMdPath',
731731
'setProjectClaudeMdPathManual',
732-
'loadProjectPathOptions'
732+
'loadProjectPathOptions',
733+
'scrollSessionsUsageDayIntoView'
733734
];
734735
allowedExtraCurrentMethodKeys.push(
735736
'normalizePackageVersion',

web-ui/modules/app.computed.session.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -773,7 +773,7 @@ export function createSessionComputed() {
773773
const rows = currentKeys
774774
.map((key) => byDay.get(key))
775775
.filter(Boolean)
776-
.sort((a, b) => b.key.localeCompare(a.key, 'en-US'));
776+
.sort((a, b) => a.key.localeCompare(b.key, 'en-US'));
777777
const rowsWithCompare = rows.map((row) => {
778778
if (!compareEnabled) {
779779
return { ...row, compareEnabled: false, prevKey: '', prevTokenTotal: 0 };

web-ui/modules/app.methods.session-browser.mjs

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -783,6 +783,7 @@ export function createSessionBrowserMethods(options = {}) {
783783
this.sessionsUsageCompareEnabled = false;
784784
}
785785
void this.loadSessionsUsage({ range });
786+
this.scrollSessionsUsageDayIntoView({ behavior: 'auto' });
786787
},
787788

788789
toggleSessionsUsageCompare() {
@@ -801,12 +802,29 @@ export function createSessionBrowserMethods(options = {}) {
801802
selectSessionsUsageDay(dayKey) {
802803
const normalized = typeof dayKey === 'string' ? dayKey.trim() : '';
803804
this.sessionsUsageSelectedDayKey = normalized;
805+
this.scrollSessionsUsageDayIntoView({ behavior: 'smooth', align: 'nearest' });
804806
},
805807

806808
clearSessionsUsageDay() {
807809
this.sessionsUsageSelectedDayKey = '';
808810
},
809811

812+
scrollSessionsUsageDayIntoView(options = {}) {
813+
if (typeof document === 'undefined' || !document) return;
814+
const behavior = options.behavior === 'smooth' ? 'smooth' : 'auto';
815+
const inline = options.align === 'nearest' ? 'nearest' : 'end';
816+
const scrollFn = () => {
817+
const node = document.querySelector('.usage-wave-label.active');
818+
if (!node || typeof node.scrollIntoView !== 'function') return;
819+
node.scrollIntoView({ inline, block: 'nearest', behavior });
820+
};
821+
if (typeof this.$nextTick === 'function') {
822+
this.$nextTick(scrollFn);
823+
} else {
824+
scrollFn();
825+
}
826+
},
827+
810828
async loadSessionsUsage(options = {}) {
811829
if (this.sessionsUsageLoading) return;
812830
const normalizedRange = typeof options.range === 'string'
@@ -855,7 +873,9 @@ export function createSessionBrowserMethods(options = {}) {
855873
this.sessionsUsageLoadedLimit = limit;
856874
this.sessionsUsageLastLoadedRange = range;
857875
if (!this.sessionsUsageSelectedDayKey && Array.isArray(this.sessionUsageDailyTableRows) && this.sessionUsageDailyTableRows.length > 0) {
858-
this.sessionsUsageSelectedDayKey = this.sessionUsageDailyTableRows[0].key;
876+
const dayKeys = this.sessionUsageDailyTableRows.map((row) => row.key).filter(Boolean).sort((a, b) => b.localeCompare(a, 'en-US'));
877+
this.sessionsUsageSelectedDayKey = dayKeys[0] || this.sessionUsageDailyTableRows[this.sessionUsageDailyTableRows.length - 1].key;
878+
this.scrollSessionsUsageDayIntoView({ behavior: 'auto' });
859879
}
860880
}
861881
}

web-ui/styles/responsive.css

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,24 @@ textarea:focus-visible {
186186

187187
.mode-cards > .segmented-control {
188188
flex-direction: row;
189+
flex-wrap: nowrap;
190+
max-width: 100%;
191+
overflow-x: auto;
192+
overflow-y: hidden;
193+
scrollbar-width: none;
194+
-webkit-overflow-scrolling: touch;
195+
scroll-snap-type: x proximity;
196+
}
197+
198+
.mode-cards > .segmented-control::-webkit-scrollbar {
199+
display: none;
200+
}
201+
202+
.mode-cards > .segmented-control > .segment {
203+
flex: 0 0 auto;
204+
min-width: 0;
205+
white-space: nowrap;
206+
scroll-snap-align: start;
189207
}
190208

191209
.prompts-editor-toolbar {

web-ui/styles/sessions-usage.css

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -342,12 +342,26 @@
342342

343343
.usage-wave-labels {
344344
display: flex;
345-
justify-content: space-between;
345+
flex-wrap: nowrap;
346+
justify-content: flex-start;
346347
margin-top: 12px;
347348
padding: 0 8px;
349+
overflow-x: auto;
350+
overflow-y: hidden;
351+
scrollbar-width: none;
352+
-webkit-overflow-scrolling: touch;
353+
scroll-snap-type: x proximity;
354+
}
355+
356+
.usage-wave-labels::-webkit-scrollbar {
357+
display: none;
348358
}
349359

350360
.usage-wave-label {
361+
flex: 0 0 auto;
362+
min-width: 0;
363+
white-space: nowrap;
364+
scroll-snap-align: start;
351365
font-size: 11px;
352366
color: var(--color-text-muted);
353367
cursor: pointer;
@@ -372,6 +386,11 @@
372386
color: var(--color-brand);
373387
font-weight: 600;
374388
background: linear-gradient(135deg, rgba(200, 121, 99, 0.12), rgba(200, 121, 99, 0.06));
389+
scroll-snap-align: center;
390+
}
391+
392+
.usage-wave-label:last-child {
393+
scroll-snap-align: end;
375394
}
376395

377396
/* ---- 日期详情 ---- */

0 commit comments

Comments
 (0)