chore: rebuild release package
This commit is contained in:
@@ -618,8 +618,8 @@ function assertFrontendSidebarCollapseContract() {
|
||||
'Rich themes should provide isolated rail treatments on top of the shared semantic fallback'
|
||||
);
|
||||
assert(
|
||||
indexSource.includes('style.css?v=20260718-shared-locator-panel-2')
|
||||
&& indexSource.includes('app.js?v=20260718-shared-locator-panel-2'),
|
||||
indexSource.includes('style.css?v=20260720-plan-progress-icons')
|
||||
&& indexSource.includes('app.js?v=20260720-plan-progress-icons'),
|
||||
'Sidebar interaction assets should share the reviewed cache-busting version'
|
||||
);
|
||||
}
|
||||
@@ -799,6 +799,141 @@ function assertMockCodexAppPromptUserNotTextTriggered() {
|
||||
assert(!source.includes('mcp-ccweb-prompt-user'), 'Regression should not depend on a mock ccweb_prompt_user tool call id');
|
||||
}
|
||||
|
||||
function assertPlanListProgressContract() {
|
||||
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 { createCodexAppRuntime } = require(path.join(REPO_DIR, 'lib', 'codex-app-runtime'));
|
||||
|
||||
const normalizeSource = extractFunctionSource(source, 'normalizePlanProgress');
|
||||
const payloadSource = extractFunctionSource(source, 'planProgressFromPayload');
|
||||
const resolveSource = extractFunctionSource(source, 'resolveToolPlanProgress');
|
||||
const progressApi = new Function(`
|
||||
const toolKind = (tool) => tool?.kind || tool?.meta?.kind || null;
|
||||
${normalizeSource}
|
||||
${payloadSource}
|
||||
${resolveSource}
|
||||
return { normalizePlanProgress, planProgressFromPayload, resolveToolPlanProgress };
|
||||
`)();
|
||||
|
||||
assert(
|
||||
JSON.stringify(progressApi.normalizePlanProgress({ completed: 3, total: 5 })) === JSON.stringify({ completed: 3, total: 5 }),
|
||||
'Plan progress should preserve valid completed and total counts'
|
||||
);
|
||||
assert(
|
||||
JSON.stringify(progressApi.normalizePlanProgress({ completed: 9, total: 5 })) === JSON.stringify({ completed: 5, total: 5 }),
|
||||
'Plan progress should clamp completed counts to the total'
|
||||
);
|
||||
assert(
|
||||
JSON.stringify(progressApi.planProgressFromPayload({ items: [
|
||||
{ completed: true },
|
||||
{ completed: false },
|
||||
{ completed: true },
|
||||
] })) === JSON.stringify({ completed: 2, total: 3 }),
|
||||
'Legacy todo payloads without explicit progress should derive counts from items'
|
||||
);
|
||||
assert(
|
||||
JSON.stringify(progressApi.resolveToolPlanProgress({
|
||||
kind: 'todo_list',
|
||||
meta: { progress: { completed: 3, total: 5 } },
|
||||
})) === JSON.stringify({ completed: 3, total: 5 }),
|
||||
'Todo tool summaries should resolve progress from runtime metadata'
|
||||
);
|
||||
assert(progressApi.resolveToolPlanProgress({ kind: 'command_execution' }) === null, 'Non-plan tools should not render plan progress');
|
||||
|
||||
const sent = [];
|
||||
const runtime = createCodexAppRuntime({
|
||||
wsSend: (_ws, payload) => sent.push(payload),
|
||||
loadSession: () => null,
|
||||
saveSession: () => {},
|
||||
});
|
||||
const entry = { ws: {}, toolCalls: [], fullText: '' };
|
||||
runtime.processCodexAppNotification(entry, {
|
||||
method: 'plan/updated',
|
||||
params: {
|
||||
id: 'regression-plan-progress',
|
||||
status: 'inProgress',
|
||||
plan: [
|
||||
{ step: '完成数据契约', status: 'completed' },
|
||||
{ step: '完成 DOM', status: 'completed' },
|
||||
{ step: '补充回归', status: 'completed' },
|
||||
{ step: '实现样式', status: 'in_progress' },
|
||||
{ step: '浏览器验收', status: 'pending' },
|
||||
],
|
||||
},
|
||||
}, 'plan-progress-session');
|
||||
const update = sent.find((message) => message.type === 'tool_update' && message.toolUseId === 'regression-plan-progress');
|
||||
assert(update, 'Plan updates should emit a tool_update payload');
|
||||
assert(update.meta?.progress?.completed === 3 && update.meta?.progress?.total === 5, 'Plan metadata should expose 3/5 progress');
|
||||
assert(update.input?.progress?.completed === 3 && update.input?.progress?.total === 5, 'Normalized todo input should expose 3/5 progress');
|
||||
const result = JSON.parse(update.result);
|
||||
assert(result.progress?.completed === 3 && result.progress?.total === 5, 'Persisted todo result should expose 3/5 progress');
|
||||
|
||||
const summarySource = extractFunctionSource(source, 'applyToolSummary');
|
||||
const progressElementSource = extractFunctionSource(source, 'createPlanProgressElement');
|
||||
assert(summarySource.includes('createPlanProgressElement(tool)'), 'Tool summaries should append the plan progress element beside the title');
|
||||
assert(progressElementSource.includes("meter.setAttribute('role', 'img')"), 'Plan progress should expose an accessible image role');
|
||||
assert(progressElementSource.includes("meter.setAttribute('aria-label', progressLabel)"), 'Plan progress should announce completed and total counts');
|
||||
assert(progressElementSource.includes('Math.min(progress.total, 12)'), 'Long plans should cap visible dots to protect the header layout');
|
||||
assert(progressElementSource.includes("count.textContent = `${progress.completed}/${progress.total}`"), 'Compacted long plans should keep an exact numeric count');
|
||||
|
||||
assert(styleSource.includes('--plan-progress-complete: var(--success);'), 'Plan progress should inherit the active theme success color');
|
||||
assert(styleSource.includes('--plan-progress-remaining: var(--accent);'), 'Remaining plan progress should inherit the active theme accent color');
|
||||
assert(/\.plan-progress-dot\s*\{[^}]*width:\s*14px;[^}]*height:\s*14px;[^}]*margin-left:\s*8px;/.test(styleSource), 'Desktop plan progress dots should keep the requested 14px size and 8px left margin');
|
||||
assert(/\.plan-progress-dot\.is-complete\s*\{[^}]*background:\s*var\(--plan-progress-complete\);/.test(styleSource), 'Completed plan dots should use the semantic completion token');
|
||||
assert(/\.plan-progress-dot\.is-remaining\s*\{[^}]*background:\s*var\(--plan-progress-remaining\);[^}]*box-shadow:\s*inset 0 0 0 1px[^}]*animation:\s*plan-progress-breathe 1\.8s/.test(styleSource), 'Remaining plan dots should use the semantic color, 1px inner border and breathing animation');
|
||||
assert(/@keyframes plan-progress-breathe\s*\{[\s\S]*?transform:\s*scale\(0\.9\);[\s\S]*?transform:\s*scale\(1\);[\s\S]*?\}/.test(styleSource), 'Plan progress breathing should use a restrained scale cycle');
|
||||
assert(/@media \(prefers-reduced-motion:\s*reduce\)[\s\S]*?\.plan-progress-dot\.is-remaining\s*\{[^}]*animation:\s*none;[^}]*transform:\s*none;/.test(styleSource), 'Plan progress breathing should respect reduced-motion preferences');
|
||||
assert(/@media \(max-width:\s*560px\)[\s\S]*?\.plan-progress-dot\s*\{[^}]*width:\s*5px;[^}]*height:\s*5px;/.test(styleSource), 'Plan progress dots should stay compact on narrow screens');
|
||||
|
||||
assert(/html\[data-theme='wasteland'\] \.plan-progress-dot\s*\{[^}]*width:\s*20px;[^}]*height:\s*20px;[^}]*border-radius:\s*0;[^}]*box-shadow:\s*none;/.test(styleSource), 'Wasteland plan progress should replace circles with 20px transparent icon canvases');
|
||||
assert(/html\[data-theme='wasteland'\] \.plan-progress-dot\.is-complete\s*\{[^}]*url\('assets\/themes\/wasteland\/icons\/plan-progress\/complete\.png'\);/.test(styleSource), 'Wasteland completed tasks should use the local green status jewel');
|
||||
assert(/html\[data-theme='wasteland'\] \.plan-progress-dot\.is-remaining\s*\{[^}]*url\('assets\/themes\/wasteland\/icons\/plan-progress\/remaining\.png'\);[^}]*box-shadow:\s*none;/.test(styleSource), 'Wasteland remaining tasks should use the local gold loading ring without a square inset border');
|
||||
assert(/@media \(max-width:\s*560px\)[\s\S]*?html\[data-theme='wasteland'\] \.plan-progress-dot\s*\{[^}]*width:\s*16px;[^}]*height:\s*16px;/.test(styleSource), 'Wasteland progress icons should remain legible and compact on narrow screens');
|
||||
const isolatedPlanIconRules = Array.from(styleSource.matchAll(/html\[data-theme='([^']+)'\] \.plan-progress-dot\.is-(?:complete|remaining)\s*\{[^}]*icons\/plan-progress\//g));
|
||||
assert(isolatedPlanIconRules.length === 2 && isolatedPlanIconRules.every((match) => match[1] === 'wasteland'), 'Plan progress image assets should stay isolated to the Wasteland theme');
|
||||
|
||||
const planAssetRoot = path.join(WASTELAND_THEME_ASSETS.root, 'icons', 'plan-progress');
|
||||
const planManifestPath = path.join(planAssetRoot, 'manifest.json');
|
||||
assert(fs.existsSync(planManifestPath), 'Wasteland plan progress should ship a reproducible asset manifest');
|
||||
const planManifest = JSON.parse(fs.readFileSync(planManifestPath, 'utf8'));
|
||||
assert(planManifest.source_sha256 === '482edda2dbc8932b3c209686fab89f50fa8600242b16379b39fa6657e722410e', 'Plan progress manifest should retain the reviewed source sheet hash');
|
||||
const expectedPlanAssets = {
|
||||
complete: {
|
||||
semantic: '已完成',
|
||||
card: 'R2C3 STATUS (ONLINE)',
|
||||
sha256: '2f75c0dfc105e7fd50b1c90d3ca3b2439306afd50c2a0b5ebdeb674cd592516a',
|
||||
},
|
||||
remaining: {
|
||||
semantic: '未完成',
|
||||
card: 'R2C4 LOADING',
|
||||
sha256: '90decca2d4b8204032fa06c0bbde6e9f49afc11bf4fabe21b8307caf5769d672',
|
||||
},
|
||||
};
|
||||
assert(Array.isArray(planManifest.assets) && planManifest.assets.length === 2, 'Plan progress manifest should list exactly two semantic assets');
|
||||
Object.entries(expectedPlanAssets).forEach(([name, expected]) => {
|
||||
const entry = planManifest.assets.find((asset) => asset.name === name);
|
||||
assert(entry?.semantic === expected.semantic && entry?.source_card === expected.card, `${name} progress icon should keep its reviewed semantic and source card`);
|
||||
assert(entry?.source_variant === '16px', `${name} progress icon should be cut from the native 16px source variant`);
|
||||
assert(entry?.size?.[0] === 20 && entry?.size?.[1] === 20, `${name} progress icon manifest should retain a 20x20 canvas`);
|
||||
assert(Array.isArray(entry?.alpha_bbox) && entry.alpha_bbox[2] >= 16 && entry.alpha_bbox[3] >= 16, `${name} progress icon should retain a visible alpha subject`);
|
||||
const assetPath = path.join(planAssetRoot, `${name}.png`);
|
||||
assert(fs.existsSync(assetPath), `Wasteland should ship ${name}.png`);
|
||||
const asset = fs.readFileSync(assetPath);
|
||||
assert(asset.subarray(0, 8).toString('hex') === '89504e470d0a1a0a', `${name}.png should remain a PNG`);
|
||||
assert(asset.readUInt32BE(16) === 20 && asset.readUInt32BE(20) === 20 && asset.readUInt8(25) === 6, `${name}.png should remain a 20x20 RGBA image`);
|
||||
const actualHash = crypto.createHash('sha256').update(asset).digest('hex');
|
||||
assert(actualHash === expected.sha256 && entry.sha256 === expected.sha256, `${name}.png should match the reviewed manifest hash`);
|
||||
});
|
||||
const extractorPath = path.join(REPO_DIR, '.trellis', 'tasks', '07-17-gilded-wasteland-theme', 'research', 'extract_plan_progress_icons.py');
|
||||
const extractorSource = fs.readFileSync(extractorPath, 'utf8');
|
||||
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=20260720-plan-progress-icons'), 'Plan progress CSS should use the current cache-busted URL');
|
||||
assert(indexSource.includes('app.js?v=20260720-plan-progress-icons'), 'Plan progress frontend logic should use the current cache-busted URL');
|
||||
}
|
||||
|
||||
function assertFrontendGildedThemeContract() {
|
||||
const source = fs.readFileSync(PUBLIC_APP_PATH, 'utf8');
|
||||
const styleSource = fs.readFileSync(PUBLIC_STYLE_PATH, 'utf8');
|
||||
@@ -911,8 +1046,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=20260718-shared-locator-panel-2'), 'Theme bundle stylesheet should use the current cache-busted asset URL');
|
||||
assert(indexSource.includes('app.js?v=20260718-shared-locator-panel-2'), 'Theme bundle app script should use the current cache-busted asset URL');
|
||||
assert(indexSource.includes('style.css?v=20260720-plan-progress-icons'), 'Theme bundle stylesheet should use the current cache-busted asset URL');
|
||||
assert(indexSource.includes('app.js?v=20260720-plan-progress-icons'), 'Theme bundle app script should use the current cache-busted asset URL');
|
||||
}
|
||||
|
||||
function assertFrontendWastelandThemeContract() {
|
||||
@@ -1165,8 +1300,8 @@ function assertFrontendWastelandThemeContract() {
|
||||
assert(contrast('#c9bda6', backgroundColor) >= 4.5, `Wasteland muted text should reach AA contrast on ${backgroundColor}`);
|
||||
});
|
||||
|
||||
assert(indexSource.includes('style.css?v=20260718-shared-locator-panel-2'), 'Wasteland stylesheet should share the cache-busted theme bundle URL');
|
||||
assert(indexSource.includes('app.js?v=20260718-shared-locator-panel-2'), 'Wasteland registration should share the cache-busted theme bundle URL');
|
||||
assert(indexSource.includes('style.css?v=20260720-plan-progress-icons'), 'Wasteland stylesheet should share the cache-busted theme bundle URL');
|
||||
assert(indexSource.includes('app.js?v=20260720-plan-progress-icons'), 'Wasteland registration should share the cache-busted theme bundle URL');
|
||||
}
|
||||
|
||||
function assertFrontendCcwebPromptContract() {
|
||||
@@ -3303,6 +3438,7 @@ async function main() {
|
||||
}
|
||||
if (regressionTarget === 'wasteland-theme') {
|
||||
assertFrontendWastelandThemeContract();
|
||||
assertPlanListProgressContract();
|
||||
console.log('Wasteland theme regression checks passed.');
|
||||
return;
|
||||
}
|
||||
@@ -3311,6 +3447,11 @@ async function main() {
|
||||
console.log('Sidebar collapse regression checks passed.');
|
||||
return;
|
||||
}
|
||||
if (regressionTarget === 'plan-list-progress') {
|
||||
assertPlanListProgressContract();
|
||||
console.log('Plan List progress regression checks passed.');
|
||||
return;
|
||||
}
|
||||
if (regressionTarget === 'runtime-image-send') {
|
||||
await runRuntimeImageSendTarget();
|
||||
console.log('Runtime image send regression checks passed.');
|
||||
@@ -3342,6 +3483,7 @@ async function main() {
|
||||
assertFrontendMarkdownLinkContract();
|
||||
assertMockCodexAppPromptUserNotTextTriggered();
|
||||
assertFrontendMcpReloadContract();
|
||||
assertPlanListProgressContract();
|
||||
assertFrontendSubagentCardMetadataContract();
|
||||
assertCodexAppRuntimeSubAgentActivityContract();
|
||||
assertFrontendPrimaryCodexAppUiContract();
|
||||
|
||||
Reference in New Issue
Block a user