From 589bf518e2f5fe65b1625a3ed9382b4c2444021c Mon Sep 17 00:00:00 2001 From: Francis Du Date: Thu, 17 Sep 2026 03:11:28 +0800 Subject: [PATCH 1/5] test(webui): report exact WebKit overflow geometry without weakening assertions --- tests/unit/ui/browser_webkit.swift | 38 +++++++++++++++++++++++------- 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/tests/unit/ui/browser_webkit.swift b/tests/unit/ui/browser_webkit.swift index 18016c0..85582d2 100644 --- a/tests/unit/ui/browser_webkit.swift +++ b/tests/unit/ui/browser_webkit.swift @@ -11,15 +11,29 @@ final class BrowserAudit: NSObject, WKNavigationDelegate { var index = 0 let check = #""" (()=>{ - const errors=[], r=e=>e.getBoundingClientRect(), visible=e=>e.getClientRects().length>0; - const check=(ok,label)=>{if(!ok)errors.push(label);}; + const errors=[],diagnostics=[],r=e=>e.getBoundingClientRect(),visible=e=>e.getClientRects().length>0; + const describe=el=>{ + const box=r(el),style=getComputedStyle(el); + return {tag:el.tagName,id:el.id,className:String(el.className), + rect:{left:box.left,top:box.top,right:box.right,bottom:box.bottom,width:box.width,height:box.height}, + clientWidth:el.clientWidth,scrollWidth:el.scrollWidth,clientHeight:el.clientHeight,scrollHeight:el.scrollHeight, + display:style.display,position:style.position,top:style.top,bottom:style.bottom, + minWidth:style.minWidth,width:style.width,height:style.height,alignSelf:style.alignSelf, + columns:style.gridTemplateColumns,rows:style.gridTemplateRows,gap:style.gap}; + }; + const check=(ok,label,details)=>{if(!ok){errors.push(label);diagnostics.push({label,...details});}}; check(window.__layoutReady===true,'production boot failed'); - check(document.documentElement.scrollWidth<=innerWidth+1,'page overflow'); + check(innerWidth===window.__expectedAuditWidth,'viewport resize not applied', + {expected:window.__expectedAuditWidth,actual:innerWidth}); + const pageFits=document.documentElement.scrollWidth<=innerWidth+1; + check(pageFits,'page overflow',pageFits?undefined:{page:describe(document.documentElement), + outside:[...document.body.querySelectorAll('*')].filter(el=>visible(el)&&(r(el).left < -1||r(el).right>innerWidth+1)).slice(0,24).map(describe)}); check(document.querySelectorAll('[role="tab"][aria-selected="true"]').length===1,'tab selection'); for(const selector of ['.bar-row','.frontier-row','.evidence-ledger-head','.evidence-ledger-row','.evidence-inspector-identity','.proof-signal-card','.workspace-context','.global-bar']){ document.querySelectorAll(selector).forEach((el,i)=>{ if(!visible(el))return; - check(el.scrollWidth<=el.clientWidth+1,selector+' content overflow '+i); + const fits=el.scrollWidth<=el.clientWidth+1; + check(fits,selector+' content overflow '+i,fits?undefined:{element:describe(el),children:[...el.children].filter(visible).map(describe)}); if(selector==='.bar-row'){ const a=r(el.querySelector('.bar-name')),b=r(el.querySelector('.bar-val')),c=r(el.querySelector('.bar-track')); check(a.right<=b.left+1,'stat overlap');check(c.top>=Math.max(a.bottom,b.bottom)-1,'bar overlap'); @@ -28,14 +42,19 @@ final class BrowserAudit: NSObject, WKNavigationDelegate { } document.querySelectorAll('.adaptive-cards,.code-distribution,.proof-main-grid,.proof-signal-grid').forEach(grid=>{ if(!visible(grid))return;const parent=r(grid),children=[...grid.children].filter(visible); - children.forEach((child,i)=>{const a=r(child);check(a.left>=parent.left-1&&a.right<=parent.right+1&&a.bottom<=parent.bottom+1,'grid child outside '+grid.className); - children.slice(i+1).forEach(other=>{const b=r(other);check(Math.min(a.right,b.right)-Math.max(a.left,b.left)<=1||Math.min(a.bottom,b.bottom)-Math.max(a.top,b.top)<=1,'grid overlap '+grid.className);});}); + children.forEach((child,i)=>{ + const a=r(child),fits=a.left>=parent.left-1&&a.right<=parent.right+1&&a.bottom<=parent.bottom+1; + check(fits,'grid child outside '+grid.className,fits?undefined:{grid:describe(grid),child:describe(child),index:i}); + children.slice(i+1).forEach(other=>{const b=r(other);check(Math.min(a.right,b.right)-Math.max(a.left,b.left)<=1||Math.min(a.bottom,b.bottom)-Math.max(a.top,b.top)<=1,'grid overlap '+grid.className);}); + }); }); if(state.workspaceTab==='proof'){ check(document.querySelectorAll('[data-evidence-key]').length===32,'missing populated ledger'); check(!document.querySelector('.evidence-inspector-section .inspector-chip.good'),'failed proof green'); } - return {width:innerWidth,language:state.language,theme:state.theme,tab:state.workspaceTab,errors}; + return {width:innerWidth,language:state.language,theme:state.theme,tab:state.workspaceTab, + scrollX,scrollY,devicePixelRatio,fontStatus:document.fonts.status, + coarsePointer:matchMedia('(pointer:coarse)').matches,errors,diagnostics}; })() """# override init(){ @@ -50,12 +69,13 @@ final class BrowserAudit: NSObject, WKNavigationDelegate { func next(){ guard index Date: Thu, 17 Sep 2026 03:11:47 +0800 Subject: [PATCH 2/5] ci(webui): run a fast macOS layout gate and retain failing geometry --- .github/workflows/webui-layout.yml | 59 ++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 .github/workflows/webui-layout.yml diff --git a/.github/workflows/webui-layout.yml b/.github/workflows/webui-layout.yml new file mode 100644 index 0000000..e422686 --- /dev/null +++ b/.github/workflows/webui-layout.yml @@ -0,0 +1,59 @@ +name: WebUI layout + +on: + pull_request: + paths: + - src/ui/intelligence_web/** + - tests/unit/ui/** + - .github/workflows/webui-layout.yml + push: + branches: [main, master] + paths: + - src/ui/intelligence_web/** + - tests/unit/ui/** + - .github/workflows/webui-layout.yml + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: webui-layout-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + webkit: + name: WebKit production layout + runs-on: macos-latest + timeout-minutes: 10 + steps: + - name: Checkout exact candidate + uses: actions/checkout@v7 + with: + ref: ${{ github.event.pull_request.head.sha || github.sha }} + persist-credentials: false + + - name: Install JavaScript test runtime + uses: actions/setup-node@v6 + with: + node-version: "24" + package-manager-cache: false + + - name: Generate offline production fixture + run: node tests/unit/ui/browser.cjs . + + - name: Validate real WebKit Observatory layout + run: swift tests/unit/ui/browser_webkit.swift + + - name: Preserve reproducible layout evidence + if: always() + uses: actions/upload-artifact@v7 + with: + name: webui-layout-${{ github.run_id }}-${{ github.run_attempt }} + path: | + target/wcode-browser-fixture.html + target/wcode-browser-audit.json + src/ui/intelligence_web/styles/ + tests/unit/ui/browser_webkit.swift + if-no-files-found: error + retention-days: 3 From 80d2ca06697a0ea1c79381bd4b06bcd3c1bac9f0 Mon Sep 17 00:00:00 2001 From: Francis Du Date: Thu, 17 Sep 2026 03:19:45 +0800 Subject: [PATCH 3/5] fix(webui): wrap evidence payloads and exercise hosted WebKit resizing Keep long policy identifiers and diagnostic lines inside the evidence inspector instead of expanding its horizontal scroll area. Extend the real-engine assertions to the inspector and its inner content. Host WKWebView in an NSWindow so viewport transitions run through the normal view hierarchy; retain the complete 96-scenario matrix, original containment checks, and exact viewport validation. Chromium: 96 full-production cases pass with zero inspector overflows. macOS verification remains required before merge or release. --- src/ui/intelligence_web/styles/data.css | 5 +++++ tests/unit/ui/browser_webkit.swift | 19 +++++++++++++++---- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/src/ui/intelligence_web/styles/data.css b/src/ui/intelligence_web/styles/data.css index 5598570..b7e6a1e 100644 --- a/src/ui/intelligence_web/styles/data.css +++ b/src/ui/intelligence_web/styles/data.css @@ -19,3 +19,8 @@ .lane-spacer{height:14px;}.card-copy{margin-top:6px;}.card-gap{margin-top:10px;}.compact-banner{margin:14px 0 0;}.quality-summary{border-top:0;}.quality-summary .pills{margin-top:0;}.risk-heading{margin-top:16px!important;} footer{margin-top:8px;padding:0 4px;color:var(--muted);font-size:12px;} code{font:13px var(--font-mono);color:var(--soft);} + +/* Unbounded identifiers must wrap before creating an inspector scrollbar. */ +.evidence-inspector-state{flex-wrap:wrap;min-width:0;} +.evidence-inspector-state code,.evidence-inspector-card .inspector-chip{min-width:0;max-width:100%;white-space:normal;overflow-wrap:anywhere;} +.evidence-inspector-section pre{overflow-wrap:anywhere;} diff --git a/tests/unit/ui/browser_webkit.swift b/tests/unit/ui/browser_webkit.swift index 85582d2..2ca3e42 100644 --- a/tests/unit/ui/browser_webkit.swift +++ b/tests/unit/ui/browser_webkit.swift @@ -5,6 +5,7 @@ import WebKit final class BrowserAudit: NSObject, WKNavigationDelegate { let web: WKWebView + let window: NSWindow let widths = [320,375,720,900,1024,1240,1280,1440,1461,1597,1676,1920] var scenarios: [(Int,String,String,String)] = [] var reports: [[String:Any]] = [] @@ -29,7 +30,7 @@ final class BrowserAudit: NSObject, WKNavigationDelegate { check(pageFits,'page overflow',pageFits?undefined:{page:describe(document.documentElement), outside:[...document.body.querySelectorAll('*')].filter(el=>visible(el)&&(r(el).left < -1||r(el).right>innerWidth+1)).slice(0,24).map(describe)}); check(document.querySelectorAll('[role="tab"][aria-selected="true"]').length===1,'tab selection'); - for(const selector of ['.bar-row','.frontier-row','.evidence-ledger-head','.evidence-ledger-row','.evidence-inspector-identity','.proof-signal-card','.workspace-context','.global-bar']){ + for(const selector of ['.bar-row','.frontier-row','.evidence-ledger-head','.evidence-ledger-row','.evidence-inspector-identity','.proof-signal-card','.workspace-context','.global-bar','.evidence-inspector-card','.evidence-inspector-section','.evidence-inspector-card .inspector-chip-list','.evidence-inspector-section pre']){ document.querySelectorAll(selector).forEach((el,i)=>{ if(!visible(el))return; const fits=el.scrollWidth<=el.clientWidth+1; @@ -54,13 +55,21 @@ final class BrowserAudit: NSObject, WKNavigationDelegate { } return {width:innerWidth,language:state.language,theme:state.theme,tab:state.workspaceTab, scrollX,scrollY,devicePixelRatio,fontStatus:document.fonts.status, - coarsePointer:matchMedia('(pointer:coarse)').matches,errors,diagnostics}; + coarsePointer:matchMedia('(pointer:coarse)').matches, + gutter:getComputedStyle(document.documentElement).getPropertyValue('--page-gutter-x'), + media:[1680,1460,1240,900,720,520].map(width=>({width,matches:matchMedia(`(max-width:${width}px)`).matches})),errors,diagnostics}; })() """# override init(){ let config=WKWebViewConfiguration();config.websiteDataStore = .nonPersistent() web=WKWebView(frame:NSRect(x:0,y:0,width:1597,height:900),configuration:config) + window=NSWindow(contentRect:NSRect(x:0,y:0,width:1597,height:900),styleMask:.borderless,backing:.buffered,defer:false) super.init();web.navigationDelegate=self + // Host the renderer so resizing exercises the actual view hierarchy. + window.isReleasedWhenClosed=false + window.contentView=web + web.autoresizingMask=[.width,.height] + window.orderFront(nil) for width in widths {for lang in ["en","zh-CN"] {for theme in ["dark","light"] {for tab in ["proof","overview"] {scenarios.append((width,lang,theme,tab))}}}} } func start(){let root=URL(fileURLWithPath:FileManager.default.currentDirectoryPath);let file=root.appendingPathComponent("target/wcode-browser-fixture.html");web.loadFileURL(file,allowingReadAccessTo:root)} @@ -74,7 +83,9 @@ final class BrowserAudit: NSObject, WKNavigationDelegate { try! data.write(to:URL(fileURLWithPath:"target/wcode-browser-audit.json"));print(String(data:data,encoding:.utf8)!);exit(failures==0 ? 0:1) } let (width,lang,theme,tab)=scenarios[index];index+=1 - web.setFrameSize(NSSize(width:width,height:900));web.layoutSubtreeIfNeeded() + window.setContentSize(NSSize(width:width,height:900)) + web.layoutSubtreeIfNeeded() + window.displayIfNeeded() let setup="window.__expectedAuditWidth=\(width);state.language='\(lang)';state.theme='\(theme)';applyTheme();applyLanguage();activateWorkspaceTab('\(tab)');window.scrollTo(0,0);" web.evaluateJavaScript(setup){_,error in if let error {fputs("\(error)\n",stderr);exit(2)} @@ -85,6 +96,6 @@ final class BrowserAudit: NSObject, WKNavigationDelegate { } } } -let app=NSApplication.shared;app.setActivationPolicy(.prohibited) +let app=NSApplication.shared;app.setActivationPolicy(.accessory) let audit=BrowserAudit();DispatchQueue.main.asyncAfter(deadline:.now()+90){fputs("browser audit timed out\n",stderr);exit(2)} audit.start();app.run() From 1c66c9bd78b8ee7f98709869510e9794da29cac6 Mon Sep 17 00:00:00 2001 From: Francis Du Date: Thu, 17 Sep 2026 03:21:56 +0800 Subject: [PATCH 4/5] ci(release): verify all 30 adversarial rounds on the exact layout candidate --- .github/workflows/webui-layout.yml | 57 +++++++++++++++++++++++++++--- 1 file changed, 53 insertions(+), 4 deletions(-) diff --git a/.github/workflows/webui-layout.yml b/.github/workflows/webui-layout.yml index e422686..ce827dc 100644 --- a/.github/workflows/webui-layout.yml +++ b/.github/workflows/webui-layout.yml @@ -3,14 +3,18 @@ name: WebUI layout on: pull_request: paths: - - src/ui/intelligence_web/** - - tests/unit/ui/** + - src/** + - tests/** + - Cargo.toml + - Cargo.lock - .github/workflows/webui-layout.yml push: branches: [main, master] paths: - - src/ui/intelligence_web/** - - tests/unit/ui/** + - src/** + - tests/** + - Cargo.toml + - Cargo.lock - .github/workflows/webui-layout.yml workflow_dispatch: @@ -57,3 +61,48 @@ jobs: tests/unit/ui/browser_webkit.swift if-no-files-found: error retention-days: 3 + + adversarial: + name: Thirty adversarial release rounds + needs: webkit + runs-on: macos-latest + timeout-minutes: 20 + steps: + - name: Checkout exact candidate + uses: actions/checkout@v7 + with: + ref: ${{ github.event.pull_request.head.sha || github.sha }} + persist-credentials: false + + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + + - name: Cache Cargo + uses: Swatinem/rust-cache@v2 + + - name: Install JavaScript test runtime + uses: actions/setup-node@v6 + with: + node-version: "24" + package-manager-cache: false + + - name: Prepare adversarial test binary + run: cargo test --locked --lib --no-run + + - name: Validate all thirty adversarial rounds + run: | + git rev-parse HEAD > target/wcode-audit-revision.txt + node tests/release_audit.cjs + + - name: Preserve revision-bound adversarial evidence + if: always() + uses: actions/upload-artifact@v7 + with: + name: adversarial-30-${{ github.run_id }}-${{ github.run_attempt }} + path: | + target/wcode-audit-revision.txt + target/wcode-adversarial-30.json + target/wcode-webkit-layout.json + target/wcode-browser-audit.json + if-no-files-found: error + retention-days: 14 From 97992845b2b3c8ff2630c7deff3e77bda71dc6a4 Mon Sep 17 00:00:00 2001 From: Francis Du Date: Thu, 17 Sep 2026 03:35:00 +0800 Subject: [PATCH 5/5] docs(release): describe inspector wrapping and reproducible native layout evidence Document the long evidence identifier fix and the window-backed WebKit/30-round CI evidence in both release-note languages. Keep the exact-revision publication gate and runtime-upgrade caveat explicit. --- docs/manual/releases/v0.7.5.md | 4 ++++ docs/manual/releases/v0.7.5.zh-CN.md | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/docs/manual/releases/v0.7.5.md b/docs/manual/releases/v0.7.5.md index bdfd1ce..9a0d3b9 100644 --- a/docs/manual/releases/v0.7.5.md +++ b/docs/manual/releases/v0.7.5.md @@ -27,6 +27,8 @@ Unregistered hosts, invalid origins and unauthenticated requests still fail clos Code statistics use shrinkable label/value tracks and a separate progress row instead of fixed widths that overlap adjacent columns. Verification previews use content-driven height, wrapping long test names and a dedicated fail-fast row layout. The evidence inspector stacks at constrained widths. English descriptive copy no longer exposes internal translation keys. +Long evidence identifiers, policy/model chips and diagnostic lines wrap inside the inspector rather than forcing a horizontal scrollbar or extending beyond their grid row. Regression checks cover the inspector's inner sections as well as the outer card; the fix preserves complete text rather than hiding overflow. + Refresh errors distinguish authorization, Host/origin denial, timeout, malformed responses and rendering exceptions without placing internal paths or stacks in page text. Full project snapshots have a bounded 120-second deadline; lightweight cached requests retain 30 seconds. Rendering failure cannot certify the new snapshot revision. Adaptive checks remain a read-only planning preview, not execution from the Observatory. ## Repository integrity and fail-closed release gates @@ -53,4 +55,6 @@ Run `cargo test --locked`, `cargo clippy --locked --all-targets -- -D warnings`, `node tests/release_audit.cjs` runs 30 distinct adversarial rounds on macOS, with independent Rust and Web lanes, nonempty-test checks, bounded execution and a before/after input digest. Results are recorded in `target/wcode-adversarial-30.json`. The full production-page WebKit audit covers 96 combinations of 12 widths (320–1920 pixels), English/Chinese, light/dark and proof/overview. Portable rendering-failure scenarios are included in `cargo test`; the actual WebKit audit also runs in macOS CI. +The full-page runner hosts WebKit in a real window, waits for fonts and animation frames, and validates the requested viewport. Navigation failures and timeouts produce partial failure reports instead of a silent hang. The independent adversarial workflow preserves the synthetic HTML, shipped CSS, layout reports, exact checked-out revision and 30-round results for diagnosis; failed geometry checks are not skipped to publish. + Publication follows reviewed commit → non-force push → successful CI for that exact SHA → version tag → distribution builds and smoke tests. After installation, restart the running wcode process, reconnect MCP and reopen the Observatory; rebuilding a binary does not update an already-running server. diff --git a/docs/manual/releases/v0.7.5.zh-CN.md b/docs/manual/releases/v0.7.5.zh-CN.md index 37d5cfa..511b9b0 100644 --- a/docs/manual/releases/v0.7.5.zh-CN.md +++ b/docs/manual/releases/v0.7.5.zh-CN.md @@ -27,6 +27,8 @@ v0.7.5 加固入口所有权、TUI 按键分发、观测台渲染与仓库验证 代码统计改用可收缩的名称/数值列,进度条单独占一行,不再因固定列宽覆盖相邻统计。验证预览按内容计算高度,长测试名自动换行,快速失败列表使用独立布局。受限宽度下证据检查器移到下方。英文说明不再暴露内部翻译键名。 +过长的证据标识、策略/模型标签和诊断文本会在检查器内部换行,不再撑出横向滚动条或超出网格行。回归检查同时覆盖外层卡片和内部各节,修复保留完整文本,不以隐藏溢出内容掩盖问题。 + 刷新错误区分授权、Host/Origin 拒绝、超时、响应异常和渲染异常,不在页面直接显示内部路径或堆栈。完整项目快照请求使用 120 秒有界等待,轻量缓存请求保持 30 秒。渲染失败不能把新快照版本记为成功;自适应检查仍是只读规划预览,不会从观测台直接执行。 ## 仓库完整性与失败关闭的发布门禁 @@ -53,4 +55,6 @@ wcode help-all --json macOS 上运行 `node tests/release_audit.cjs` 可执行 30 轮不同场景的对抗检查:Rust 与 Web 两条独立验证线并行,每项要求实际命中测试、执行有界,并比对检查前后的输入摘要。结果保存在 `target/wcode-adversarial-30.json`。完整生产页面的 WebKit 审计覆盖十二种宽度(320–1920 像素)、中英文、明暗主题与证据/总览页面的 96 种组合。跨平台渲染故障场景已接入 `cargo test`,macOS CI 也执行真实 WebKit 审计。 +完整页面测试将 WebKit 挂载到真实窗口,等待字体和动画帧就绪,并核对实际视口宽度。导航失败或超时会输出部分失败报告,不再静默卡住。独立对抗检查工作流保留合成测试页、随包 CSS、布局报告、实际检出的提交号和 30 轮结果,便于诊断;不会跳过布局失败来发布。 + 发布顺序为审查提交 → 非强制推送 → 精确 SHA 的 CI 全绿 → 版本标签 → 分发包构建与冒烟测试。安装后必须重启正在运行的 wcode、重新连接 MCP 并重新打开观测台;构建新二进制不会自动更新旧服务进程。