fix: 消除会话列表解析阻塞并按需加载工具详情
This commit is contained in:
@@ -684,7 +684,7 @@ function assertFrontendSidebarCollapseContract() {
|
||||
'Rich themes should provide isolated rail treatments on top of the shared semantic fallback'
|
||||
);
|
||||
assert(
|
||||
indexSource.includes('style.css?v=20260913-history-control-position')
|
||||
indexSource.includes('style.css?v=20261017-file-browser-image-gallery')
|
||||
&& indexSource.includes('app.js?v=__CC_WEB_FRONTEND_ASSET_VERSION__'),
|
||||
'Sidebar interaction assets should share the reviewed cache-busting version'
|
||||
);
|
||||
@@ -1231,7 +1231,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=20260913-history-control-position'), 'Plan progress CSS should use the current cache-busted URL');
|
||||
assert(indexSource.includes('style.css?v=20261017-file-browser-image-gallery'), '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');
|
||||
}
|
||||
|
||||
@@ -1347,7 +1347,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=20260913-history-control-position'), 'Theme bundle stylesheet should use the current cache-busted asset URL');
|
||||
assert(indexSource.includes('style.css?v=20261017-file-browser-image-gallery'), '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');
|
||||
}
|
||||
|
||||
@@ -1601,7 +1601,7 @@ function assertFrontendWastelandThemeContract() {
|
||||
assert(contrast('#c9bda6', backgroundColor) >= 4.5, `Wasteland muted text should reach AA contrast on ${backgroundColor}`);
|
||||
});
|
||||
|
||||
assert(indexSource.includes('style.css?v=20260913-history-control-position'), 'Wasteland stylesheet should share the cache-busted theme bundle URL');
|
||||
assert(indexSource.includes('style.css?v=20261017-file-browser-image-gallery'), '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');
|
||||
}
|
||||
|
||||
@@ -1652,7 +1652,7 @@ function assertFrontendFileBrowserImageContract() {
|
||||
assert(browser.includes('/api/fs/image?sessionId='), 'Image previews should use the authenticated image endpoint');
|
||||
assert(browser.includes('/api/fs/read?sessionId='), 'Text previews should retain the text endpoint');
|
||||
assert(source.includes('async function fetchAuthImage(url, signal)') && source.includes('Authorization: `Bearer ${authToken}`'), 'Image fetch should use Bearer authentication');
|
||||
assert(browser.includes("mimeType === 'image/svg+xml'") && browser.includes('reader.readAsDataURL(blob);'), 'SVG previews should use isolated data URLs');
|
||||
assert(browser.includes("mimeType !== 'image/svg+xml'") && browser.includes('reader.readAsDataURL(blob);'), 'SVG previews should use isolated data URLs');
|
||||
assert(browser.includes('URL.createObjectURL(blob)') && browser.includes('URL.revokeObjectURL(state.previewImageUrl)'), 'Raster previews should manage object URL lifetime');
|
||||
assert(browser.includes('state.previewAbortController?.abort()') && browser.includes('requestId !== state.previewRequestId'), 'Switching previews should abort or reject stale image requests');
|
||||
assert(browser.includes('cancelFileBrowserPreview(state)') && browser.includes('clearFileBrowserImagePreview(fileBrowserState)'), 'Closing and switching views should release image resources');
|
||||
@@ -1680,7 +1680,7 @@ function assertFrontendFileBrowserImageContract() {
|
||||
overlay: { parentNode: { removeChild() {} } },
|
||||
escapeHandler() {},
|
||||
};
|
||||
const createLifecycle = new Function('state', 'URL', 'document', `let fileBrowserState = state;\n${lifecycle}\nreturn { cancelFileBrowserPreview, renderFileBrowserPreviewLoading, closeFileBrowser };`);
|
||||
const createLifecycle = new Function('state', 'URL', 'document', `let fileBrowserState = state;\nlet attachmentPreviewModal = null;\n${lifecycle}\nreturn { cancelFileBrowserPreview, renderFileBrowserPreviewLoading, closeFileBrowser };`);
|
||||
const actions = createLifecycle(state, { revokeObjectURL(url) { released.push(url); } }, { removeEventListener() {} });
|
||||
actions.cancelFileBrowserPreview(state);
|
||||
assert(aborted === 1 && state.previewRequestId === 4 && state.previewImageUrl === '', 'Switching previews should abort the old request and invalidate its response');
|
||||
@@ -1693,6 +1693,121 @@ function assertFrontendFileBrowserImageContract() {
|
||||
assert(released[2] === 'blob:third' && state.previewRequestId === 5, 'Closing the browser should release its image and invalidate pending responses');
|
||||
}
|
||||
|
||||
async function assertFrontendFileBrowserImageGallery() {
|
||||
const source = fs.readFileSync(PUBLIC_APP_PATH, 'utf8');
|
||||
const styles = fs.readFileSync(PUBLIC_STYLE_PATH, 'utf8');
|
||||
const functions = [
|
||||
'isFileBrowserImagePath',
|
||||
'getFileBrowserImageGallery',
|
||||
'clearAttachmentPreviewImage',
|
||||
'closeAttachmentPreviewModal',
|
||||
'stepAttachmentPreviewImage',
|
||||
].map((name) => extractFunctionSource(source, name)).join('\n');
|
||||
const loadImageSource = `async ${extractFunctionSource(source, 'loadAttachmentPreviewImage')}`;
|
||||
const requests = [];
|
||||
const revoked = [];
|
||||
let focusRestored = 0;
|
||||
let removed = 0;
|
||||
const makeElement = () => ({
|
||||
textContent: '',
|
||||
hidden: false,
|
||||
disabled: false,
|
||||
src: '',
|
||||
alt: '',
|
||||
classList: { add() {}, remove() {} },
|
||||
removeAttribute(name) { if (name === 'src') this.src = ''; },
|
||||
});
|
||||
const api = new Function('getFileBrowserImagePreview', 'getAttachmentPreviewUrl', 'URL', 'document', `
|
||||
let attachmentPreviewModal = null;
|
||||
const FILE_BROWSER_IMAGE_EXTENSIONS = new Set(['png', 'jpg', 'jpeg', 'gif', 'webp', 'bmp', 'svg', 'avif']);
|
||||
const normalizeBrowserPath = (value) => value;
|
||||
const getPathLeaf = (value) => value.split('/').pop();
|
||||
const formatFileSize = (value) => String(value);
|
||||
${functions}
|
||||
${loadImageSource}
|
||||
return {
|
||||
getFileBrowserImageGallery, loadAttachmentPreviewImage, stepAttachmentPreviewImage,
|
||||
closeAttachmentPreviewModal,
|
||||
activate(state) { attachmentPreviewModal = state; },
|
||||
current() { return attachmentPreviewModal; },
|
||||
};
|
||||
`)(
|
||||
(sessionId, filePath, signal) => new Promise((resolve, reject) => requests.push({ sessionId, filePath, signal, resolve, reject })),
|
||||
async () => 'blob:message-cache',
|
||||
{ revokeObjectURL(url) { revoked.push(url); } },
|
||||
{ removeEventListener() {} }
|
||||
);
|
||||
const gallery = api.getFileBrowserImageGallery({
|
||||
sessionId: 'session-1',
|
||||
directoryEntries: [
|
||||
{ kind: 'directory', path: 'folder.png', imagePreviewableHint: true },
|
||||
{ kind: 'file', path: 'b.png', imagePreviewableHint: true },
|
||||
{ kind: 'file', path: 'text.png', imagePreviewableHint: false },
|
||||
{ kind: 'file', path: 'linked-image.data', name: '链接图片', imagePreviewableHint: true },
|
||||
{ kind: 'file', path: 'notes.txt', imagePreviewableHint: false },
|
||||
{ kind: 'file', path: 'a.SVG' },
|
||||
],
|
||||
});
|
||||
assert(gallery.map((item) => item.filePath).join(',') === 'b.png,linked-image.data,a.SVG', 'Gallery should preserve directory order, include image symlinks, and skip directories/text');
|
||||
assert(gallery[1].filename === '链接图片' && gallery[1].sessionId === 'session-1', 'Gallery items should retain display names and session authentication context');
|
||||
const state = {
|
||||
gallery, index: 0, requestId: 0, imageObjectUrl: '',
|
||||
imgEl: makeElement(), stageEl: makeElement(), placeholderEl: makeElement(),
|
||||
nameEl: makeElement(), descEl: makeElement(),
|
||||
previousBtn: makeElement(), nextBtn: makeElement(),
|
||||
overlay: { remove() { removed += 1; } },
|
||||
returnFocus: { isConnected: true, focus() { focusRestored += 1; } },
|
||||
};
|
||||
api.activate(state);
|
||||
const firstLoad = api.loadAttachmentPreviewImage(state, 0);
|
||||
assert(state.previousBtn.disabled && !state.nextBtn.disabled && state.descEl.textContent.startsWith('1 / 3'), 'First image should disable previous and show its position while loading');
|
||||
const lastLoad = api.loadAttachmentPreviewImage(state, 2);
|
||||
assert(requests[0].signal.aborted && state.nextBtn.disabled && !state.previousBtn.disabled, 'Switching should cancel the old request and disable next at the last image');
|
||||
requests[0].resolve({ url: 'blob:stale', objectUrl: true });
|
||||
await firstLoad;
|
||||
assert(revoked.includes('blob:stale') && state.imgEl.src === '', 'Late raster responses should be released without replacing the current image');
|
||||
requests[1].resolve({ url: 'data:image/svg+xml;base64,PHN2Zy8+', objectUrl: false, blob: { size: 12 }, mimeType: 'image/svg+xml' });
|
||||
await lastLoad;
|
||||
state.imgEl.onload();
|
||||
assert(!state.imgEl.hidden && state.placeholderEl.hidden && state.nameEl.textContent === 'a.SVG' && state.descEl.textContent.startsWith('3 / 3'), 'The selected image should render with its filename and page count');
|
||||
api.stepAttachmentPreviewImage(state, 1);
|
||||
assert(requests.length === 2, 'Next at the last image should not wrap or request another image');
|
||||
api.stepAttachmentPreviewImage(state, -1);
|
||||
requests[2].reject(new Error('图片不存在'));
|
||||
await new Promise((resolve) => setImmediate(resolve));
|
||||
assert(state.placeholderEl.textContent === '图片不存在' && !state.placeholderEl.hidden && state.imgEl.hidden, 'Failed gallery images should retain an error state and allow further navigation');
|
||||
const rasterLoad = api.loadAttachmentPreviewImage(state, 0);
|
||||
requests[3].resolve({ url: 'blob:active', objectUrl: true });
|
||||
await rasterLoad;
|
||||
const pendingLoad = api.loadAttachmentPreviewImage(state, 1);
|
||||
assert(revoked.includes('blob:active'), 'Leaving a raster image should release its Blob URL');
|
||||
api.closeAttachmentPreviewModal();
|
||||
assert(requests[4].signal.aborted && api.current() === null && removed === 1 && focusRestored === 1, 'Closing the gallery should abort loading, remove the overlay and restore focus');
|
||||
requests[4].resolve({ url: 'blob:after-close', objectUrl: true });
|
||||
await pendingLoad;
|
||||
assert(revoked.includes('blob:after-close'), 'A response arriving after close should also be released');
|
||||
const messageState = { ...state, gallery: null, previousBtn: null, nextBtn: null, attachment: { filename: 'message.png', size: 5 }, requestId: 0 };
|
||||
api.activate(messageState);
|
||||
await api.loadAttachmentPreviewImage(messageState, 0);
|
||||
assert(messageState.imgEl.src === 'blob:message-cache', 'Message image previews should keep using the existing attachment cache');
|
||||
api.closeAttachmentPreviewModal();
|
||||
assert(!revoked.includes('blob:message-cache'), 'Closing a message image should preserve the shared attachment cache URL');
|
||||
const singleState = { ...state, gallery: [gallery[0]], requestId: 0 };
|
||||
api.activate(singleState);
|
||||
const singleLoad = api.loadAttachmentPreviewImage(singleState, 0);
|
||||
assert(singleState.previousBtn.disabled && singleState.nextBtn.disabled && singleState.descEl.textContent.startsWith('1 / 1'), 'Single-image folders should disable both navigation controls');
|
||||
api.closeAttachmentPreviewModal();
|
||||
requests[5].resolve({ url: 'data:image/svg+xml;base64,PHN2Zy8+', objectUrl: false });
|
||||
await singleLoad;
|
||||
|
||||
const modalSource = extractFunctionSource(source, 'openAttachmentPreviewModal');
|
||||
const browserSource = extractFunctionSource(source, 'showFileBrowser');
|
||||
assert(modalSource.includes("document.addEventListener('keydown', state.escapeHandler, true)") && modalSource.includes('e.stopImmediatePropagation()'), 'The topmost preview should consume Escape before the file browser handler');
|
||||
assert(modalSource.includes("e.key === 'ArrowLeft'") && modalSource.includes("e.key === 'ArrowRight'"), 'Gallery should support both arrow keys');
|
||||
assert(browserSource.includes('<button class="file-browser-preview-image"') && browserSource.includes("state.previewImageEl.addEventListener('click', openFileBrowserImageGallery)"), 'The file image preview should be a keyboard-accessible button');
|
||||
assert(styles.includes('.attachment-preview-overlay.is-fullscreen') && styles.includes('height: 100dvh;') && styles.includes('touch-action: manipulation;'), 'The gallery should fill the viewport and retain touch-friendly navigation');
|
||||
}
|
||||
|
||||
function assertMarkdownRenderingContract() {
|
||||
const source = fs.readFileSync(PUBLIC_APP_PATH, 'utf8');
|
||||
const indexSource = fs.readFileSync(PUBLIC_INDEX_PATH, 'utf8');
|
||||
@@ -2851,7 +2966,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=20260913-history-control-position'), 'Goal label CSS should use the current cache-busted URL');
|
||||
assert(indexSource.includes('style.css?v=20261017-file-browser-image-gallery'), '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');
|
||||
@@ -6703,7 +6818,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=20260913-history-control-position')
|
||||
assert(indexSource.includes('style.css?v=20261017-file-browser-image-gallery')
|
||||
&& indexSource.includes('app.js?v=__CC_WEB_FRONTEND_ASSET_VERSION__'),
|
||||
'Advanced search CSS and frontend script should share the reviewed cache-bust');
|
||||
|
||||
@@ -6820,7 +6935,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=20260913-history-control-position')
|
||||
assert(indexSource.includes('style.css?v=20261017-file-browser-image-gallery')
|
||||
&& 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');
|
||||
@@ -7278,7 +7393,7 @@ function makeLargeSessionPreviewFixture(sessionId, tailFields = {}) {
|
||||
],
|
||||
},
|
||||
],
|
||||
padding: 'x'.repeat(70 * 1024),
|
||||
padding: 'x'.repeat(256 * 1024),
|
||||
id: sessionId,
|
||||
...tailFields,
|
||||
});
|
||||
@@ -7292,7 +7407,7 @@ function makeHeadTailJoinRiskFixture(sessionId, previewBytes) {
|
||||
assert(paddingBytes > 0, 'Boundary fixture should fit the preview head');
|
||||
const head = `${beforePadding}${'x'.repeat(paddingBytes)}${beforeCwd}`;
|
||||
assert(Buffer.byteLength(head) === previewBytes, 'Boundary fixture should end the head preview after cwd colon');
|
||||
return `${head}${' '.repeat(70 * 1024)}"/tmp/joined-preview-cwd","messages":[]}`;
|
||||
return `${head}${' '.repeat(previewBytes + 70 * 1024)}"/tmp/joined-preview-cwd","messages":[]}`;
|
||||
}
|
||||
|
||||
async function runSessionPreviewMetadataRegression() {
|
||||
@@ -7307,7 +7422,7 @@ async function runSessionPreviewMetadataRegression() {
|
||||
mkdirp(homeDir);
|
||||
|
||||
const fullParseBytes = 64 * 1024;
|
||||
const previewBytes = 16 * 1024;
|
||||
const previewBytes = 128 * 1024;
|
||||
const sourceSessionId = 'preview-parent-source';
|
||||
const tailCwd = path.join(homeDir, '上下游协同平台');
|
||||
const tailSessionId = 'preview-tail-top-level';
|
||||
@@ -7356,6 +7471,17 @@ async function runSessionPreviewMetadataRegression() {
|
||||
boundarySessionPath,
|
||||
makeHeadTailJoinRiskFixture(boundarySessionId, previewBytes)
|
||||
);
|
||||
// 尾部高密度嵌套伪字段曾触发重复JSON解析,阻塞整个服务的HTTP读取。
|
||||
const denseIds = Array.from({ length: 8 }, (_, index) => `preview-dense-${index}`);
|
||||
const denseMessages = Array.from({ length: 6500 }, () => ({
|
||||
id: 'tool-id', title: '工具标题', cwd: '/错误目录', agent: 'claude', input: '转义\\"及括号}]',
|
||||
}));
|
||||
for (const id of denseIds) fs.writeFileSync(path.join(sessionsDir, `${id}.json`), JSON.stringify({
|
||||
padding: 'x'.repeat(256 * 1024), messages: denseMessages, id, title: '真实顶层标题', agent: 'codexapp', cwd: tailCwd,
|
||||
}));
|
||||
fs.writeFileSync(path.join(sessionsDir, 'preview-http-target.json'), JSON.stringify({
|
||||
id: 'preview-http-target', title: 'HTTP读取目标', agent: 'codexapp', messages: [{ id: 'target-message', role: 'user', content: '真实正文' }],
|
||||
}));
|
||||
assert(fs.statSync(tailSessionPath).size > fullParseBytes, 'Tail fixture must use oversized preview parsing');
|
||||
assert(fs.statSync(childSessionPath).size > fullParseBytes, 'MCP child fixture must use oversized preview parsing');
|
||||
assert(fs.statSync(nullSessionPath).size > fullParseBytes, 'Null fixture must use oversized preview parsing');
|
||||
@@ -7376,7 +7502,7 @@ async function runSessionPreviewMetadataRegression() {
|
||||
CLAUDE_PATH: MOCK_CLAUDE,
|
||||
CODEX_PATH: MOCK_CODEX_APP_SERVER,
|
||||
}, async () => {
|
||||
const { ws, messages } = await connectWs(port, password);
|
||||
const { ws, messages, token } = await connectWs(port, password);
|
||||
const list = await nextMessage(messages, ws, (msg) => (
|
||||
msg.type === 'session_list' &&
|
||||
[tailSessionId, childSessionId, nullSessionId, boundarySessionId].every((id) => (
|
||||
@@ -7402,6 +7528,25 @@ async function runSessionPreviewMetadataRegression() {
|
||||
|
||||
assert(boundarySession.cwd === '', `Preview parser should not join head cwd key with tail value, got ${JSON.stringify(boundarySession.cwd)}`);
|
||||
assert(boundarySession.projectName === '', `Boundary preview should not derive a projectName from a joined value, got ${JSON.stringify(boundarySession.projectName)}`);
|
||||
for (const id of denseIds) {
|
||||
assert(sessionById.get(id)?.title === '真实顶层标题', '高密度工具伪标题不能污染会话标题');
|
||||
assert(sessionById.get(id)?.cwd === tailCwd, '高密度工具伪目录不能污染会话目录');
|
||||
}
|
||||
const started = Date.now();
|
||||
const oldListCount = messages.filter(message => message.type === 'session_list').length;
|
||||
ws.send(JSON.stringify({ type: 'list_sessions' }));
|
||||
const [loaded] = await Promise.all([
|
||||
(async () => {
|
||||
const response = await fetch(`http://127.0.0.1:${port}/api/sessions/preview-http-target?requestId=metadata-concurrent-http`, {
|
||||
headers: { Authorization: `Bearer ${token}` }, signal: AbortSignal.timeout(3000),
|
||||
});
|
||||
assert(response.ok, '刷新列表时HTTP读取必须成功');
|
||||
return response.json();
|
||||
})(),
|
||||
nextMessage(messages, ws, message => message.type === 'session_list' && messages.filter(item => item.type === 'session_list').length > oldListCount, 3000),
|
||||
]);
|
||||
assert(loaded.type === 'session_info' && loaded.messages[0]?.content === '真实正文', '刷新列表时HTTP切换仍须返回完整首屏');
|
||||
assert(Date.now() - started < 3000, '高密度列表解析不得让并发HTTP切换排队数秒');
|
||||
|
||||
const childList = await callInternalMcp(port, 'SessionPreviewMcp!234', {
|
||||
tool: 'ccweb_list_conversations',
|
||||
@@ -7583,6 +7728,7 @@ function assertCcwebDisplayImageContract() {
|
||||
const inline = prepareImagePayload({ source: 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw==' });
|
||||
assert(inline.ok && inline.image.mimeType === 'image/gif', 'Base64 GIF images should be accepted');
|
||||
assert(prepareImagePayload({ source: 'relative.png' }).code === 'invalid_local_path', 'Local images should require absolute paths');
|
||||
require('./ccweb-display-image-unit').run();
|
||||
}
|
||||
|
||||
function assertCcwebListConversationsScopeContract() {
|
||||
@@ -7795,6 +7941,7 @@ async function main() {
|
||||
}
|
||||
if (regressionTarget === 'file-browser-image') {
|
||||
assertFrontendFileBrowserImageContract();
|
||||
await assertFrontendFileBrowserImageGallery();
|
||||
await runFileBrowserImageRegression();
|
||||
console.log('File browser image regression checks passed.');
|
||||
return;
|
||||
@@ -7856,6 +8003,7 @@ async function main() {
|
||||
assertFrontendCcwebPromptContract();
|
||||
assertFrontendMarkdownLinkContract();
|
||||
assertFrontendFileBrowserImageContract();
|
||||
await assertFrontendFileBrowserImageGallery();
|
||||
assertMarkdownRenderingContract();
|
||||
assertMockCodexAppPromptUserNotTextTriggered();
|
||||
assertFrontendMcpReloadContract();
|
||||
|
||||
Reference in New Issue
Block a user