chore: rebuild release package

This commit is contained in:
shiyue
2026-07-18 12:39:55 +08:00
parent a76de06c47
commit b90f5e1e44
14 changed files with 512 additions and 13 deletions

View File

@@ -524,6 +524,69 @@ function createFakeCodexConfig(homeDir, { model = 'gpt-5.5', reasoningEffort = '
].join('\n'));
}
function assertFrontendSidebarCollapseContract() {
const source = fs.readFileSync(PUBLIC_APP_PATH, 'utf8');
const indexSource = fs.readFileSync(PUBLIC_INDEX_PATH, 'utf8');
const styleSource = fs.readFileSync(PUBLIC_STYLE_PATH, 'utf8');
const buttonMarkup = indexSource.match(/<button id="menu-btn"[^>]*>/)?.[0] || '';
assert(buttonMarkup, 'Chat header should retain the sidebar toggle next to the title');
['type="button"', 'aria-controls="sidebar"', 'aria-expanded="true"', 'aria-label="收起左侧菜单"'].forEach((attribute) => {
assert(buttonMarkup.includes(attribute), `Sidebar toggle should expose ${attribute}`);
});
assert(source.includes("const SIDEBAR_COLLAPSE_STORAGE_KEY = 'cc-web-sidebar-collapsed'"), 'Desktop sidebar preference should use a stable storage key');
assert(source.includes("const SIDEBAR_DRAWER_MEDIA_QUERY = '(max-width: 768px)'"), 'Sidebar drawer mode should match the mobile CSS breakpoint');
assert(source.includes("const SIDEBAR_DESKTOP_COLLAPSE_MEDIA_QUERY = '(width > 768px) and (hover: hover) and (pointer: fine)'"), 'Desktop collapse mode should match the hover rail CSS capabilities');
assert(source.includes("app.classList.toggle('sidebar-collapsed', nextCollapsed)"), 'Desktop sidebar should keep its fixed state on the app root');
assert(source.includes('persistSidebarCollapsedPreference(nextCollapsed)'), 'Desktop sidebar toggle should persist the user preference');
assert(
source.includes("menuBtn.setAttribute('aria-expanded', String(expanded))")
&& source.includes("menuBtn.setAttribute('aria-label', label)"),
'Sidebar toggle should synchronize its accessible state and label'
);
assert(
/menuBtn\.addEventListener\('click',[\s\S]*?if \(isSidebarDrawerMode\(\)\)[\s\S]*?openSidebar\(\)[\s\S]*?if \(isSidebarDesktopCollapseMode\(\)\)[\s\S]*?setSidebarCollapsed\(/.test(source),
'Sidebar toggle should preserve the mobile drawer path before using desktop fixed collapse'
);
assert(
source.includes('[sidebarDrawerMedia, sidebarDesktopCollapseMedia].forEach((media) =>')
&& source.includes("media.addEventListener('change', handleSidebarInputModeChange)"),
'Sidebar should reset transient state when either drawer or desktop collapse mode changes'
);
assert(styleSource.includes('--sidebar-rail-width: 14px'), 'Collapsed desktop sidebar should retain a narrow hover rail');
assert(
styleSource.includes('@media (width > 768px) and (hover: hover) and (pointer: fine)'),
'Desktop hover behavior should stay isolated from touch and narrow-screen drawers'
);
assert(
styleSource.includes('margin-right: calc(var(--sidebar-rail-width) - var(--sidebar-width))')
&& styleSource.includes('transform: translateX(calc(var(--sidebar-rail-width) - var(--sidebar-width)))'),
'Collapsed sidebar should only consume the rail width without resizing theme-specific sidebars'
);
assert(
styleSource.includes('.app.sidebar-collapsed .sidebar:is(:hover, :focus-within)')
&& styleSource.includes('transform: translateX(0)'),
'Collapsed sidebar should temporarily expand for pointer hover and keyboard focus'
);
assert(styleSource.includes('.menu-btn:focus-visible'), 'Sidebar toggle should retain a visible keyboard focus treatment');
assert(
source.includes('if (isSidebarDesktopCollapseMode())')
&& source.includes("const expanded = isDrawer\n ? sidebar.classList.contains('open')\n : (isDesktopCollapsible ? !app.classList.contains('sidebar-collapsed') : true)"),
'Unsupported wide-screen pointer modes should stay visually and accessibly expanded'
);
assert(
styleSource.includes("html[data-theme='gilded'] .app.sidebar-collapsed .sidebar::after")
&& styleSource.includes("html[data-theme='wasteland'] .app.sidebar-collapsed .sidebar::after"),
'Rich themes should provide isolated rail treatments on top of the shared semantic fallback'
);
assert(
indexSource.includes('style.css?v=20260718-wasteland-bg-right-4')
&& indexSource.includes('app.js?v=20260718-wasteland-bg-right-4'),
'Sidebar interaction assets should share the reviewed cache-busting version'
);
}
function assertFrontendGenerationControlsContract() {
const source = fs.readFileSync(PUBLIC_APP_PATH, 'utf8');
const controlsStart = source.indexOf('function updateGenerationControls()');
@@ -811,8 +874,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=20260717-wasteland-hifi-23'), 'Theme bundle stylesheet should use the current cache-busted asset URL');
assert(indexSource.includes('app.js?v=20260718-theme-bundle-4'), 'Theme bundle app script should use the current cache-busted asset URL');
assert(indexSource.includes('style.css?v=20260718-wasteland-bg-right-4'), 'Theme bundle stylesheet should use the current cache-busted asset URL');
assert(indexSource.includes('app.js?v=20260718-wasteland-bg-right-4'), 'Theme bundle app script should use the current cache-busted asset URL');
}
function assertFrontendWastelandThemeContract() {
@@ -826,6 +889,8 @@ function assertFrontendWastelandThemeContract() {
: '';
const themeStyleStart = styleSource.indexOf('/* === 暗金荒野');
const themeStyle = themeStyleStart >= 0 ? styleSource.slice(themeStyleStart) : '';
const highFidelityStyleStart = styleSource.indexOf('/* === 暗金荒野高保真复刻');
const highFidelityStyle = highFidelityStyleStart >= 0 ? styleSource.slice(highFidelityStyleStart) : '';
const themeOption = source.match(/\{\s*value: 'wasteland',[\s\S]*?\n\s*\},/);
assert(themeOption, 'Frontend should register the wasteland theme option');
@@ -853,6 +918,7 @@ function assertFrontendWastelandThemeContract() {
'--wasteland-cut: polygon(',
].forEach((token) => assert(themeVars.includes(token), `Wasteland theme is missing semantic token: ${token}`));
assert(themeStyleStart >= 0, 'Stylesheet should include a dedicated wasteland component layer');
assert(highFidelityStyleStart >= 0, 'Stylesheet should retain the final high-fidelity wasteland override layer');
const bodyRule = themeStyle.match(/html\[data-theme='wasteland'\] body\s*\{([^}]*)\}/);
assert(bodyRule && bodyRule[1].includes("url('assets/themes/wasteland/background.webp')"), 'Wasteland body should render the reviewed local knight background');
@@ -1037,7 +1103,14 @@ function assertFrontendWastelandThemeContract() {
});
assert(!/url\(\s*['"]?https?:\/\//i.test(`${themeVars}\n${themeStyle}\n${JSON.stringify(manifest)}`), 'Wasteland variables, components and asset manifest should not load remote URLs');
assert(/@media \(max-width:\s*720px\)[\s\S]*?html\[data-theme='wasteland'\] body\s*\{[\s\S]*?background-position:\s*center,\s*center,\s*70% 50%;/.test(themeStyle), 'Wasteland 720px layout should reposition and darken the local background');
assert(
/@media \(min-width:\s*721px\) and \(max-width:\s*1100px\)[\s\S]*?html\[data-theme='wasteland'\] body\s*\{[^}]*background-position:\s*center,\s*center,\s*75% 50%;/.test(highFidelityStyle),
'Wasteland 721-1100px layout should reveal the right side of the local background'
);
assert(
/@media \(max-width:\s*720px\)[\s\S]*?html\[data-theme='wasteland'\] body\s*\{[^}]*background-position:\s*center,\s*center,\s*75% 50%;/.test(highFidelityStyle),
'Wasteland 720px layout should keep the local background right-aligned'
);
assert(/@media \(max-width:\s*560px\)[\s\S]*?html\[data-theme='wasteland'\] \.input-wrapper\s*\{[\s\S]*?min-width:\s*0;[\s\S]*?min-height:\s*48px;/.test(themeStyle), 'Wasteland 560px layout should keep the composer within the viewport');
assert(/@media \(prefers-reduced-motion:\s*reduce\)[\s\S]*?html\[data-theme='wasteland'\] \*[\s\S]*?animation:\s*none !important;[\s\S]*?transition:\s*none !important;/.test(themeStyle), 'Wasteland motion should respect reduced-motion preferences');
@@ -1055,8 +1128,8 @@ function assertFrontendWastelandThemeContract() {
assert(contrast('#c9bda6', backgroundColor) >= 4.5, `Wasteland muted text should reach AA contrast on ${backgroundColor}`);
});
assert(indexSource.includes('style.css?v=20260717-wasteland-hifi-23'), 'Wasteland stylesheet should share the cache-busted theme bundle URL');
assert(indexSource.includes('app.js?v=20260718-theme-bundle-4'), 'Wasteland registration should share the cache-busted theme bundle URL');
assert(indexSource.includes('style.css?v=20260718-wasteland-bg-right-4'), 'Wasteland stylesheet should share the cache-busted theme bundle URL');
assert(indexSource.includes('app.js?v=20260718-wasteland-bg-right-4'), 'Wasteland registration should share the cache-busted theme bundle URL');
}
function assertFrontendCcwebPromptContract() {
@@ -2730,6 +2803,11 @@ async function main() {
console.log('Wasteland theme regression checks passed.');
return;
}
if (regressionTarget === 'sidebar-collapse') {
assertFrontendSidebarCollapseContract();
console.log('Sidebar collapse regression checks passed.');
return;
}
if (regressionTarget === 'runtime-image-send') {
await runRuntimeImageSendTarget();
console.log('Runtime image send regression checks passed.');
@@ -2748,6 +2826,7 @@ async function main() {
assertUnlimitedImageAttachmentsContract();
assertFrontendGildedThemeContract();
assertFrontendWastelandThemeContract();
assertFrontendSidebarCollapseContract();
assertFrontendGenerationControlsContract();
assertFrontendComposerMcpContract();
assertComposerSlashRoutingContract();