From 84a3cd73bbe0d2f61d7c4077f658c7d32b91dd65 Mon Sep 17 00:00:00 2001 From: Miayui <774713819@qq.com> Date: Mon, 14 Sep 2026 18:48:11 +0800 Subject: [PATCH] feat(web-console): unify spatial UI and chat reset --- apps/web-console/app.js | 7 +- apps/web-console/assistant-chat.css | 27 +++++- apps/web-console/assistant-chat.js | 83 +++++++++++++++---- apps/web-console/index.html | 12 +-- apps/web-console/self-extension.css | 17 +++- apps/web-console/self-extension.js | 16 ++-- apps/web-console/service-worker.js | 14 ++-- apps/web-console/ui-consistency.css | 81 +++++++++++++++++- tests/e2e/test_seat_web_api.py | 34 ++++---- .../unit/test_assistant_frontend_contract.py | 62 ++++++++++++-- .../test_energy_monitor_frontend_contract.py | 4 +- tests/unit/test_frontend_theme_contract.py | 4 +- tests/unit/test_nfc_frontend_contract.py | 4 +- .../test_requested_frontend_enhancements.py | 3 +- .../test_spatial_ui_consistency_contract.py | 45 +++++++++- tests/unit/test_vue_frontend_contract.py | 6 +- 16 files changed, 333 insertions(+), 86 deletions(-) diff --git a/apps/web-console/app.js b/apps/web-console/app.js index bebc071..7bfa0a2 100755 --- a/apps/web-console/app.js +++ b/apps/web-console/app.js @@ -8854,10 +8854,9 @@ function downloadArtifact(type) { URL.revokeObjectURL(link.href); } -function startNewConversation() { - state.conversationId = createConversationId(); +function startNewConversation(event) { + state.conversationId = String(event?.detail?.conversationId || createConversationId()); sessionStorage.setItem("cognitive-agent.conversation-id", state.conversationId); - window.location.reload(); } window.CognitiveAgentConsole = { @@ -9024,7 +9023,7 @@ document.addEventListener("DOMContentLoaded", async () => { $("#requestForm").requestSubmit(); } }); - $("#newConversation").addEventListener("click", startNewConversation); + window.addEventListener("cognitive-agent:new-conversation", startNewConversation); $("#vehicleSelect").addEventListener("change", (event) => { selectVehicle(event.target.value); }); diff --git a/apps/web-console/assistant-chat.css b/apps/web-console/assistant-chat.css index f44bedd..e5db16e 100644 --- a/apps/web-console/assistant-chat.css +++ b/apps/web-console/assistant-chat.css @@ -1512,21 +1512,42 @@ width: 100%; max-width: none; align-self: stretch; + display: grid; + grid-template-columns: 30px minmax(0, 1fr); + column-gap: 9px; + row-gap: 7px; +} + +.assistant-message-with-dispatched-controls > .assistant-avatar { + grid-column: 1; + grid-row: 1; } .assistant-message-with-dispatched-controls .assistant-message-body { min-width: 0; - flex: 1 1 0; + display: contents; +} + +.assistant-message-with-dispatched-controls .assistant-message-body > :not( + .assistant-dispatched-controls-vue-host, + .assistant-dispatched-controls +) { + grid-column: 2; } .assistant-dispatched-controls-vue-host { width: 100%; - max-width: 336px; + max-width: none; +} + +.assistant-message-with-dispatched-controls .assistant-dispatched-controls-vue-host, +.assistant-message-with-dispatched-controls .assistant-message-body > .assistant-dispatched-controls { + grid-column: 1 / -1; } .assistant-dispatched-controls { width: 100%; - max-width: 336px; + max-width: none; box-sizing: border-box; overflow: hidden; color: var(--assistant-text, #233044); diff --git a/apps/web-console/assistant-chat.js b/apps/web-console/assistant-chat.js index a5a9690..11acb37 100644 --- a/apps/web-console/assistant-chat.js +++ b/apps/web-console/assistant-chat.js @@ -72,7 +72,14 @@ trigger.setAttribute("aria-label", "展开AI助手"); trigger.title = "展开AI助手"; trigger.innerHTML = 'AI助手'; - ($("#v2PanelControlDock") || document.body).append(trigger); + const panelDock = $("#v2PanelControlDock"); + if (panelDock) { + const firstRightPanelToggle = $("[data-panel-toggle]", panelDock); + if (firstRightPanelToggle) panelDock.insertBefore(trigger, firstRightPanelToggle); + else panelDock.append(trigger); + } else { + document.body.append(trigger); + } trigger.addEventListener("click", () => { setDrawerOpen(!document.body.classList.contains("assistant-drawer-open")); @@ -1405,17 +1412,22 @@ const assistantMessage = createMessage("assistant", "", { pending: true }); pendingMessage = assistantMessage; latestControlResult = null; - pendingController = new AbortController(); + const requestController = new AbortController(); + pendingController = requestController; setComposerPending(true); let completedError = null; + let resetForNewConversation = false; try { - await streamAssistant(messageText, assistantMessage, pendingController.signal); + await streamAssistant(messageText, assistantMessage, requestController.signal); setMessageContent(assistantMessage, assistantMessage.content, { final: true }); persistMessages(); } catch (error) { completedError = error; - if (pendingController.signal.aborted) { + resetForNewConversation = requestController.signal.aborted + && requestController.signal.reason === "new_conversation"; + if (resetForNewConversation) return; + if (requestController.signal.aborted) { const stopped = assistantMessage.content ? `${assistantMessage.content}\n\n已停止继续生成。已下发的车辆操作仍会由安全执行链路完成。` : "已停止生成。已下发的车辆操作仍会由安全执行链路完成。"; @@ -1429,17 +1441,21 @@ } persistMessages(); } finally { - pendingController = null; - pendingMessage = null; - setComposerPending(false); - input.focus({ preventScroll: true }); - window.dispatchEvent(new CustomEvent("cognitive-agent:request-complete", { - detail: { - utterance: messageText, - result: latestControlResult, - error: completedError, - }, - })); + if (pendingController === requestController) { + pendingController = null; + pendingMessage = null; + setComposerPending(false); + } + if (!resetForNewConversation) { + input.focus({ preventScroll: true }); + window.dispatchEvent(new CustomEvent("cognitive-agent:request-complete", { + detail: { + utterance: messageText, + result: latestControlResult, + error: completedError, + }, + })); + } } } @@ -1490,6 +1506,37 @@ stored.forEach((message) => createMessage(message.role, String(message.content || ""))); } + function resetConversation() { + const activeController = pendingController; + pendingController = null; + pendingMessage = null; + latestControlResult = null; + if (activeController) activeController.abort("new_conversation"); + + sessionStorage.removeItem(STORAGE_KEY); + clearActiveModeDraftContext(); + const nextConversationId = createConversationId(); + sessionStorage.setItem(CONVERSATION_STORAGE_KEY, nextConversationId); + + const input = $("#utterance"); + if (input) { + input.value = ""; + delete input.dataset.speechStage; + delete input.dataset.speechUtteranceId; + delete input.dataset.speechValue; + input.dispatchEvent(new Event("input", { bubbles: true })); + resizeComposerInput(input); + } + renderInitialMessages(); + setComposerPending(false); + setDrawerOpen(true); + requestAnimationFrame(() => input?.focus({ preventScroll: true })); + window.dispatchEvent(new CustomEvent("cognitive-agent:new-conversation", { + detail: { conversationId: nextConversationId }, + })); + return nextConversationId; + } + function initialize() { if (initialized || !$("#requestForm") || !$("#assistantMessages")) return; initialized = true; @@ -1526,9 +1573,8 @@ document.addEventListener("click", (event) => { const target = event.target instanceof Element ? event.target : null; if (!target?.closest("#newConversation")) return; - sessionStorage.removeItem(STORAGE_KEY); - clearActiveModeDraftContext(); - sessionStorage.setItem(CONVERSATION_STORAGE_KEY, createConversationId()); + event.preventDefault?.(); + resetConversation(); }, true); document.addEventListener("DOMContentLoaded", initialize); @@ -1536,6 +1582,7 @@ submit: submitAssistant, open: () => setDrawerOpen(true), close: () => setDrawerOpen(false), + resetConversation, startModeCreation, announceModeExecution, announceModeCreated(profile) { diff --git a/apps/web-console/index.html b/apps/web-console/index.html index 0a8ca75..2103283 100755 --- a/apps/web-console/index.html +++ b/apps/web-console/index.html @@ -35,11 +35,11 @@ - + - + - + @@ -48,13 +48,13 @@ - + - - + + diff --git a/apps/web-console/self-extension.css b/apps/web-console/self-extension.css index fb50f82..e304e19 100644 --- a/apps/web-console/self-extension.css +++ b/apps/web-console/self-extension.css @@ -2351,10 +2351,11 @@ form > .v2-extension-primary { padding: 15px 20px; } -.v2-gscu-config-form > label, -.v2-gscu-actions label { +.v2-extension-dialog .v2-gscu-config-form > label, +.v2-extension-dialog .v2-gscu-actions label { display: grid; gap: 5px; + margin: 0; color: #4c6263; font-size: 9px; font-weight: 800; @@ -2370,7 +2371,9 @@ form > .v2-extension-primary { .v2-gscu-config-form > .v2-extension-primary { min-width: 135px; - margin: 0 0 22px; + height: 40px; + min-height: 40px; + margin: 0; } .v2-gscu-actions { @@ -4104,6 +4107,14 @@ form > .v2-extension-primary { cursor: pointer; } +.v2-extension-dialog .v2-pet-card-region > header > button { + width: auto; + min-width: 104px; + height: 34px; + flex: 0 0 auto; + padding-inline: 13px; +} + .v2-pet-card-region button:disabled, .v2-nfc-pet-binding button:disabled { opacity: 0.52; diff --git a/apps/web-console/self-extension.js b/apps/web-console/self-extension.js index c984496..62bccb2 100644 --- a/apps/web-console/self-extension.js +++ b/apps/web-console/self-extension.js @@ -732,12 +732,12 @@

红外学习器 TCP 控制

正在读取连接状态--
CHECK
- - + +
-
TCP 动作调试设备 ID 唯一标识控制对象;对象与动作可填写多个自然语言别名
+
TCP 动作调试
@@ -757,12 +757,12 @@
-
已学习红外信号按设备 ID 分组;对象名称用于自然语言识别和调试,动作请包含结果值,如“切换为蓝色”
+
已学习红外信号
JSON 调试结果
等待操作
-

宠物设置

添加或编辑宠物资料;保存后可在 NFC 设备授权中绑定卡片。

+

宠物设置

添加或编辑宠物资料;保存后可在 NFC 设备授权中绑定卡片。

@@ -4085,12 +4085,12 @@
-
${escapeHtml(nfcTitle)}

${escapeHtml(nfcNote)}

${nfcCapabilities.length ? `
${nfcCapabilities.slice(0, 6).map((capability) => `${escapeHtml(capability.name)}`).join("")}
` : ""}
+
${escapeHtml(nfcTitle)}${nfcPet ? "" : `

${escapeHtml(nfcNote)}

`}${nfcCapabilities.length ? `
${nfcCapabilities.slice(0, 6).map((capability) => `${escapeHtml(capability.name)}`).join("")}
` : ""}
-
${gatewayLabel}${escapeHtml(gatewayNote)}
+
${gatewayLabel}
${String(health.transport || "GSCU").toUpperCase()}
@@ -4098,7 +4098,7 @@ ${nfcPet ? renderDetectedPetCard(nfcPet) : ""} ${devices.length ? devices.map((device) => `
-
${escapeHtml(device.name)}${escapeHtml(device.type)}${device.cardUid ? " · 已绑定 NFC" : ""}
${device.actions.length} 项能力
+
${escapeHtml(device.name)}
${device.actions.length} 项能力
${device.actions.slice(0, 8).map((action) => `${escapeHtml(action)}`).join("")}
`).join("") : nfcPet ? "" : `
diff --git a/apps/web-console/service-worker.js b/apps/web-console/service-worker.js index 6844702..d5ec45f 100644 --- a/apps/web-console/service-worker.js +++ b/apps/web-console/service-worker.js @@ -1,4 +1,4 @@ -const CACHE_VERSION = "cognitive-agent-pad-v302"; +const CACHE_VERSION = "cognitive-agent-pad-v305"; const SHELL_CACHE = `${CACHE_VERSION}-shell`; const RUNTIME_CACHE = `${CACHE_VERSION}-runtime`; const APP_SHELL = [ @@ -6,21 +6,21 @@ const APP_SHELL = [ "/index.html", "/styles.css?v=dual-seat-23", "/network.js?v=android-pad-2", - "/app.js?v=dual-seat-139", + "/app.js?v=dual-seat-140", "/gesture-control.js?v=android-pad-9", "/pwa.js?v=android-pad-3", "/console-v2.css?v=delivery-state-69", "/console-v2.js?v=local-first-70", - "/self-extension.css?v=pet-settings-49", - "/self-extension.js?v=pet-settings-49", + "/self-extension.css?v=pet-settings-52", + "/self-extension.js?v=pet-settings-52", "/speech-input.css?v=sensevoice-7", "/speech-input.js?v=sensevoice-13", "/speech-audio-worklet.js?v=sensevoice-5", - "/assistant-chat.css?v=codex-assistant-28", - "/assistant-chat.js?v=codex-assistant-32", + "/assistant-chat.css?v=codex-assistant-29", + "/assistant-chat.js?v=codex-assistant-33", "/energy-monitor.css?v=energy-monitor-4", "/energy-monitor.js?v=energy-monitor-4", - "/ui-consistency.css?v=spatial-ui-4", + "/ui-consistency.css?v=spatial-ui-5", "/codepen-glass-theme.css?v=creative-cloud-2", "/vue-ui.js?v=vue-presentation-3", "/assets/pets/self-extension-cat.png?v=3", diff --git a/apps/web-console/ui-consistency.css b/apps/web-console/ui-consistency.css index 7e9a0ad..06e381f 100644 --- a/apps/web-console/ui-consistency.css +++ b/apps/web-console/ui-consistency.css @@ -2,12 +2,16 @@ Keep geometry consistent across legacy modules without changing their domain-specific colors, content density, or interaction behavior. - Hierarchy: control 10px -> item 12px -> section 16px -> panel 18px -> - modal 22px. All modules consume the same semantic color tokens. */ + Hierarchy: control 10px -> card 13px -> section 15px -> panel 16px -> + modal 20px. All modules consume the same semantic color tokens. */ :root { --ui-control-height-compact: 30px; --ui-control-height: 38px; --ui-control-size-icon: 36px; + --ui-font-card-title: 13px; + --ui-font-section-title: 15px; + --ui-font-panel-title: 16px; + --ui-inline-action-width: 88px; --ui-radius-control-inner: calc(var(--radius) - 2px); --ui-radius-control: var(--radius); --ui-radius-item: calc(var(--radius) + 2px); @@ -27,6 +31,43 @@ --ui-focus-ring: 0 0 0 3px color-mix(in oklch, var(--ring) 22%, transparent); } +/* One readable type scale prevents large legacy headings and tiny newer card + titles from competing inside the same workspace. */ +.console-v2 :is( + .v2-operation-heading h2, + .joint-twin-shell .twin-heading h3, + .v2-device-sync-board > header h3, + .v2-extension-sync-heading h3, + .v2-sync-sidebar-heading h2, + .workspace-panel-title h3, + .config-panel-heading h3 +) { + font-size: var(--ui-font-panel-title) !important; + line-height: 1.25; + letter-spacing: -0.02em; +} + +.console-v2 :is( + .v2-pet-card-region > header h4, + .v2-gscu-actions > header strong, + .v2-gscu-command-catalog > header strong +) { + font-size: var(--ui-font-section-title) !important; + line-height: 1.3; +} + +.console-v2 :is( + .v2-device-card-focus strong, + .v2-nfc-inline strong, + .v2-extension-gateway strong, + .v2-extension-synced-device header strong, + .v2-pet-presence-card > header strong, + .v2-pet-card > header > strong +) { + font-size: var(--ui-font-card-title) !important; + line-height: 1.35; +} + /* Top-level workspace surfaces share one frame. */ .console-v2.v2-passive-display.v2-interactive-sync :is( .v2-operation-shell, @@ -174,6 +215,7 @@ /* Repeated inline actions use one compact size. */ .console-v2 :is( .v2-device-adjust-toggle, + .v2-pet-settings-launch, .v2-extension-nfc-launch, .v2-extension-gateway > button, .v2-nfc-inline button @@ -184,6 +226,36 @@ border-radius: var(--ui-radius-control-inner); } +/* Related extension actions use equal columns and a shared trailing action + width, so every card keeps the same horizontal rhythm. */ +.console-v2 .v2-extension-sync-actions { + width: calc(var(--ui-inline-action-width) * 2 + 6px); + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 6px; +} + +.console-v2 .v2-extension-sync-actions > :is( + .v2-pet-settings-launch, + .v2-extension-nfc-launch +) { + width: 100%; + min-width: 0; + height: var(--ui-control-height-compact); + justify-content: center; + padding-inline: 7px; + font-size: 10px; +} + +.console-v2 :is( + .v2-nfc-inline > button, + .v2-extension-gateway > button +) { + width: var(--ui-inline-action-width); + min-width: var(--ui-inline-action-width); + justify-content: center; +} + /* Header icon actions are distinct from circular record/send actions. */ .assistant-drawer-actions .context-reset, .assistant-drawer-close, @@ -246,6 +318,7 @@ .segmented > button, .twin-selector > button, .v2-device-adjust-toggle, + .v2-pet-settings-launch, .v2-extension-nfc-launch, .v2-extension-gateway > button, .v2-nfc-inline button @@ -263,6 +336,10 @@ --ui-control-height-compact: 32px; --ui-control-size-icon: 38px; } + + .console-v2 .v2-extension-sync-actions { + width: 100%; + } } @media (prefers-reduced-motion: reduce) { diff --git a/tests/e2e/test_seat_web_api.py b/tests/e2e/test_seat_web_api.py index d6285cd..182f892 100755 --- a/tests/e2e/test_seat_web_api.py +++ b/tests/e2e/test_seat_web_api.py @@ -807,7 +807,7 @@ operations: def test_static_console_and_runtime_endpoint(self) -> None: with self.opener.open(f"{self.base_url}/", timeout=3) as response: html = response.read().decode("utf-8") - with self.opener.open(f"{self.base_url}/app.js?v=dual-seat-139", timeout=3) as response: + with self.opener.open(f"{self.base_url}/app.js?v=dual-seat-140", timeout=3) as response: app_script = response.read().decode("utf-8") with self.opener.open( f"{self.base_url}/gesture-control.js?v=android-pad-9", timeout=3 @@ -834,15 +834,15 @@ operations: ) as response: speech_worklet = response.read().decode("utf-8") with self.opener.open( - f"{self.base_url}/self-extension.js?v=pet-settings-49", timeout=3 + f"{self.base_url}/self-extension.js?v=pet-settings-52", timeout=3 ) as response: extension_script = response.read().decode("utf-8") with self.opener.open( - f"{self.base_url}/self-extension.css?v=pet-settings-49", timeout=3 + f"{self.base_url}/self-extension.css?v=pet-settings-52", timeout=3 ) as response: extension_styles = response.read().decode("utf-8").replace("\r\n", "\n") with self.opener.open( - f"{self.base_url}/assistant-chat.js?v=codex-assistant-32", timeout=3 + f"{self.base_url}/assistant-chat.js?v=codex-assistant-33", timeout=3 ) as response: assistant_script = response.read().decode("utf-8") with self.opener.open( @@ -887,7 +887,7 @@ operations: self.assertIn("CMD 0x431 · FB 0x41A · CH1", html) self.assertIn("CMD 0x431 · FB 0x388 · CH1", html) self.assertNotIn("透明车体 · 实体设备", html) - self.assertIn('src="/app.js?v=dual-seat-139"', html) + self.assertIn('src="/app.js?v=dual-seat-140"', html) self.assertIn('href="/styles.css?v=dual-seat-23"', html) self.assertIn('src="/gesture-control.js?v=android-pad-9"', html) self.assertNotIn('class="vehicle-system-hud"', html) @@ -900,16 +900,16 @@ operations: self.assertIn('', html) self.assertIn('id="v2BootScreen"', html) self.assertIn('正在加载车机控制与实时状态', html) - self.assertIn('src="/self-extension.js?v=pet-settings-49"', html) - self.assertIn('href="/self-extension.css?v=pet-settings-49"', html) + self.assertIn('src="/self-extension.js?v=pet-settings-52"', html) + self.assertIn('href="/self-extension.css?v=pet-settings-52"', html) self.assertNotIn("self-calibration", html) self.assertNotIn("self-repair", html) self.assertNotIn("self-evolution", html) self.assertNotIn("滚动查看更多", html) self.assertIn('aria-label="发送消息"', html) self.assertIn('id="assistantTranscript"', html) - self.assertIn('href="/assistant-chat.css?v=codex-assistant-28"', html) - self.assertIn('src="/assistant-chat.js?v=codex-assistant-32"', html) + self.assertIn('href="/assistant-chat.css?v=codex-assistant-29"', html) + self.assertIn('src="/assistant-chat.js?v=codex-assistant-33"', html) self.assertIn('href="/energy-monitor.css?v=energy-monitor-4"', html) self.assertIn('src="/energy-monitor.js?v=energy-monitor-4"', html) self.assertIn('href="/codepen-glass-theme.css?v=creative-cloud-2"', html) @@ -929,7 +929,11 @@ operations: self.assertIn('setSidePanelVisibility("devices", false)', console_script) self.assertIn('setSidePanelVisibility("extensions", false)', console_script) self.assertIn('setDrawerOpen(true)', assistant_script) - self.assertIn('($("#v2PanelControlDock") || document.body).append(trigger)', assistant_script) + self.assertIn('const panelDock = $("#v2PanelControlDock")', assistant_script) + self.assertIn( + "panelDock.insertBefore(trigger, firstRightPanelToggle)", + assistant_script, + ) self.assertIn('trigger.id = "energyMonitorToggle"', energy_script) self.assertIn('document.body.append(layer)', energy_script) self.assertIn('.energy-monitor-layer', energy_styles) @@ -1194,14 +1198,14 @@ operations: self.assertIn("CAN 指令已提交,等待车辆状态确认", console_script) self.assertIn('canvas.addEventListener("webglcontextlost"', app_script) self.assertIn('"cognitive-agent:module-change"', app_script) - self.assertIn('const CACHE_VERSION = "cognitive-agent-pad-v302"', service_worker) + self.assertIn('const CACHE_VERSION = "cognitive-agent-pad-v305"', service_worker) self.assertIn('/console-v2.css?v=delivery-state-69', service_worker) self.assertIn('/console-v2.js?v=local-first-70', service_worker) - self.assertIn('/assistant-chat.css?v=codex-assistant-28', service_worker) - self.assertIn('/assistant-chat.js?v=codex-assistant-32', service_worker) + self.assertIn('/assistant-chat.css?v=codex-assistant-29', service_worker) + self.assertIn('/assistant-chat.js?v=codex-assistant-33', service_worker) self.assertIn('/energy-monitor.css?v=energy-monitor-4', service_worker) self.assertIn('/energy-monitor.js?v=energy-monitor-4', service_worker) - self.assertIn('"/app.js?v=dual-seat-139"', service_worker) + self.assertIn('"/app.js?v=dual-seat-140"', service_worker) self.assertIn('"/speech-input.js?v=sensevoice-13"', service_worker) self.assertIn('"/speech-input.css?v=sensevoice-7"', service_worker) self.assertIn('"/speech-audio-worklet.js?v=sensevoice-5"', service_worker) @@ -1658,7 +1662,7 @@ operations: ) as response: extension_script = response.read().decode("utf-8") with self.opener.open( - f"{self.base_url}/app.js?v=dual-seat-139", timeout=3 + f"{self.base_url}/app.js?v=dual-seat-140", timeout=3 ) as response: app_script = response.read().decode("utf-8") diff --git a/tests/unit/test_assistant_frontend_contract.py b/tests/unit/test_assistant_frontend_contract.py index 1d69d63..d7204fc 100644 --- a/tests/unit/test_assistant_frontend_contract.py +++ b/tests/unit/test_assistant_frontend_contract.py @@ -13,15 +13,15 @@ def test_console_contains_ai_assistant_surface(): assert 'aria-label="AI助手对话"' in html assert 'placeholder="向 AI助手提问或交代工作"' in html assert 'aria-label="发送消息"' in html - assert 'href="/assistant-chat.css?v=codex-assistant-28"' in html - assert 'src="/assistant-chat.js?v=codex-assistant-32"' in html + assert 'href="/assistant-chat.css?v=codex-assistant-29"' in html + assert 'src="/assistant-chat.js?v=codex-assistant-33"' in html def test_codex_assistant_assets_are_versioned_together_in_the_service_worker(): service_worker = (ROOT / "apps/web-console/service-worker.js").read_text(encoding="utf-8") - assert 'const CACHE_VERSION = "cognitive-agent-pad-v302"' in service_worker - assert '"/assistant-chat.css?v=codex-assistant-28"' in service_worker - assert '"/assistant-chat.js?v=codex-assistant-32"' in service_worker + assert 'const CACHE_VERSION = "cognitive-agent-pad-v305"' in service_worker + assert '"/assistant-chat.css?v=codex-assistant-29"' in service_worker + assert '"/assistant-chat.js?v=codex-assistant-33"' in service_worker def test_codex_frontend_uses_one_streaming_endpoint_and_agent_work_events(): @@ -121,7 +121,11 @@ def test_starting_a_new_codex_conversation_rotates_the_conversation_id(): script = (ROOT / "apps/web-console/assistant-chat.js").read_text(encoding="utf-8") assert 'const CONVERSATION_STORAGE_KEY = "cognitive-agent.conversation-id"' in script assert "function createConversationId()" in script - assert "sessionStorage.setItem(CONVERSATION_STORAGE_KEY, createConversationId())" in script + assert "const nextConversationId = createConversationId()" in script + assert ( + "sessionStorage.setItem(CONVERSATION_STORAGE_KEY, nextConversationId)" + in script + ) def test_assistant_uses_the_shared_top_right_dock_and_opens_by_default(): @@ -137,7 +141,9 @@ def test_assistant_uses_the_shared_top_right_dock_and_opens_by_default(): assert 'trigger.setAttribute("aria-pressed", String(open))' in script assert 'drawer.toggleAttribute("inert", !open)' in script assert 'trigger.className = "assistant-drawer-toggle v2-panel-control-button"' in script - assert '($("#v2PanelControlDock") || document.body).append(trigger)' in script + assert 'const panelDock = $("#v2PanelControlDock")' in script + assert "panelDock.insertBefore(trigger, firstRightPanelToggle)" in script + assert "document.body.append(trigger)" in script assert 'id = "assistantDrawerClose"' not in script assert "setDrawerOpen(true)" in script assert 'event.key === "Escape"' in script @@ -252,8 +258,8 @@ def test_latest_same_name_mode_draft_retires_stale_card_and_lifecycle_clears_con "async function tryModeProfileDraft", 1 )[0] new_conversation_flow = assistant.split( - 'if (!target?.closest("#newConversation")) return;', 1 - )[1].split("}, true);", 1)[0] + "function resetConversation()", 1 + )[1].split("function initialize()", 1)[0] assert "card.dataset.assistantModeName !== nextName" in replace_flow assert "card.dataset.assistantModeDraft === nextRequestId" in replace_flow @@ -266,6 +272,32 @@ def test_latest_same_name_mode_draft_retires_stale_card_and_lifecycle_clears_con assert ".assistant-mode-draft-status.superseded" in css +def test_new_conversation_resets_chat_in_place_and_keeps_panel_order_spatial(): + app = (ROOT / "apps/web-console/app.js").read_text(encoding="utf-8") + assistant = (ROOT / "apps/web-console/assistant-chat.js").read_text( + encoding="utf-8" + ) + reset_flow = assistant.split("function resetConversation()", 1)[1].split( + "function initialize()", 1 + )[0] + app_flow = app.split("function startNewConversation", 1)[1].split( + "window.CognitiveAgentConsole", 1 + )[0] + + assert 'activeController.abort("new_conversation")' in reset_flow + assert "sessionStorage.removeItem(STORAGE_KEY)" in reset_flow + assert "renderInitialMessages()" in reset_flow + assert "setComposerPending(false)" in reset_flow + assert '"cognitive-agent:new-conversation"' in reset_flow + assert "window.location.reload()" not in app_flow + assert ( + 'window.addEventListener("cognitive-agent:new-conversation", ' + "startNewConversation)" + ) in app + assert 'const firstRightPanelToggle = $("[data-panel-toggle]", panelDock)' in assistant + assert "panelDock.insertBefore(trigger, firstRightPanelToggle)" in assistant + + def test_assistant_composer_grows_to_a_cap_then_scrolls_its_own_content(): assistant = (ROOT / "apps/web-console/assistant-chat.js").read_text(encoding="utf-8") css = (ROOT / "apps/web-console/assistant-chat.css").read_text(encoding="utf-8") @@ -383,6 +415,18 @@ def test_chat_styles_include_accessible_responsive_and_reduced_motion_states(): assert "@media (prefers-reduced-motion: reduce)" in css +def test_dispatched_control_card_spans_the_full_assistant_message_row(): + css = (ROOT / "apps/web-console/assistant-chat.css").read_text(encoding="utf-8") + layout = css.split("/* Compact command receipt", 1)[1].split( + "/* The function-mode strip", 1 + )[0] + + assert "grid-template-columns: 30px minmax(0, 1fr)" in layout + assert "display: contents" in layout + assert "grid-column: 1 / -1" in layout + assert "max-width: none" in layout + + def test_assistant_does_not_restore_collapsed_sidebars_and_uses_mobile_overlays(): script = (ROOT / "apps/web-console/assistant-chat.js").read_text(encoding="utf-8") css = (ROOT / "apps/web-console/assistant-chat.css").read_text(encoding="utf-8") diff --git a/tests/unit/test_energy_monitor_frontend_contract.py b/tests/unit/test_energy_monitor_frontend_contract.py index 4ac2aeb..02b80f5 100644 --- a/tests/unit/test_energy_monitor_frontend_contract.py +++ b/tests/unit/test_energy_monitor_frontend_contract.py @@ -35,10 +35,10 @@ def test_energy_monitor_assets_are_versioned_and_cached() -> None: ): assert asset in html assert f'"{asset}"' in service_worker - assert html.index("/assistant-chat.js?v=codex-assistant-32") < html.index( + assert html.index("/assistant-chat.js?v=codex-assistant-33") < html.index( "/energy-monitor.js?v=energy-monitor-4" ) - assert 'const CACHE_VERSION = "cognitive-agent-pad-v302"' in service_worker + assert 'const CACHE_VERSION = "cognitive-agent-pad-v305"' in service_worker def test_energy_monitor_uses_the_fifth_shared_dock_icon_and_starts_closed() -> None: diff --git a/tests/unit/test_frontend_theme_contract.py b/tests/unit/test_frontend_theme_contract.py index d06a96f..672d883 100644 --- a/tests/unit/test_frontend_theme_contract.py +++ b/tests/unit/test_frontend_theme_contract.py @@ -42,13 +42,13 @@ def test_theme_asset_versions_are_consistent_with_the_pwa_shell() -> None: for asset in ( "/styles.css?v=dual-seat-23", "/console-v2.css?v=delivery-state-69", - "/self-extension.css?v=pet-settings-49", + "/self-extension.css?v=pet-settings-52", "/speech-input.css?v=sensevoice-7", "/codepen-glass-theme.css?v=creative-cloud-2", ): assert asset in html assert f'"{asset}"' in service_worker - assert 'const CACHE_VERSION = "cognitive-agent-pad-v302"' in service_worker + assert 'const CACHE_VERSION = "cognitive-agent-pad-v305"' in service_worker def test_reference_glass_theme_covers_the_complete_workspace() -> None: diff --git a/tests/unit/test_nfc_frontend_contract.py b/tests/unit/test_nfc_frontend_contract.py index 4389147..a50f9a2 100644 --- a/tests/unit/test_nfc_frontend_contract.py +++ b/tests/unit/test_nfc_frontend_contract.py @@ -60,8 +60,8 @@ def test_unified_console_exposes_device_id_based_nfc_authorization() -> None: assert '.v2-nfc-command-list' in styles assert '.v2-nfc-command-option' in styles assert '.v2-nfc-select-all' in styles - assert 'self-extension.js?v=pet-settings-49' in html - assert 'self-extension.css?v=pet-settings-49' in html + assert 'self-extension.js?v=pet-settings-52' in html + assert 'self-extension.css?v=pet-settings-52' in html def test_unified_console_exposes_gscu_tcp_configuration_and_actions() -> None: diff --git a/tests/unit/test_requested_frontend_enhancements.py b/tests/unit/test_requested_frontend_enhancements.py index 67b1f43..e060f9d 100644 --- a/tests/unit/test_requested_frontend_enhancements.py +++ b/tests/unit/test_requested_frontend_enhancements.py @@ -1110,7 +1110,8 @@ def test_interactive_console_uses_one_top_right_icon_dock_for_all_five_panels() assert 'data-lucide="car-front"' in console assert 'data-lucide="usb"' in console assert 'data-lucide="bot-message-square"' in assistant - assert '($("#v2PanelControlDock") || document.body).append(trigger)' in assistant + assert 'const panelDock = $("#v2PanelControlDock")' in assistant + assert "panelDock.insertBefore(trigger, firstRightPanelToggle)" in assistant assert 'trigger.className = "assistant-drawer-toggle v2-panel-control-button"' in assistant assert 'trigger.className = "energy-monitor-toggle v2-panel-control-button"' in energy assert '$("#v2PanelControlDock").append(trigger)' in energy diff --git a/tests/unit/test_spatial_ui_consistency_contract.py b/tests/unit/test_spatial_ui_consistency_contract.py index 921ac08..6185c82 100644 --- a/tests/unit/test_spatial_ui_consistency_contract.py +++ b/tests/unit/test_spatial_ui_consistency_contract.py @@ -12,7 +12,7 @@ def source(name: str) -> str: def test_spatial_ui_contract_is_loaded_last_and_cached() -> None: html = source("index.html") service_worker = source("service-worker.js") - asset = "/ui-consistency.css?v=spatial-ui-4" + asset = "/ui-consistency.css?v=spatial-ui-5" assert asset in html assert f'"{asset}"' in service_worker @@ -25,6 +25,10 @@ def test_same_kind_surfaces_share_geometry_tokens() -> None: for token in ( "--ui-control-height-compact:", "--ui-control-height:", + "--ui-font-card-title:", + "--ui-font-section-title:", + "--ui-font-panel-title:", + "--ui-inline-action-width:", "--ui-radius-control:", "--ui-radius-item:", "--ui-radius-section:", @@ -59,6 +63,7 @@ def test_same_kind_controls_share_sizes_and_focus_treatment() -> None: "#v2PanelControlDock > .v2-panel-control-button", ".v2-mode-rail .mode-rail-actions > button", ".v2-device-adjust-toggle", + ".v2-pet-settings-launch", ".v2-extension-nfc-launch", ".energy-trend-tabs button", ".mode-gallery-close", @@ -71,6 +76,44 @@ def test_same_kind_controls_share_sizes_and_focus_treatment() -> None: assert "outline: none" in styles +def test_panel_titles_and_extension_actions_share_type_and_size_tokens() -> None: + styles = source("ui-consistency.css") + + for selector in ( + ".v2-operation-heading h2", + ".joint-twin-shell .twin-heading h3", + ".v2-device-sync-board > header h3", + ".v2-extension-sync-heading h3", + ".v2-device-card-focus strong", + ".v2-nfc-inline strong", + ".v2-extension-gateway strong", + ): + assert selector in styles + + assert "font-size: var(--ui-font-panel-title) !important" in styles + assert "font-size: var(--ui-font-card-title) !important" in styles + assert "grid-template-columns: repeat(2, minmax(0, 1fr))" in styles + assert "width: var(--ui-inline-action-width)" in styles + + +def test_pet_and_tcp_dialog_controls_keep_accessible_aligned_layout() -> None: + styles = source("self-extension.css") + extension = source("self-extension.js") + tcp_form = styles.split(".v2-gscu-config-form {", 1)[1].split("}", 1)[0] + tcp_submit = styles.split( + ".v2-gscu-config-form > .v2-extension-primary {", 1 + )[1].split("}", 1)[0] + + assert ".v2-extension-dialog .v2-pet-card-region > header > button" in styles + assert "min-width: 104px" in styles + assert "align-items: end" in tcp_form + assert "height: 40px" in tcp_submit + assert "margin: 0" in tcp_submit + assert 'id="extensionGscuTcpHostHint"' in extension + assert 'id="extensionGscuTcpPortHint"' in extension + assert 'class="visually-hidden" id="extensionPetSettingsDescription"' in extension + + def test_compact_sidebar_overrides_cannot_break_shared_geometry() -> None: styles = source("ui-consistency.css") diff --git a/tests/unit/test_vue_frontend_contract.py b/tests/unit/test_vue_frontend_contract.py index 714772e..bedce05 100644 --- a/tests/unit/test_vue_frontend_contract.py +++ b/tests/unit/test_vue_frontend_contract.py @@ -18,14 +18,14 @@ def test_vue_presentation_assets_are_local_ordered_and_cached() -> None: presentation = "/vue-ui.js?v=vue-presentation-3" assert html.index(runtime) < html.index(presentation) < html.index( - "/app.js?v=dual-seat-139" + "/app.js?v=dual-seat-140" ) assert html.index(presentation) < html.index( - "/assistant-chat.js?v=codex-assistant-32" + "/assistant-chat.js?v=codex-assistant-33" ) assert f'"{runtime}"' in service_worker assert f'"{presentation}"' in service_worker - assert 'const CACHE_VERSION = "cognitive-agent-pad-v302"' in service_worker + assert 'const CACHE_VERSION = "cognitive-agent-pad-v305"' in service_worker assert "unpkg.com" not in html assert "cdn.jsdelivr.net" not in html