优化目录候选文件名显示并重新打包

This commit is contained in:
shiyue
2026-09-11 13:56:43 +08:00
parent b8fcc12229
commit 965aefe9a4
6 changed files with 371 additions and 14 deletions

View File

@@ -671,7 +671,7 @@ function assertFrontendSidebarCollapseContract() {
'Rich themes should provide isolated rail treatments on top of the shared semantic fallback'
);
assert(
indexSource.includes('style.css?v=20260818-goal-mode-label')
indexSource.includes('style.css?v=20260911-file-suggestion')
&& indexSource.includes('app.js?v=__CC_WEB_FRONTEND_ASSET_VERSION__'),
'Sidebar interaction assets should share the reviewed cache-busting version'
);
@@ -900,6 +900,42 @@ function assertFrontendComposerMcpContract() {
assert(menuStart >= 0 && menuEnd > menuStart, 'Frontend should define showCmdMenu before requestComposerSuggestions');
const menuBlock = source.slice(menuStart, menuEnd);
assert(/item\.kind\s*===\s*'mcp'/.test(menuBlock) && menuBlock.includes("'MCP'"), 'Composer menu should render MCP item labels');
assert(
source.includes('function getComposerFileSuggestionDisplay(item)')
&& menuBlock.includes('getComposerFileSuggestionDisplay(item)')
&& menuBlock.includes("' file-suggestion'")
&& menuBlock.includes('escapeHtmlAttr(fileDisplay.fullPath)'),
'Composer file suggestions should show the basename first while retaining the full path as a tooltip'
);
const fileDisplaySource = extractFunctionSource(source, 'getComposerFileSuggestionDisplay');
const fileDisplayApi = new Function(`${fileDisplaySource}; return getComposerFileSuggestionDisplay;`)();
const nestedFileDisplay = fileDisplayApi({
kind: 'file',
itemType: 'file',
name: '01_设计书说明文档/wayfinder/README.md',
label: '@01_设计书说明文档/wayfinder/README.md',
insertion: '@01_设计书说明文档/wayfinder/README.md',
});
assert(nestedFileDisplay.primaryText === 'README.md', 'Nested file suggestions should keep the final filename as the primary text');
assert(nestedFileDisplay.secondaryText === '@01_设计书说明文档/wayfinder/', 'Nested file suggestions should show the parent path as secondary text');
assert(nestedFileDisplay.fullPath === '@01_设计书说明文档/wayfinder/README.md', 'Nested file suggestions should retain the complete insertion path');
const nestedDirectoryDisplay = fileDisplayApi({
kind: 'file',
itemType: 'directory',
name: '01_设计书说明文档/wayfinder/hooks',
label: '@01_设计书说明文档/wayfinder/hooks/',
insertion: '@01_设计书说明文档/wayfinder/hooks/',
});
assert(nestedDirectoryDisplay.primaryText === 'hooks/', 'Nested directory suggestions should keep the final directory name and slash visible');
assert(nestedDirectoryDisplay.secondaryText === '@01_设计书说明文档/wayfinder/', 'Nested directory suggestions should show the parent path as secondary text');
const styleSource = fs.readFileSync(PUBLIC_STYLE_PATH, 'utf8');
assert(
/\.cmd-menu\s*\{[\s\S]*?width:\s*min\(560px,\s*calc\(100vw\s*-\s*32px\)\)/.test(styleSource)
&& styleSource.includes('.cmd-item.file-suggestion .cmd-item-cmd')
&& styleSource.includes('.cmd-item.file-suggestion .cmd-item-desc')
&& /\.cmd-menu\s*\{[^}]*left:\s*10px;[^}]*right:\s*10px;[^}]*width:\s*auto;/.test(styleSource),
'Composer menu should reserve enough width and style file path hierarchy'
);
const selectStart = source.indexOf('function selectComposerItemByIndex(index)');
const selectEnd = source.indexOf('\n function selectCmdMenuItem()', selectStart);
assert(selectStart >= 0 && selectEnd > selectStart, 'Frontend should define selectComposerItemByIndex before selectCmdMenuItem');
@@ -1154,7 +1190,7 @@ 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=20260818-goal-mode-label'), 'Plan progress CSS should use the current cache-busted URL');
assert(indexSource.includes('style.css?v=20260911-file-suggestion'), 'Plan progress CSS should use the current cache-busted URL');
assert(indexSource.includes('app.js?v=__CC_WEB_FRONTEND_ASSET_VERSION__'), 'Plan progress frontend logic should use the dynamic cache-busted URL');
}
@@ -1270,7 +1306,7 @@ 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=20260818-goal-mode-label'), 'Theme bundle stylesheet should use the current cache-busted asset URL');
assert(indexSource.includes('style.css?v=20260911-file-suggestion'), 'Theme bundle stylesheet should use the current cache-busted asset URL');
assert(indexSource.includes('app.js?v=__CC_WEB_FRONTEND_ASSET_VERSION__'), 'Theme bundle app script should use the dynamic cache-busted asset URL');
}
@@ -1524,7 +1560,7 @@ function assertFrontendWastelandThemeContract() {
assert(contrast('#c9bda6', backgroundColor) >= 4.5, `Wasteland muted text should reach AA contrast on ${backgroundColor}`);
});
assert(indexSource.includes('style.css?v=20260818-goal-mode-label'), 'Wasteland stylesheet should share the cache-busted theme bundle URL');
assert(indexSource.includes('style.css?v=20260911-file-suggestion'), 'Wasteland stylesheet should share the cache-busted theme bundle URL');
assert(indexSource.includes('app.js?v=__CC_WEB_FRONTEND_ASSET_VERSION__'), 'Wasteland registration should share the dynamic cache-busted theme bundle URL');
}
@@ -2554,7 +2590,7 @@ function assertGoalModeTitleContract() {
assert(styleSource.includes('.msg.user.goal-message .msg-bubble'), 'Goal label styling should remain scoped to user Goal bubbles');
assert(styleSource.includes('.goal-message-label'), 'Goal mode label should have a dedicated style');
assert(styleSource.includes("html[data-theme='wasteland'] .msg.user.goal-message .goal-message-label"), 'Wasteland should provide a dedicated Goal label treatment');
assert(indexSource.includes('style.css?v=20260818-goal-mode-label'), 'Goal label CSS should use the current cache-busted URL');
assert(indexSource.includes('style.css?v=20260911-file-suggestion'), 'Goal label CSS should use the current cache-busted URL');
assert(serverSource.includes('GOAL_DERIVED_TITLE_MAX_CHARS = 60'), 'Goal-derived titles should keep the 60-character limit');
assert(serverSource.includes('function isDefaultConversationTitle(session)'), 'Goal title updates should use a default-title guard');
assert(serverSource.includes('function applyDerivedGoalConversationTitle(session, objective)'), 'Goal title updates should stay in the /goal-specific server path');
@@ -5568,7 +5604,7 @@ 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=20260818-goal-mode-label')
assert(indexSource.includes('style.css?v=20260911-file-suggestion')
&& indexSource.includes('app.js?v=__CC_WEB_FRONTEND_ASSET_VERSION__'),
'Advanced search CSS and frontend script should share the reviewed cache-bust');
@@ -5685,7 +5721,7 @@ 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=20260818-goal-mode-label')
assert(indexSource.includes('style.css?v=20260911-file-suggestion')
&& indexSource.includes('vendor/echarts.min.js?v=5.6.0')
&& indexSource.includes('app.js?v=__CC_WEB_FRONTEND_ASSET_VERSION__'),
'Usage dashboard CSS, local ECharts runtime, and frontend script should share the current asset contract');
@@ -6433,6 +6469,11 @@ async function main() {
console.log('Composer slash routing regression checks passed.');
return;
}
if (regressionTarget === 'composer-file-display') {
assertFrontendComposerMcpContract();
console.log('Composer file display regression checks passed.');
return;
}
if (regressionTarget === 'codexapp-unrouted-routing') {
assertCodexAppUnroutedNotificationRoutingContract();
console.log('Codex App unrouted routing regression checks passed.');
@@ -6847,6 +6888,12 @@ async function main() {
'command = "definitely-missing-mcp-command"',
].join('\n'));
fs.writeFileSync(path.join(codexInitCwd, 'context.txt'), 'Composer file context body.');
const nestedComposerRoot = path.join(codexInitCwd, '01_设计书说明文档', 'wayfinder');
const nestedComposerHooks = path.join(nestedComposerRoot, 'hooks', 'deep');
mkdirp(nestedComposerHooks);
fs.writeFileSync(path.join(nestedComposerRoot, 'README.md'), 'Nested composer file context body.');
fs.writeFileSync(path.join(nestedComposerRoot, 'hooks', 'index.md'), 'Nested composer directory file.');
fs.writeFileSync(path.join(nestedComposerHooks, 'notes.txt'), 'Three-level composer file context body.');
ws.send(JSON.stringify({ type: 'new_session', agent: 'codex', cwd: codexInitCwd, mode: 'plan' }));
const codexSession = await nextMessage(messages, ws, (msg) => msg.type === 'session_info' && msg.agent === 'codex' && msg.cwd === codexInitCwd);
assert(codexSession.mode === 'plan', 'Codex new_session should follow requested mode');
@@ -6964,6 +7011,35 @@ async function main() {
assert(fileComposer.items.some((item) => item.kind === 'file' && item.name === 'context.txt'), 'Composer file suggestions should include cwd file');
assert(!fileComposer.items.some((item) => item.kind === 'mcp'), 'Composer file suggestions should not include MCP tools');
ws.send(JSON.stringify({
type: 'composer_suggestions',
requestId: 'reg-file-nested',
trigger: '@',
query: '01_设计书说明文档/wayfinder/',
sessionId: codexSession.sessionId,
agent: 'codex',
}));
const nestedFileComposer = await nextMessage(messages, ws, (msg) => msg.type === 'composer_suggestions' && msg.requestId === 'reg-file-nested');
const nestedFileSuggestion = nestedFileComposer.items.find((item) => item.kind === 'file' && item.name === '01_设计书说明文档/wayfinder/README.md');
const nestedDirectorySuggestion = nestedFileComposer.items.find((item) => item.kind === 'file' && item.itemType === 'directory' && item.name === '01_设计书说明文档/wayfinder/hooks');
assert(nestedFileSuggestion, 'Composer file suggestions should include a two-level nested file');
assert(nestedDirectorySuggestion, 'Composer file suggestions should include a two-level nested directory');
assert(nestedFileSuggestion.label === '@01_设计书说明文档/wayfinder/README.md' && nestedFileSuggestion.insertion === nestedFileSuggestion.label, 'Nested file suggestions should retain the complete insertion path');
assert(nestedDirectorySuggestion.label === '@01_设计书说明文档/wayfinder/hooks/' && nestedDirectorySuggestion.insertion === nestedDirectorySuggestion.label && nestedDirectorySuggestion.appendSpace === false, 'Nested directory suggestions should retain the trailing slash and continue completion');
ws.send(JSON.stringify({
type: 'composer_suggestions',
requestId: 'reg-file-deep',
trigger: '@',
query: '01_设计书说明文档/wayfinder/hooks/deep/',
sessionId: codexSession.sessionId,
agent: 'codex',
}));
const deepFileComposer = await nextMessage(messages, ws, (msg) => msg.type === 'composer_suggestions' && msg.requestId === 'reg-file-deep');
const deepFileSuggestion = deepFileComposer.items.find((item) => item.kind === 'file' && item.name === '01_设计书说明文档/wayfinder/hooks/deep/notes.txt');
assert(deepFileSuggestion, 'Composer file suggestions should include a three-level nested file');
assert(deepFileSuggestion.label === '@01_设计书说明文档/wayfinder/hooks/deep/notes.txt' && deepFileSuggestion.insertion === deepFileSuggestion.label, 'Three-level file suggestions should retain the complete insertion path');
ws.send(JSON.stringify({
type: 'message',
text: '@shipit @quick-note @context.txt $regression-skill $project-skill run composer regression',