fix: stabilize usage loading and session search

This commit is contained in:
shiyue
2026-08-05 10:41:50 +08:00
parent c61b7434d2
commit 3f00cfe7a4
22 changed files with 109 additions and 47 deletions

View File

@@ -622,8 +622,8 @@ function assertFrontendSidebarCollapseContract() {
'Rich themes should provide isolated rail treatments on top of the shared semantic fallback'
);
assert(
indexSource.includes('style.css?v=20260803-usage-statistics-echarts')
&& indexSource.includes('app.js?v=20260803-usage-statistics-echarts'),
indexSource.includes('style.css?v=20260805-usage-loading-inline')
&& indexSource.includes('app.js?v=20260805-usage-loading-inline'),
'Sidebar interaction assets should share the reviewed cache-busting version'
);
}
@@ -946,8 +946,8 @@ function assertPlanListProgressContract() {
assert(extractorSource.includes('references/source-assets/wasteland-icon-sheet.webp'), 'Plan progress extractor should read the archived source sheet');
assert(!extractorSource.includes('sessions/_attachments'), 'Plan progress extractor should not depend on temporary session attachments');
assert(indexSource.includes('style.css?v=20260803-usage-statistics-echarts'), 'Plan progress CSS should use the current cache-busted URL');
assert(indexSource.includes('app.js?v=20260803-usage-statistics-echarts'), 'Plan progress frontend logic should use the current cache-busted URL');
assert(indexSource.includes('style.css?v=20260805-usage-loading-inline'), 'Plan progress CSS should use the current cache-busted URL');
assert(indexSource.includes('app.js?v=20260805-usage-loading-inline'), 'Plan progress frontend logic should use the current cache-busted URL');
}
function assertFrontendGildedThemeContract() {
@@ -1062,8 +1062,8 @@ function assertFrontendGildedThemeContract() {
assert(contrast('#655446', '#fff7ea') >= 4.5, 'Gilded muted text should remain readable on ivory panels');
assert(contrast('#fff7ea', '#7a3f20') >= 7, 'Gilded primary action text should reach AAA contrast on copper');
assert(themeStyle.includes('@media (prefers-reduced-motion: reduce)'), 'Gilded theme motion should respect reduced-motion preferences');
assert(indexSource.includes('style.css?v=20260803-usage-statistics-echarts'), 'Theme bundle stylesheet should use the current cache-busted asset URL');
assert(indexSource.includes('app.js?v=20260803-usage-statistics-echarts'), 'Theme bundle app script should use the current cache-busted asset URL');
assert(indexSource.includes('style.css?v=20260805-usage-loading-inline'), 'Theme bundle stylesheet should use the current cache-busted asset URL');
assert(indexSource.includes('app.js?v=20260805-usage-loading-inline'), 'Theme bundle app script should use the current cache-busted asset URL');
}
function assertFrontendWastelandThemeContract() {
@@ -1316,8 +1316,8 @@ function assertFrontendWastelandThemeContract() {
assert(contrast('#c9bda6', backgroundColor) >= 4.5, `Wasteland muted text should reach AA contrast on ${backgroundColor}`);
});
assert(indexSource.includes('style.css?v=20260803-usage-statistics-echarts'), 'Wasteland stylesheet should share the cache-busted theme bundle URL');
assert(indexSource.includes('app.js?v=20260803-usage-statistics-echarts'), 'Wasteland registration should share the cache-busted theme bundle URL');
assert(indexSource.includes('style.css?v=20260805-usage-loading-inline'), 'Wasteland stylesheet should share the cache-busted theme bundle URL');
assert(indexSource.includes('app.js?v=20260805-usage-loading-inline'), 'Wasteland registration should share the cache-busted theme bundle URL');
}
function assertFrontendCcwebPromptContract() {
@@ -4449,8 +4449,8 @@ function assertAdvancedSessionSearchContract() {
'Existing sidebar search clear control contract should remain unchanged');
assert(indexSource.includes('id="advanced-search-panel"') && indexSource.includes('id="advanced-search-results"'),
'Advanced search workspace should expose stable panel and result hooks');
assert(indexSource.includes('style.css?v=20260803-usage-statistics-echarts')
&& indexSource.includes('app.js?v=20260803-usage-statistics-echarts'),
assert(indexSource.includes('style.css?v=20260805-usage-loading-inline')
&& indexSource.includes('app.js?v=20260805-usage-loading-inline'),
'Advanced search CSS and frontend script should share the reviewed cache-bust');
const panelStyleStart = styleSource.indexOf('.advanced-search-panel {');
@@ -4500,12 +4500,45 @@ function assertAdvancedSessionSearchContract() {
assert(searchIndexSource.includes("role !== 'user' && role !== 'assistant'")
&& searchIndexSource.includes("type === 'tool_use'")
&& searchIndexSource.includes('function resultTime(result)')
&& searchIndexSource.includes('resultTime(b) - resultTime(a)')
&& searchIndexSource.includes('MAX_QUERY_CHARS_HARD_LIMIT = 200')
&& searchIndexSource.includes('MAX_RESULTS_HARD_LIMIT = 50')
&& !searchIndexSource.includes('new RegExp('),
'Search index should constrain input and exclude system/tool/attachment content without dynamic regexes');
}
async function assertAdvancedSearchTimeOrderingContract() {
const { createSessionSearchIndex } = require(SESSION_SEARCH_INDEX_PATH);
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'cc-web-advanced-search-time-'));
try {
const sessionsDir = path.join(tempRoot, 'sessions');
mkdirp(sessionsDir);
fs.writeFileSync(path.join(sessionsDir, 'session-old-updated.json'), JSON.stringify({
id: 'session-old-updated',
title: '旧命中时间',
updated: '2026-08-05T12:00:00.000Z',
messages: [{ role: 'user', content: '同一关键词', timestamp: '2026-08-01T12:00:00.000Z' }],
}));
fs.writeFileSync(path.join(sessionsDir, 'session-new-message.json'), JSON.stringify({
id: 'session-new-message',
title: '新命中时间',
updated: '2026-08-01T12:00:00.000Z',
messages: [{ role: 'user', content: '同一关键词', timestamp: '2026-08-04T12:00:00.000Z' }],
}));
const index = createSessionSearchIndex({ sessionsDir, maxResults: 50 });
await index.initialize();
const result = index.search({ query: '同一关键词', sort: 'newest', matchMode: 'contains' });
assert(result.results[0]?.sessionId === 'session-new-message',
'最新优先应按命中消息时间排序,而不是按会话 updated 排序');
assert(result.results[0]?.matchTimestamp === '2026-08-04T12:00:00.000Z'
&& result.results[0]?.sessionUpdated === '2026-08-01T12:00:00.000Z',
'搜索结果应同时保留命中消息时间和会话更新时间');
} finally {
fs.rmSync(tempRoot, { recursive: true, force: true });
}
}
function assertUsageStatisticsContract() {
const serverSource = fs.readFileSync(SERVER_PATH, 'utf8');
const frontendSource = fs.readFileSync(PUBLIC_APP_PATH, 'utf8');
@@ -4533,10 +4566,13 @@ function assertUsageStatisticsContract() {
'Usage dashboard should be a chat-main-local workspace');
assert(indexSource.includes('class="usage-dashboard-open"') && !indexSource.includes('class="settings-btn usage-dashboard-open"'),
'Usage entry must use its own class so theme-specific settings pseudo-elements cannot leak');
assert(indexSource.includes('style.css?v=20260803-usage-statistics-echarts')
assert(indexSource.includes('style.css?v=20260805-usage-loading-inline')
&& indexSource.includes('vendor/echarts.min.js?v=5.6.0')
&& indexSource.includes('app.js?v=20260803-usage-statistics-echarts'),
&& indexSource.includes('app.js?v=20260805-usage-loading-inline'),
'Usage dashboard CSS, local ECharts runtime, and frontend script should share the current asset contract');
assert(indexSource.includes('id="usage-dashboard-refresh-label" aria-live="polite"')
&& !indexSource.includes('id="usage-dashboard-loading"'),
'Usage loading feedback should live inside the refresh action without inserting a temporary content row');
assert(metricGridIndex > usagePanelIndex
&& primaryGridIndex > metricGridIndex
&& trendPanelIndex > primaryGridIndex
@@ -4577,7 +4613,11 @@ function assertUsageStatisticsContract() {
assert(usageFunctions.includes("type: 'usage_stats_query'")
&& usageFunctions.includes('usageDashboardState.requestId')
&& usageFunctions.includes('usageDashboardDetail.dataset.motion')
&& usageFunctions.includes('setUsageMcpDetailOpen'),
&& usageFunctions.includes('setUsageMcpDetailOpen')
&& usageFunctions.includes("const refreshText = loading ? '整理中' : '刷新'")
&& usageFunctions.includes("setAttribute('aria-busy', loading ? 'true' : 'false')")
&& !frontendSource.includes('usageDashboardLoading')
&& !styleSource.includes('.usage-dashboard__loading'),
'Usage dashboard should use an independent request id and use an in-context detail drawer with reduced-motion handling');
assert(usageFunctions.includes('function getUsageEchart')
&& usageFunctions.includes('window.echarts')
@@ -5054,6 +5094,7 @@ async function main() {
}
if (regressionTarget === 'advanced-session-search') {
assertAdvancedSessionSearchContract();
await assertAdvancedSearchTimeOrderingContract();
await runAdvancedSessionSearchRegression();
console.log('Advanced session search regression checks passed.');
return;
@@ -5100,6 +5141,7 @@ async function main() {
assertSessionSwitchResilienceContract();
assertSessionSwitchRaceContract();
assertAdvancedSessionSearchContract();
await assertAdvancedSearchTimeOrderingContract();
assertUsageStatisticsUnitChecks();
assertUsageStatisticsContract();
assertCodexAppChildToolRoutingContract();