fix: stabilize usage loading and session search
|
Before Width: | Height: | Size: 200 KiB After Width: | Height: | Size: 200 KiB |
|
Before Width: | Height: | Size: 200 KiB After Width: | Height: | Size: 200 KiB |
|
Before Width: | Height: | Size: 212 KiB After Width: | Height: | Size: 211 KiB |
|
Before Width: | Height: | Size: 186 KiB After Width: | Height: | Size: 186 KiB |
|
Before Width: | Height: | Size: 188 KiB After Width: | Height: | Size: 189 KiB |
|
Before Width: | Height: | Size: 260 KiB After Width: | Height: | Size: 258 KiB |
|
Before Width: | Height: | Size: 186 KiB After Width: | Height: | Size: 186 KiB |
|
Before Width: | Height: | Size: 201 KiB After Width: | Height: | Size: 201 KiB |
|
Before Width: | Height: | Size: 186 KiB After Width: | Height: | Size: 187 KiB |
|
Before Width: | Height: | Size: 186 KiB After Width: | Height: | Size: 186 KiB |
|
Before Width: | Height: | Size: 203 KiB After Width: | Height: | Size: 203 KiB |
|
Before Width: | Height: | Size: 122 KiB After Width: | Height: | Size: 122 KiB |
|
Before Width: | Height: | Size: 192 KiB After Width: | Height: | Size: 192 KiB |
|
Before Width: | Height: | Size: 307 KiB After Width: | Height: | Size: 307 KiB |
@@ -411,6 +411,29 @@ async function main() {
|
||||
await waitFor(() => evaluate(cdp, "!document.querySelector('#usage-dashboard-panel').hidden"), 10000);
|
||||
await waitFor(() => evaluate(cdp, "document.querySelector('#usage-dashboard-panel').dataset.state !== 'loading'"), 30000);
|
||||
await sleep(320);
|
||||
const loadingLayout = await evaluate(cdp, `(() => {
|
||||
const metricGrid = document.querySelector('.usage-dashboard__metric-grid');
|
||||
const beforeTop = metricGrid.getBoundingClientRect().top;
|
||||
document.querySelector('#usage-dashboard-refresh').click();
|
||||
const afterTop = metricGrid.getBoundingClientRect().top;
|
||||
return {
|
||||
state: document.querySelector('#usage-dashboard-panel').dataset.state,
|
||||
label: document.querySelector('#usage-dashboard-refresh-label').textContent,
|
||||
busy: document.querySelector('#usage-dashboard-refresh').getAttribute('aria-busy'),
|
||||
contentShift: Math.round(Math.abs(afterTop - beforeTop) * 10) / 10,
|
||||
standaloneLoading: !!document.querySelector('#usage-dashboard-loading'),
|
||||
};
|
||||
})()`);
|
||||
if (loadingLayout.state !== 'loading'
|
||||
|| loadingLayout.label !== '整理中'
|
||||
|| loadingLayout.busy !== 'true'
|
||||
|| loadingLayout.contentShift > 0.5
|
||||
|| loadingLayout.standaloneLoading) {
|
||||
throw new Error(`加载状态仍引发布局变化:${JSON.stringify(loadingLayout)}`);
|
||||
}
|
||||
await waitFor(() => evaluate(cdp, `document.querySelector('#usage-dashboard-panel').dataset.state !== 'loading'
|
||||
&& document.querySelector('#usage-dashboard-refresh-label').textContent === '刷新'`), 30000);
|
||||
await sleep(300);
|
||||
await evaluate(cdp, `(() => {
|
||||
const from = document.querySelector('#usage-dashboard-from');
|
||||
const to = document.querySelector('#usage-dashboard-to');
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"generatedAt": "2026-08-03T14:15:13.710Z",
|
||||
"generatedAt": "2026-08-05T01:06:45.134Z",
|
||||
"chrome": "/home/hdzx/.cache/ms-playwright/chromium-1228/chrome-linux64/chrome",
|
||||
"views": [
|
||||
{
|
||||
|
||||
@@ -322,6 +322,17 @@ function updatedTime(doc) {
|
||||
return Number.isFinite(time) ? time : 0;
|
||||
}
|
||||
|
||||
// “最新”按本次命中的消息时间排序;没有消息时间时才回退到会话更新时间。
|
||||
function resultTime(result) {
|
||||
const matchTimes = (result?.matches || [])
|
||||
.map((match) => new Date(match?.timestamp || 0).getTime())
|
||||
.filter((time) => Number.isFinite(time) && time > 0);
|
||||
if (matchTimes.length > 0) return Math.max(...matchTimes);
|
||||
const directTime = new Date(result?.timestamp || 0).getTime();
|
||||
if (Number.isFinite(directTime) && directTime > 0) return directTime;
|
||||
return updatedTime(result);
|
||||
}
|
||||
|
||||
function buildQuery(rawQuery, options) {
|
||||
const maxQueryChars = clampInteger(
|
||||
options.maxQueryChars,
|
||||
@@ -735,6 +746,8 @@ function createSessionSearchIndex(options = {}) {
|
||||
messageIndex: firstMatch ? firstMatch.messageIndex : null,
|
||||
role: firstMatch ? firstMatch.role : null,
|
||||
timestamp: firstMatch ? firstMatch.timestamp : null,
|
||||
matchTimestamp: firstMatch ? firstMatch.timestamp : null,
|
||||
sessionUpdated: doc.updated,
|
||||
title: doc.title,
|
||||
projectName: doc.projectName,
|
||||
updated: doc.updated,
|
||||
@@ -773,8 +786,8 @@ function createSessionSearchIndex(options = {}) {
|
||||
}
|
||||
|
||||
results.sort((a, b) => {
|
||||
if (sort === 'newest') return updatedTime(b) - updatedTime(a) || b.score - a.score || a.sessionId.localeCompare(b.sessionId);
|
||||
return b.score - a.score || updatedTime(b) - updatedTime(a) || a.sessionId.localeCompare(b.sessionId);
|
||||
if (sort === 'newest') return resultTime(b) - resultTime(a) || b.score - a.score || a.sessionId.localeCompare(b.sessionId);
|
||||
return b.score - a.score || resultTime(b) - resultTime(a) || a.sessionId.localeCompare(b.sessionId);
|
||||
});
|
||||
|
||||
return {
|
||||
|
||||
@@ -349,13 +349,13 @@
|
||||
const usageDashboardStatus = $('#usage-dashboard-status');
|
||||
const usageDashboardGeneratedAt = $('#usage-dashboard-generated-at');
|
||||
const usageDashboardRefresh = $('#usage-dashboard-refresh');
|
||||
const usageDashboardRefreshLabel = $('#usage-dashboard-refresh-label');
|
||||
const usageDashboardPeriodWeek = $('#usage-dashboard-period-week');
|
||||
const usageDashboardPeriodMonth = $('#usage-dashboard-period-month');
|
||||
const usageDashboardPeriodCustom = $('#usage-dashboard-period-custom');
|
||||
const usageDashboardFrom = $('#usage-dashboard-from');
|
||||
const usageDashboardTo = $('#usage-dashboard-to');
|
||||
const usageDashboardApply = $('#usage-dashboard-apply');
|
||||
const usageDashboardLoading = $('#usage-dashboard-loading');
|
||||
const usageDashboardError = $('#usage-dashboard-error');
|
||||
const usageDashboardTrend = $('#usage-dashboard-trend');
|
||||
const usageDashboardMcpStatus = $('#usage-dashboard-mcp-status');
|
||||
@@ -4142,7 +4142,8 @@
|
||||
title: result?.title || match?.title || '未命名会话',
|
||||
projectName: result?.projectName || match?.projectName || '',
|
||||
cwd: result?.cwd || match?.cwd || '',
|
||||
updated: match?.timestamp || result?.updated || null,
|
||||
updated: match?.timestamp || result?.matchTimestamp || result?.updated || null,
|
||||
sessionUpdated: result?.sessionUpdated || result?.updated || null,
|
||||
snippet: match?.snippet || result?.snippet || '',
|
||||
messageIndex: Number.isFinite(Number(match?.messageIndex)) ? Number(match.messageIndex) : null,
|
||||
});
|
||||
@@ -4459,19 +4460,23 @@
|
||||
}
|
||||
|
||||
function setUsageDashboardState(state, message = '') {
|
||||
usageDashboardState.loading = state === 'loading';
|
||||
const loading = state === 'loading';
|
||||
usageDashboardState.loading = loading;
|
||||
usageDashboardState.error = state === 'error' ? String(message || '统计查询失败') : '';
|
||||
if (usageDashboardPanel) usageDashboardPanel.dataset.state = state;
|
||||
if (usageDashboardLoading) usageDashboardLoading.hidden = state !== 'loading';
|
||||
if (usageDashboardError) {
|
||||
usageDashboardError.hidden = state !== 'error';
|
||||
usageDashboardError.textContent = state === 'error' ? usageDashboardState.error : '';
|
||||
}
|
||||
if (usageDashboardRefresh) {
|
||||
usageDashboardRefresh.disabled = state === 'loading';
|
||||
usageDashboardRefresh.setAttribute('aria-busy', state === 'loading' ? 'true' : 'false');
|
||||
const refreshText = loading ? '整理中' : '刷新';
|
||||
usageDashboardRefresh.disabled = loading;
|
||||
usageDashboardRefresh.setAttribute('aria-busy', loading ? 'true' : 'false');
|
||||
usageDashboardRefresh.setAttribute('aria-label', loading ? '正在整理统计数据' : '刷新统计');
|
||||
usageDashboardRefresh.title = loading ? '正在整理统计数据' : '刷新统计';
|
||||
if (usageDashboardRefreshLabel) usageDashboardRefreshLabel.textContent = refreshText;
|
||||
}
|
||||
if (usageDashboardApply) usageDashboardApply.disabled = state === 'loading';
|
||||
if (usageDashboardApply) usageDashboardApply.disabled = loading;
|
||||
}
|
||||
|
||||
function formatUsageNumber(value) {
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
document.documentElement.dataset.dividerTime = dividerTime;
|
||||
})();
|
||||
</script>
|
||||
<link rel="stylesheet" href="style.css?v=20260803-usage-statistics-echarts">
|
||||
<link rel="stylesheet" href="style.css?v=20260805-usage-loading-inline">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/atom-one-dark.min.css">
|
||||
</head>
|
||||
<body>
|
||||
@@ -164,7 +164,7 @@
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||
<path d="M21 12a9 9 0 0 0-15.12-6.6L3 8"></path><path d="M3 3v5h5"></path><path d="M3 12a9 9 0 0 0 15.12 6.6L21 16"></path><path d="M16 16h5v5"></path>
|
||||
</svg>
|
||||
<span>刷新</span>
|
||||
<span id="usage-dashboard-refresh-label" aria-live="polite">刷新</span>
|
||||
</button>
|
||||
<button id="usage-dashboard-close" class="usage-dashboard__icon-button usage-dashboard__back" type="button" aria-label="返回聊天" title="返回聊天">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||
@@ -196,7 +196,6 @@
|
||||
</div>
|
||||
|
||||
<div class="usage-dashboard__body">
|
||||
<div id="usage-dashboard-loading" class="usage-dashboard__loading" role="status" hidden>正在整理统计数据</div>
|
||||
<div id="usage-dashboard-error" class="usage-dashboard__error" role="alert" hidden></div>
|
||||
|
||||
<section class="usage-dashboard__metric-grid" aria-label="统计概览">
|
||||
@@ -387,6 +386,6 @@
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/mermaid/10.9.1/mermaid.min.js"></script>
|
||||
<script src="vendor/echarts.min.js?v=5.6.0"></script>
|
||||
<script src="app.js?v=20260803-usage-statistics-echarts"></script>
|
||||
<script src="app.js?v=20260805-usage-loading-inline"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -10209,7 +10209,6 @@ html[data-theme='gilded'] {
|
||||
scrollbar-gutter: stable;
|
||||
}
|
||||
|
||||
.usage-dashboard__loading,
|
||||
.usage-dashboard__error {
|
||||
margin-bottom: 14px;
|
||||
padding: 10px 12px;
|
||||
@@ -10220,18 +10219,6 @@ html[data-theme='gilded'] {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.usage-dashboard__loading::before {
|
||||
content: '';
|
||||
display: inline-block;
|
||||
width: 9px;
|
||||
height: 9px;
|
||||
margin-right: 8px;
|
||||
border: 2px solid var(--usage-dashboard-border);
|
||||
border-top-color: var(--usage-dashboard-accent);
|
||||
border-radius: 50%;
|
||||
animation: usageDashboardSpin 800ms linear infinite;
|
||||
}
|
||||
|
||||
.usage-dashboard__error {
|
||||
color: var(--usage-dashboard-danger);
|
||||
border-color: color-mix(in srgb, var(--usage-dashboard-danger) 30%, transparent);
|
||||
@@ -10812,7 +10799,6 @@ html[data-theme='gilded'] {
|
||||
padding: 8px 14px 30px;
|
||||
}
|
||||
|
||||
.usage-dashboard__loading,
|
||||
.usage-dashboard__error {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
@@ -11950,7 +11936,6 @@ html[data-theme='wasteland'] :is(
|
||||
.usage-dashboard__dates input,
|
||||
.usage-dashboard__metric,
|
||||
.usage-dashboard__panel,
|
||||
.usage-dashboard__loading,
|
||||
.usage-dashboard__error,
|
||||
.usage-dashboard__badge,
|
||||
.usage-dashboard__row-action
|
||||
@@ -11973,7 +11958,6 @@ html[data-theme='wasteland'] .usage-dashboard__metric {
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.usage-dashboard,
|
||||
.usage-dashboard__loading::before,
|
||||
.usage-dashboard__refresh svg,
|
||||
.usage-dashboard-open,
|
||||
.usage-dashboard__icon-button,
|
||||
@@ -11984,8 +11968,4 @@ html[data-theme='wasteland'] .usage-dashboard__metric {
|
||||
transition: none !important;
|
||||
scroll-behavior: auto !important;
|
||||
}
|
||||
|
||||
.usage-dashboard__loading::before {
|
||||
border-color: var(--usage-dashboard-accent);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||