Skip to content

feat(web-shell): add footnote previews and per-turn sources - #11480

Open
callmeYe wants to merge 18 commits into
mainfrom
codex/markdown-footnote-cards
Open

feat(web-shell): add footnote previews and per-turn sources#11480
callmeYe wants to merge 18 commits into
mainfrom
codex/markdown-footnote-cards

Conversation

@callmeYe

@callmeYe callmeYe commented Sep 9, 2026

Copy link
Copy Markdown
Collaborator

What this PR does

Web Shell renders resolved Markdown footnotes as grouped, paginated previews. Numeric, named and Chinese IDs, linked sources and multiline plain-text notes share the same behavior. Hosts can choose an SVG/image URL for each inline group and mount framework-neutral DOM or SolidJS content for the current page while Qwen retains navigation, hover, keyboard handling and pagination. Original definitions remain intact when copying Markdown or exporting a static document. Definitions containing nested footnote references stay in the standard list so their navigation remains intact.

The Assistant action footer independently shows the current turn's Sources-panel entries, including attachment fallbacks, instead of counting footnotes. A source reused in another turn counts in both turns. Hover opens the turn's source list, and selecting an item uses the existing source preview. Hosts can choose a separate footer icon and explicitly associate registered sources with user turns. Source and attachment data update without opening the environment panel or interacting with the report.

Why it's needed

Reports combine web pages, files, attachments, knowledge records and explanatory notes. Inline footnotes identify what a passage cites; the Sources panel also contains material associated with a turn that has no footnote. These two controls need independent data while retaining the same host customization and existing source-opening behavior.

Reviewer Test Plan

How to verify

  • Render adjacent [^a][^b], separate [^c], repeated IDs, Chinese/numeric IDs and multiline plain notes. Expect one group per adjacent run, one page per definition, complete original definitions and no URL-based merging.
  • Customize the inline icon and current-page content independently. Expect the complete group on each page, updates without remounting the host view, cleanup on close, and working fallback for declined or failed mounts. Paging stays open and links continue through the host's existing Markdown link renderer.
  • Associate the same registered source with two turns and another source with only the second turn. Expect footer counts of one and two, only on each turn's final answer, even when the reports have no footnotes. Unrelated session sources and deleted sources must not appear.
  • Upload attachments with only attachment-list capability available. Expect the source footer before opening the environment panel, and updated counts when a later turn reuses an attachment and adds another. Registered attachments and fallback attachments must not duplicate each other.
  • Open the demo with three footnotes, four current-turn sources and five session-wide sources. Expect independent inline/footer icons, a four-item hover list, a five-item session panel and the existing preview action for source rows. Custom footer icons remain 14px and normally centered; only the default knowledge glyph keeps the optical lift.
  • Check keyboard/touch controls, hover visibility, streaming updates, narrow viewports, light/dark themes, isolated messages, missing definitions, host link interception, Markdown copy and static export. Activating a source by keyboard should restore the trigger unless the host moves focus; a clicked source popup should remain open without relying on automatic button focus.

Evidence (Before & After)

Before: the footer summarized footnotes and could omit source-panel entries that had no footnote. After: the demo retains three inline footnotes while its footer shows four turn sources from the five-item session inventory. Browser regressions also verify that asynchronously loaded sources appear without clicking the report and that attachment-only sources refresh with the environment panel closed. A separate E2E report records the completed development and production checks.

Tested on

OS Status
🍏 macOS ✅ Local focused tests and Chromium development/production E2E
🪟 Windows ⚠️ Not tested locally
🐧 Linux ⚠️ Not tested locally; CI runs separately

Environment (optional)

Local Web Shell development server, built production app and a separately compiled host consuming the published transcript entry point, with controlled mock-daemon events and source inventories.

Risk & Scope

  • Main tradeoff: turn associations come from successful top-level source-registration records, user attachment references or explicit host associations. Registration timestamps are not treated as evidence of use. Hook/client/manual registrations without a turn association remain session-panel entries.
  • Out of scope: new Core/MCP persistence protocols, automatic tracking of every source producer, OpenCode business URL resolution and injecting primary-session data into secondary panes. Read-only transcript hosts supply their own source inventory and open callback; panes without source data remain empty.
  • Migration within this unmerged PR: the footer icon callback moves out of Markdown customization and receives real Sources entries. The current-page content slot applies only to inline footnotes. Existing main-branch Markdown customization remains compatible.
  • Design: English · 中文.

Linked Issues

Builds on the Session Sources capability from #11262.

中文说明

本 PR 的改动

Web Shell 将可解析的 Markdown 脚注展示为可聚合、可翻页的预览。数字、命名、中文 ID、有链接的来源及多行纯文本说明使用相同行为。宿主可为各组正文脚注选择 SVG/图片 URL,并以框架无关接口挂载原生 DOM 或 SolidJS 当前页内容,Qwen 保留导航、Hover、键盘操作和分页。复制 Markdown 或导出静态文档时保留原始脚注定义。包含嵌套脚注引用的定义保留普通文末列表,确保导航完整。

Assistant 操作栏独立展示本轮来源面板条目(包含附件兜底),不再统计脚注。旧来源在其他轮次再次使用时,两轮都计入。Hover 展示本轮来源列表,点击条目沿用现有来源预览。宿主可独立定制操作栏图标,并将已登记来源明确关联到用户轮次。来源与附件数据更新不依赖打开环境面板或操作报告。

为什么需要

报告混合网页、文件、附件、知识库记录及说明性脚注。正文脚注表达段落引用,来源面板还包含本轮关联、但没有脚注的材料。两个入口需要独立的数据,同时保留一致的宿主定制能力和已有来源打开行为。

审阅者测试计划

如何验证

  • 渲染相邻的 [^a][^b]、独立的 [^c]、重复 ID、中文/数字 ID 及多行纯文本说明。应按连续引用分组,每个 definition 一页,保留完整原文,不按 URL 合并。
  • 分别定制正文图标和当前页内容。翻页时应保留完整分组,更新不重挂宿主视图,关闭时清理;拒绝或失败时回退。点击分页后卡片保持打开,链接继续通过宿主现有 Markdown 链接组件。
  • 两轮关联同一已登记来源,第二轮再关联另一个来源。即使报告没有脚注,也应分别显示一个和两个来源,且仅出现在各轮最终回答。其他会话条目和已删除来源不应出现。
  • 在仅支持附件列表的环境上传附件。无需打开环境面板就应显示来源入口;下一轮复用附件并新增另一个后应更新数量。已登记附件与兜底附件不应重复。
  • 打开含三个脚注、四个本轮来源、五个会话来源的 Demo。正文与操作栏图标独立,Hover 列表四项,会话面板五项,来源条目沿用现有预览动作。自定义操作栏图标保持 14px 并正常居中,只有默认知识图标保留视觉上移补偿。
  • 检查键盘/触屏、Hover 显示、流式更新、窄视口、亮暗主题、跨消息隔离、缺失 definition、宿主链接接管、Markdown 复制及静态导出。键盘激活来源后应恢复触发器焦点,宿主主动移焦时尊重宿主;点击展开后不依赖按钮自动获焦来保持浮层。

证据(修改前后)

修改前:操作栏汇总脚注,可能遗漏没有脚注的来源面板条目。修改后:Demo 保留三个正文脚注,操作栏从五个会话来源中展示四个本轮来源。浏览器回归同时验证异步来源加载后无需点击报告即可显示,以及环境面板关闭时附件来源继续刷新。独立 E2E 报告记录已完成的开发和生产验收。

测试平台

OS 状态
🍏 macOS ✅ 本地定向测试及 Chromium 开发/生产 E2E
🪟 Windows ⚠️ 未在本地测试
🐧 Linux ⚠️ 未在本地测试,CI 单独执行

环境(可选)

本地 Web Shell 开发服务器、生产构建应用及单独编译的 transcript 导出入口消费宿主,使用受控 mock daemon 事件和来源数据。

风险与范围

  • 主要取舍:轮次关联来自成功的顶层来源登记记录、用户附件引用或宿主显式关联,不将登记时间视为使用证据。没有轮次关联的 hook/client/手动登记仍只属于会话来源面板。
  • 范围之外:新增 Core/MCP 持久化协议、自动追踪所有来源生产者、OpenCode 业务 URL 拼接,以及把主会话数据注入其他面板。只读 transcript 宿主提供自身来源目录和打开回调;无来源数据的面板保持为空。
  • 此未合入 PR 内的接口迁移:操作栏图标函数移出 Markdown 定制,接收真实来源条目;当前页内容插槽仅用于正文脚注。main 分支已有 Markdown 定制能力保持兼容。
  • 设计文档:English · 中文

关联事项

基于 #11262 的 Session Sources 能力。

@callmeYe

callmeYe commented Sep 9, 2026

Copy link
Copy Markdown
Collaborator Author

E2E verification report

Verified head: 623181ef57755e86727c00da7d6ee98d949c56cf after merging current origin/main. Environment: macOS, Node 22, Chromium desktop and touch emulation.

Check Result
Focused regression tests 258 passed across citation cards, assistant footer, Markdown, and advanced-table extraction
Development browser scenarios 12 passed in 25.5s
Production browser scenarios 10 passed in 25.2s; 2 source-only harnesses skipped
Repository build Passed
Web Shell typecheck Passed
Changed-file ESLint, Prettier, and diff checks Passed
npm/pnpm lockfile consistency Passed
Generated dependency notices Stable on regeneration; 656 dependencies
Independent final review C0 / S0

Remote CI on this final optical-alignment head passed Ubuntu tests, lint/static checks, no-AK integration, Web Shell E2E Smoke, visual capture, real-daemon E2E, desktop checks, and all install/lockfile gates. The separate automated code review is still running.

The final browser runs verify source-only activation through source-*, adjacent grouping and deduplication, single-source SVG rendering, multi-source counts, hover-only placement beside copy/branch/time, message-level citation pagination, delayed-dismissal pinning and reset, ordinary footnote definitions and return links, streaming updates without card remounts, keyboard and touch input, light/dark themes, narrow viewport collision, advanced tables, Shadow DOM portal placement, thumbnail failure, and absence of source-page metadata fetches. Geometry assertions pin the footer citation icon and copy icon to 14 px, citation text and time to 11 px, a 1 px optical lift for the knowledge glyph, and middle alignment for inline citation badges.

The development run also verifies the complete host demo: a fixed DataWorks knowledge locator reaches the host Markdown link renderer, the DOM exposes a controlled href="#", and clicking the card title opens the mocked right panel with the original kind, kbInstanceId, and docId fields. This fixture demonstrates the Qwen-to-host handoff; authenticated retrieval, production locator validation, and the final OpenCode URL builder remain outside this PR.

Commands from packages/web-shell:

npx vitest run --config vitest.config.ts client/components/messages/FootnoteCard.test.tsx client/components/messages/AssistantMessage.test.tsx client/components/messages/Markdown.test.ts client/components/messages/EnhancedMarkdownTable.test.tsx
PLAYWRIGHT_PORT=5187 npx playwright test client/e2e/web-shell.footnotes.spec.ts --config playwright.config.ts --project chromium --grep 'footnote source previews|source locator demo' --workers=1
FOOTNOTE_BUILT=1 PLAYWRIGHT_PORT=5188 npx playwright test client/e2e/web-shell.footnotes.spec.ts --config playwright.config.ts --project chromium --grep 'footnote source previews|source locator demo' --workers=1
中文测试报告

最终验证提交为 623181ef57755e86727c00da7d6ee98d949c56cf,已合入当前 origin/main。258 项定向回归测试、全仓构建、Web Shell 类型检查、lint、格式、diff、双锁文件和依赖声明校验全部通过。开发态 12 个浏览器场景通过,生产构建 10 个产品场景通过;Shadow DOM 源码页面和交互 Demo 是源码专用入口,因此在生产轮次按设计跳过。远端同一 head 的 Ubuntu 测试、lint/static、no-AK 集成、Web Shell E2E Smoke、视觉快照、real-daemon E2E、桌面检查和安装门禁也全部通过;单独的自动代码审查仍在运行。

浏览器验收覆盖 source-* 专用转换、相邻来源聚合去重、单来源 SVG、多来源数字、引用总入口在复制/分支/时间操作栏内随消息 Hover 显示、逐条翻页、延迟关闭竞态、普通脚注及回链、流式更新不重挂卡片、键盘和触屏、深浅主题、窄屏、增强表格、Shadow DOM portal、图片失败降级,以及预览时不抓取来源网页 metadata。

开发态 Demo 还验证了完整知识库 locator 会交给宿主 Markdown 链接渲染器;点击卡片标题后,宿主用受控的 href="#" 打开模拟右侧面板,并取得原始定位字段。真实鉴权检索、生产 locator 校验和 OpenCode 最终 URL 拼接不在本 PR 范围内。

@qwen-code-ci-bot

qwen-code-ci-bot commented Sep 9, 2026

Copy link
Copy Markdown
Collaborator

Qwen Triage finishedview run. See the stage comments in this thread for the result.

Qwen Triage 已完成 —— 查看运行。结果见本线程中的各阶段评论。

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Thanks for the PR — this one arrives with a bilingual design doc and a real test suite behind it, which makes the gate easy to run.

Template ✓ — every required section is present, including the reviewer test plan and the OS matrix. One small gap: the template asks a PR that adds a design document to link both language versions from the body, and docs/design/markdown-footnote-cards.md / .zh-CN.md are not linked. Worth adding so a reviewer lands on them directly.

Problem: this is a presentation change rather than a bug fix, so there is no reproduction to ask for. The premise holds against the code — remarkGfm is already wired into the shared renderer, so GFM footnotes already parse and already render today, as a run of separate superscript numbers whose only affordance is a jump to the footer. What I could not establish from the repo is how often that input actually arrives: I found no code in packages/core or packages/cli that emits footnote markdown, so it only shows up when a model writes it. Is there an observed case behind this — a provider, a web-search-style answer — or is it anticipatory? Not a blocker either way, because when no footnotes are present the new sup component falls through to a plain <sup>, so the cost of being early is one extra tree walk per parse. It is just the first question a maintainer will ask.

Direction: aligned. It stays inside packages/web-shell, changes no public API and no backend, and it keeps the two escape hatches that matter — static/document export retains standard footnotes, and a host that overrides sup opts out of grouping entirely. I checked claude-code's CHANGELOG for a comparable affordance and found no direct reference, but citation presentation is a normal concern for a chat surface, so the area is relevant.

Size: no core paths are touched, so the core-module gate does not apply here. For context, the 1391 changed lines break down as 486 production logic, 754 test, 88 docs, 62 lockfile, 1 SVG asset — the test code outweighs the feature, which is the right way round, and it sits below both the 500- and 1000-line thresholds.

Approach: the shape looks right. Annotating grouped sup nodes in the AST instead of post-processing the DOM is what lets the advanced-table copy path keep the original reference text, and that is the non-obvious part of the design — I would keep it. Two things I would genuinely weigh before merging:

  • The card is a full hover/focus/touch state machine: delayed open, delayed close, focus routing into the popover, per-page pagination, thumbnails with failure collapse. That is 218 lines for one affordance. If most of the value is "read the source without jumping to the footer", a first cut of group + count + a plain list of links would cover a lot of it with a fraction of the surface. I am not asking for a rewrite — the pagination and thumbnail paths are tested and behave — only flagging that this is the part a maintainer may want to trim.
  • rehype-footnote-cards.ts walks the whole tree to locate the footnotes section before the if (!footer) return early exit, so a document with no footnotes still pays a full traversal on every throttled streaming re-render. If the footer is a top-level node of the hast root under the pinned mdast-util-gfm-footnote@2.1.0 — please sanity-check that rather than take my word — scanning tree.children first would make the common path O(top-level children). Entirely optional; I have no measurement saying it matters.

Nothing in the diff looks unrelated to the stated goal. The MarkdownLink change earns its place: namespaced anchors need explicit scroll-and-focus, because native #fragment navigation does not cross a ShadowRoot boundary.

Risk: no elevated risk signals — none of the changed files match the high-risk paths from the revert-history analysis. The real exposure is breadth rather than any single path: this switches on for every shared interactive Markdown surface at once, so an unintended visual change somewhere else is the failure mode to watch. Capture web-shell visuals is the job that would catch that, and it is still running.

Moving on to code review. 🔍

中文说明

感谢贡献!这个 PR 自带中英双语设计文档和一套实打实的测试,门禁跑起来很顺畅。

模板 ✓ —— 必填章节齐全,包括审阅者验证计划和操作系统矩阵。一个小缺口:模板要求新增设计文档的 PR 在正文里给出两个语言版本的链接,而 docs/design/markdown-footnote-cards.md / .zh-CN.md 没有被链接。补上更方便审阅者直达。

问题: 这是展示层改动而不是 bug 修复,所以不需要复现。前提在代码里成立——remarkGfm 已经接入共用渲染器,GFM 脚注现在就能解析、就能渲染,只是一串独立的上标数字,唯一的交互是跳到文末。我无法从仓库确认的是这种输入实际出现的频率:在 packages/corepackages/cli 里没有找到生成脚注 Markdown 的代码,也就是说只有模型自己写出来时才会出现。这背后是否有观测到的场景(某个 provider、某种联网搜索式回答),还是提前布局?两种情况都不构成阻塞,因为没有脚注时新的 sup 组件会退回普通 <sup>,提前布局的代价只是每次解析多一次树遍历。只是这会是 maintainer 第一个问的问题。

方向: 对齐。改动限定在 packages/web-shell 内,不动公开 API、不动后端,并且保留了两个关键的退出通道——静态/文档导出维持标准脚注,宿主自定义 sup 时完全不聚合。我查了 claude-code 的 CHANGELOG,没有找到直接对应的能力,但引用来源的展示对聊天界面来说是正常关注点,这个方向是相关的。

规模: 未触及核心路径,因此核心模块门禁不适用。供参考:1391 行改动拆分为 486 行生产逻辑754 行测试88 行文档62 行锁文件1 个 SVG 资源——测试代码多于功能代码,比例是对的,且低于 500 行和 1000 行两个阈值。

方案: 整体形态是对的。在 AST 上给聚合后的 sup 节点附加数据、而不是事后处理 DOM,正是高级表格复制能保留原始引用文本的原因,这是设计里最不显然的一点,建议保留。合并前有两处值得认真权衡:

  • 卡片是一套完整的 hover/focus/touch 状态机:延迟打开、延迟关闭、焦点移入弹层、逐页翻页、缩略图及失败收起。一个交互 affordance 用了 218 行。如果价值主要是"不用跳到文末就能看来源",那么"聚合 + 数量 + 一个纯链接列表"的第一版能用小得多的面覆盖大部分场景。不是要求重写——翻页和缩略图路径都有测试且行为正确——只是提示这部分是 maintainer 可能想裁剪的地方。
  • rehype-footnote-cards.tsif (!footer) return 提前退出之前就遍历了整棵树去找脚注区块,因此没有脚注的文档在每次流式节流重渲染时仍要付一次完整遍历。如果在锁定的 mdast-util-gfm-footnote@2.1.0 下脚注区块是 hast root 的顶层节点(请自行核实,不要只信我这句话),先扫 tree.children 就能让常见路径变成 O(顶层子节点数)。纯属可选,我没有数据说明它有实际影响。

diff 里没有与目标无关的改动。MarkdownLink 的改动是必要的:命名空间化的锚点需要显式的滚动加聚焦,因为原生 #fragment 跳转无法跨越 ShadowRoot 边界。

风险: 无升级风险信号——改动文件都没有命中回滚历史分析得出的高风险路径。真正的暴露面是广度而非某个单点:它一次性对所有共用的交互式 Markdown 界面生效,所以要盯的失败模式是别处出现了非预期的视觉变化。Capture web-shell visuals 就是能发现这类问题的任务,目前仍在运行。

进入代码审查 🔍

Qwen Code · qwen3.8-max-2026-09-02

Reviewed at c3338de142f28152f3517fb1bab38f8c92191b35 · re-run with @qwen-code /triage

@qwen-code-ci-bot

qwen-code-ci-bot commented Sep 9, 2026

Copy link
Copy Markdown
Collaborator

Code review

I went in expecting to push back on scope and mostly found careful work instead. No defects in the feature code. The things I checked that could plausibly have been wrong, and weren't:

  • Ref forwarding. PopoverContent is a React.forwardRef wrapper in this repo, so ref={content} is sound on both React 18 and 19 — which matters because the close-on-blur logic and the keyboard focus routing both read content.current. PopoverTrigger asChild wraps a plain DOM <button>, so Slot has nothing to forward. This is the AGENTS.md ref rule and it is satisfied.
  • The primitives it reaches for all exist. Button size="icon-sm" is a real variant; isSafeHref, isSafeImageSrc and markdownUrlTransform are all exported from Markdown.tsx; useExternalLinkOpener returns (event, url: string | undefined), so passing a possibly-undefined note.href typechecks; sourceComponents is in scope above the rehypePlugins memo it now feeds.
  • Duplicate DOM ids. This is where I expected to find a bug and didn't. Grouping remaps every ref in a group to the first ref's namespaced id, and the retained anchors are moved into the first sup's children — so naively you'd get four elements sharing one id. You don't, because FootnoteSup drops children when it renders the card, so those anchors never reach the DOM in interactive mode; they exist in the hast purely so advanced-table copy keeps the original reference text. The unit test asserting new Set(ids).size === ids.length across two documents pins this.
  • Security. Extracted links and images go through the existing URL policy before rendering, titles and summaries render as React text children, and no raw HTML is introduced. There is a test that feeds it javascript:alert%281%29 and a data:image/svg+xml URI and asserts both are dropped while the safe ones survive.
  • Blast radius when the feature is unused. group is off in document mode and off when a host overrides sup, and with no footnoteCards data the component renders a plain <sup>. Both opt-outs are tested. Unresolved [^ref] stays literal text until its definition streams in, also tested.

Four things to fix or consider — the first one blocks, the rest do not:

1. Blocking: NOTICES.txt was not regenerated after the dependency was added. Lint & Static is red on this commit:

Error: NOTICES.txt is out of date.
Please run: npm run generate:notices --workspace=qwen-code-vscode-ide-companion
Then commit the updated file.
 packages/vscode-ide-companion/NOTICES.txt | 27 +++++++++++++++++++++++++++
 1 file changed, 27 insertions(+)

This is the same miss as the lockfile one, one step further along: adding @types/hast changes the dependency tree, and packages/vscode-ide-companion/NOTICES.txt is generated from that tree, so it gains the new license entry. Both lockfiles were regenerated in c3338de1; the notices file was not. I confirmed this is not pre-existing drift — Lint & Static is green on main at 3733c8eb, the same base this PR sits on, and this PR is the only thing that touched the dependency tree. The sibling check two steps earlier (settings.schema.json) passed, so the drift is confined to the notices generation. The command above plus a commit clears it.

2. cssUrlValue already exists. FootnoteCard.tsx builds the icon mask with maskImage: `url("${knowledgeIcon}")`, twice. The repo has packages/web-shell/client/utils/cssUrlVar.ts, whose cssUrlValue(url) produces the same url("…") string with ", \, CR, LF and FF escaped — that is exactly what it is for. There is no real exposure here since knowledgeIcon is a build-time-bundled asset path, so this is a convention point rather than a security one: maskImage: cssUrlValue(knowledgeIcon). The two identical five-line mask style objects want to be one shared constant while you're in there.

3. The two lockfiles now disagree. @types/hast is pinned at ^3.0.5 while the tree already resolved 3.0.4, so npm installs a second nested copy at packages/web-shell/node_modules/@types/hast@3.0.5 alongside the root 3.0.4. The pnpm lockfile went the other way and deduped every transitive consumer up to 3.0.5, dropping 3.0.4 entirely — which is also why the notices diff is 27 lines rather than one entry. Type-only import, erased at build, so nothing breaks at runtime, but ^3.0.4 would have deduped under npm too and kept both lockfiles describing the same tree.

4. Carried over from the gate, so I won't repeat the argument: the card is a large state machine for one affordance, and the tree walk that locates the footnotes section runs before the early return, so footnote-free documents pay a full traversal per parse. Both are judgement calls for a maintainer, not defects.

My own independent take before reading the code was: group adjacent refs into a count pill, namespace anchors with useId(), route footnote links through the existing URL policy, and show the sources in the existing shared Popover — no pagination, no thumbnails. On the plumbing this PR matches that exactly, including the useId() namespacing and the ShadowRoot-aware link handling I'd have had to discover the hard way. It goes materially further than I would have on the card itself. That is the one place where a maintainer might reasonably ask for less.

sequenceDiagram
    participant P6 as Reader
    participant P1 as Markdown renderer
    participant P2 as rehypeFootnoteCards
    participant P3 as FootnoteSup
    participant P4 as FootnoteCard popover
    participant P5 as MarkdownLink backref
    P1->>P2: parse footnote markdown into hast
    P2->>P2: collect definitions and namespaced anchors
    P2->>P2: merge adjacent refs into one sup
    P2-->>P1: sup carries footnoteCards data
    P1->>P3: render sup component
    P3->>P4: notes present, render trigger pill with count
    P3-->>P1: no notes, render plain sup
    P6->>P4: hover, focus or tap
    P4-->>P6: one source per page with prev and next
    P6->>P5: click footer return link
    P5->>P5: resolve namespaced id via getRootNode
    P5-->>P6: scroll and focus the owning message entry
Loading

Test evidence

This is an unattended CI run, so I did not build or execute anything from this PR — the evidence below is the PR's own CI, read off the API for the reviewed commit.

The first commit was red and the author fixed part of it before this review landed. At 171514bd, pnpm Worktree Smoke failed on all three OSes with ERR_PNPM_OUTDATED_LOCKFILE: @types/hast@^3.0.5 was added to packages/web-shell/package.json and to package-lock.json, but pnpm-lock.yaml was never regenerated — the exact case AGENTS.md calls out about committing both lockfiles together. The follow-up commit c3338de1 adds the importer entry and the 3.0.5 resolution, and Install is now green on macOS, Ubuntu and Windows. I verified that commit's entire delta is pnpm-lock.yaml (+31/−21), so my read of the production code at 171514bd applies unchanged to the head below.

The same dependency addition then tripped the next generated-artifact gate: Lint & Static is red on stale NOTICES.txt (finding 1 above). Everything else has landed green, and the greens matter here — Test (ubuntu-latest) means the nine new unit cases actually pass in CI, and Capture web-shell visuals passing is the answer to the concern I raised in the gate about this switching on for every shared Markdown surface at once. web-shell E2E Smoke, which runs the new Playwright scenarios in CI, was still in flight when I read this.

Final CI results for c3338de (auto-updated by the triage finalize job after CI completed):

Check Conclusion
Lint & Static (ubuntu-latest, Node 22.x) ❌ failure
Capture web-shell visuals (ubuntu-latest, Node 22.x) ✅ success
Classify PR ✅ success
Desktop Shell (ubuntu-22.04) ✅ success
Desktop Shell (windows-2022) ✅ success
Install (macos-latest) ✅ success
Install (ubuntu-latest) ✅ success
Install (windows-latest) ✅ success
Integration Tests (no-AK, No Sandbox) ✅ success
macos-latest / Java 21 ✅ success
Real daemon E2E / Java 11 ✅ success
Test (ubuntu-latest, Node 22.x) ✅ success
ubuntu-latest / Java 11 ✅ success
ubuntu-latest / Java 17 ✅ success
ubuntu-latest / Java 21 ✅ success
web-shell E2E Smoke (ubuntu-latest, Node 22.x) ✅ success
windows-latest / Java 21 ✅ success

One row per check name (latest run); skipped checks omitted; failures sort first. / 每个检查名一行(取最新一次运行),省略 skipped,失败项排在最前。

Not verified, and I want to be explicit about it: no CI job runs the browser suite on Windows or macOS. Test (windows-latest) and Test (macos-latest) are skipped, and Desktop Shell (windows-2022) is a packaging check rather than the Playwright scenarios. So cross-platform browser behaviour rests on the author's local run, which the PR body states was macOS-only. The author's E2E report (their claim, not evidence I reproduced) describes a baseline-versus-change browser replay at exactly this commit, 225 focused unit tests, and clean build/typecheck/bundle.

Sandboxed verification would settle part of this: @qwen-code /verify — an A/B of the built shell against the base build, mock-free, for the grouping and return-link-isolation behaviour. Worth naming because the evidence for those claims is the spec this PR adds, which by construction cannot exist on base, so nothing yet compares the two builds directly. It does not close the Windows gap; no lane here does, and that stays a maintainer judgement call.

中文说明

代码审查

我本来准备好要在范围上提出异议,结果发现大部分工作做得很细致。功能代码本身没有缺陷。以下是我重点核查、原本可能出问题但没出问题的地方:

  • ref 转发。 本仓库的 PopoverContentReact.forwardRef 包装,因此 ref={content} 在 React 18 和 19 上都成立——这很关键,因为失焦关闭逻辑和键盘焦点路由都要读 content.currentPopoverTrigger asChild 包裹的是原生 DOM <button>Slot 无需转发。AGENTS.md 的 ref 规则满足。
  • 它调用的基础设施都真实存在。 Button size="icon-sm" 是有效变体;isSafeHrefisSafeImageSrcmarkdownUrlTransform 都从 Markdown.tsx 导出;useExternalLinkOpener 返回 (event, url: string | undefined),所以传入可能为 undefined 的 note.href 能通过类型检查;sourceComponents 在引用它的 rehypePlugins memo 之上已在作用域内。
  • DOM id 重复。 这是我原本预期会出 bug 的地方,结果没有。聚合会把一组引用的 id 全部映射到首个引用的命名空间 id,并把保留下来的锚点移入第一个 sup 的 children——照理会出现四个元素共用一个 id。实际不会,因为 FootnoteSup 在渲染卡片时丢弃了 children,交互模式下这些锚点根本不会进入 DOM;它们留在 hast 里只是为了让高级表格复制保留原始引用文本。单测里 new Set(ids).size === ids.length 跨两个文档断言了这一点。
  • 安全。 提取出的链接和图片在渲染前都经过既有 URL 策略,标题和摘要作为 React 文本子节点渲染,未引入原始 HTML。有一个测试喂进 javascript:alert%281%29data:image/svg+xml URI,断言两者都被丢弃而安全项保留。
  • 功能未触发时的影响面。 文档模式下 group 关闭,宿主自定义 sup 时也关闭;没有 footnoteCards 数据时组件渲染普通 <sup>。两个退出通道都有测试。未定义的 [^ref] 在定义流入前保持字面文本,同样有测试。

四处需要修改或考虑——第一处是阻塞项,其余不是:

1. 阻塞项:新增依赖后没有重新生成 NOTICES.txt 本提交上 Lint & Static 是红的,报错为 NOTICES.txt is out of dategit diff --stat 显示 packages/vscode-ide-companion/NOTICES.txt 有 27 行新增(完整日志见上方英文版代码块)。这和锁文件那次是同一个疏漏的下一步:新增 @types/hast 改变了依赖树,而 NOTICES.txt 是从依赖树生成的,因此会多出新的许可条目。c3338de1 重新生成了两个锁文件,但没有重新生成 notices。我确认这不是主干上已有的漂移——Lint & Static 在本 PR 所基于的同一个 main 提交 3733c8eb 上是绿的,而本 PR 是唯一改动依赖树的变更。紧邻的前一步检查(settings.schema.json)是通过的,所以漂移只发生在 notices 生成这一步。执行报错里给出的命令并提交即可解决。

2. cssUrlValue 已经存在。 FootnoteCard.tsxmaskImage: `url("${knowledgeIcon}")` 手工拼接图标遮罩,而且写了两遍。仓库里有 packages/web-shell/client/utils/cssUrlVar.ts,其 cssUrlValue(url) 生成的正是同样的 url("…") 字符串,并会转义 "\、CR、LF、FF——它就是干这个的。这里没有实际风险,因为 knowledgeIcon 是构建期打包的资源路径,所以这是规范问题而非安全问题:改成 maskImage: cssUrlValue(knowledgeIcon)。顺手把那两处完全相同的五行遮罩样式对象合成一个共享常量。

3. 两个锁文件现在不一致。 @types/hast 锁在 ^3.0.5,而树里已解析到 3.0.4,所以 npm 会在根目录 3.0.4 之外,于 packages/web-shell/node_modules/@types/hast@3.0.5 再装一份嵌套副本。pnpm 锁文件走了另一条路,把所有传递依赖统一提升到 3.0.5,彻底去掉了 3.0.4——这也是 notices 差异有 27 行而不是一个条目的原因。纯类型导入、构建时擦除,运行时不会出问题,但写 ^3.0.4 在 npm 下也能去重,两个锁文件就能描述同一棵树。

4. 门禁阶段已提过的两点,不再重复论证:卡片对单个交互来说是个庞大的状态机;定位脚注区块的整树遍历发生在提前返回之前,因此没有脚注的文档每次解析都要付一次完整遍历。两者都是 maintainer 的判断取舍,不是缺陷。

在读代码之前我自己的方案是:把相邻引用聚合成一个带数量的胶囊、用 useId() 给锚点加命名空间、把脚注链接走既有 URL 策略、用现有共享 Popover 展示来源——不做翻页,不做缩略图。在管线部分这个 PR 与我的方案完全一致,包括 useId() 命名空间和 ShadowRoot 感知的链接处理(这部分我大概得踩坑才能想到)。它在卡片本身上走得比我会做的远得多,这也是 maintainer 可能有理由要求做减法的地方。

(时序图见上方英文版,内容一致:hast 标注 → 组件分派 → 弹层交互 → 跨 ShadowRoot 的命名空间回链路由。)

测试证据

这是无人值守的 CI 运行,因此我没有构建或执行本 PR 的任何代码——下面的证据全部来自本 PR 自己的 CI,通过 API 读取所审提交的结果。

第一个提交是红的,作者在本次审查发布前修复了其中一部分。171514bd 上,pnpm Worktree Smoke 三个系统全部失败,报 ERR_PNPM_OUTDATED_LOCKFILE@types/hast@^3.0.5 加进了 packages/web-shell/package.jsonpackage-lock.json,但 pnpm-lock.yaml 没有重新生成——正是 AGENTS.md 强调"两个锁文件要一起提交"针对的情形。后续提交 c3338de1 补上了 importer 条目和 3.0.5 解析,现在 macOS、Ubuntu、Windows 的 Install 全绿。我核实过该提交的全部改动就是 pnpm-lock.yaml(+31/−21),因此我在 171514bd 上对生产代码的审查对下面的 head 完全适用。

同一次依赖新增接着绊倒了下一个生成物门禁:Lint & StaticNOTICES.txt 过期而变红(见上面第 1 条)。其余全部落定为绿,而且这些绿是有意义的——Test (ubuntu-latest) 通过说明九个新增单测用例在 CI 里确实跑过了;Capture web-shell visuals 通过则回答了我在门禁里提出的顾虑,即这个改动一次性对所有共用 Markdown 界面生效。我读取时,运行新增 Playwright 场景的 web-shell E2E Smoke 仍在进行。CI 明细见上方表格(表内为英文原始检查名与结论)。

明确说明未验证的部分:CI 里没有任何任务在 Windows 或 macOS 上运行浏览器套件。 Test (windows-latest)Test (macos-latest) 都是 skipped,而 Desktop Shell (windows-2022) 是打包检查,不是 Playwright 场景。所以跨平台浏览器行为依赖作者的本地运行,而 PR 正文说明该运行仅在 macOS 上完成。作者的 E2E 报告(属于作者的声明,不是我复现的证据)描述了在完全相同的提交上做的改动前/改动后浏览器回放、225 项定向单测,以及干净的 build/typecheck/bundle。

沙箱验证能坐实其中一部分:@qwen-code /verify —— 对构建产物与基线构建做 A/B、不使用 mock,验证聚合与回链隔离行为。之所以要点名,是因为这些结论目前的证据来自本 PR 新增的用例,而这些用例在基线上按定义并不存在,所以还没有任何东西直接对比过两个构建。它无法弥补 Windows 的缺口;这里没有通道能弥补,那部分留给 maintainer 判断。

Qwen Code · qwen3.8-max-2026-09-02

Reviewed at c3338de142f28152f3517fb1bab38f8c92191b35 · re-run with @qwen-code /triage

@qwen-code-ci-bot

qwen-code-ci-bot commented Sep 9, 2026

Copy link
Copy Markdown
Collaborator

🖼️ web-shell visual preview

Rendered against a mock daemon (no real backend): the PR base vs this PR head c68a32e. Only screenshots that changed are shown (flows below, if any, are head-only) — refreshes on every push.

Screenshots · before / after

ℹ️ No screenshot changed against the PR base — but this PR edits 15 render-shaping files:

  • packages/web-shell/client/App.tsx
  • packages/web-shell/client/assets/icons/knowledge.svg
  • packages/web-shell/client/components/MessageItem.tsx
  • packages/web-shell/client/components/MessageList.tsx
  • packages/web-shell/client/components/WebShellTranscript.tsx
  • packages/web-shell/client/components/messages/AssistantMessage.tsx
  • packages/web-shell/client/components/messages/FootnoteCard.tsx
  • packages/web-shell/client/components/messages/FootnotePreviewContent.tsx
  • …and 7 more.

Either the change has no visual effect (logic, plumbing, a state the scenarios never reach), or no scenario renders this UI — in which case the preview cannot see it, and an empty result is a coverage gap rather than a clean bill of health. To make it visible, add a scenario to packages/web-shell/client/e2e/visuals/screenshots.spec.ts that seeds whatever state the UI is gated on; it then appears here as a head-only (NEW) capture.

Full-resolution recordings (.webm) are attached to the workflow run.

Qwen Code · web-shell visuals

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Confidence: 2/5 — on the code alone I would be at 4; the score is low because a required gate is red from this PR's own dependency change, so it cannot merge as-is. One generated file needs regenerating and this is a different review.

Stepping back: this is the kind of PR that is easy to wave through and easy to reject, and I tried to do neither.

What convinced me the feature work is real rather than plausible-looking is the test suite. Nine unit cases and ten browser scenarios that pin behaviour I would otherwise have had to take on faith — that grouping stops at punctuation, cell and inline-code boundaries; that an unresolved reference stays literal until its definition streams in; that a selected source survives the document growing and falls back cleanly when it is removed; that two messages reusing the same footnote ids keep isolated return targets. Those are precisely the cases a careless implementation gets wrong, and they are the cases someone wrote down before shipping. The unsafe-URL test feeds it javascript: and data: URIs and asserts both are dropped, which tells me the security path was thought about rather than bolted on. Test (ubuntu-latest) is green, so those nine cases pass in CI and not just on the author's machine.

The duplicate-id question is the one I spent the most time on, because the AST rewrite really does map four nodes onto one namespaced id. It is safe only because FootnoteSup discards children when it renders the card — the retained anchors exist in the hast for table-copy fidelity and never reach the DOM. That invariant is non-obvious, and it is the kind of thing a future refactor silently breaks. Credit where due: the code comments the retained-children trick, and the id-uniqueness test would catch a regression.

I also want to record the concern I raised in the gate and then had answered for me. I flagged that this switches on for every shared interactive Markdown surface at once, so an unintended visual change somewhere else was the failure mode to watch. Capture web-shell visuals came back green. That was the right check for the right worry, and it is now closed rather than assumed.

What blocks it is one file, and it is mechanical. Lint & Static fails because packages/vscode-ide-companion/NOTICES.txt is stale — 27 lines of license notices missing. The chain is short and unambiguous: the PR adds @types/hast, that changes the dependency tree, NOTICES.txt is generated from that tree, and it was not regenerated. I checked this against main rather than assuming it: Lint & Static is green at 3733c8eb, the same base this PR sits on, and the adjacent settings.schema.json drift check in the very same job passed. So this is not pre-existing rot and not a flaky runner — it is this diff. npm run generate:notices --workspace=qwen-code-vscode-ide-companion and a commit clears it.

It is the same miss as the first commit's, one step further along the generated-artifact chain: package.json changed, package-lock.json and pnpm-lock.yaml were both regenerated in c3338de1 within about twenty minutes, and the third generated file was missed. Worth internalising as one rule rather than three fixes — when a dependency changes, everything derived from the tree moves: both lockfiles and the notices.

Where I still have a reservation is scope, and I want to state it plainly rather than bury it under the blocker. My own answer to this problem was a count pill over the existing shared Popover, no pagination and no thumbnails. The plumbing here matches that instinct exactly — useId() namespacing, existing URL policy, ShadowRoot-aware link handling — but the card goes considerably further: a 218-line hover/focus/touch state machine with delayed open, delayed close, per-page pagination, focus routing into the popover, and thumbnails that collapse on load failure. All of it is tested and all of it behaves. It is also more machinery than "read the source without jumping to the footer" strictly needs, and AGENTS.md puts simplicity first for a reason. If a maintainer's instinct is to ask for a smaller first cut, that is a legitimate ask and this PR would survive it well. It is not why I am requesting changes.

The second half of that reservation is the one I could not resolve, and it is a product question rather than a code question: I found nothing in packages/core or packages/cli that emits footnote markdown, so this activates only when a model chooses to write footnotes. I could not confirm from the repo how often that happens in practice. What makes this safe to land anyway is that the failure mode of being early is bounded and tested — no footnotes means no annotation means a plain <sup>, identical to today. The cost of guessing wrong is one extra tree walk per parse, not a regression.

Two small things I would fix while the branch is open, neither worth a round trip on its own: use the existing cssUrlValue helper for the mask URL instead of hand-building it twice, and consider ^3.0.4 for @types/hast so npm dedupes to one copy and the two lockfiles describe the same tree — which would also shrink the notices diff.

Verdict: request changes, for the stale NOTICES.txt and nothing else. web-shell E2E Smoke was still running when I wrote this; if it lands red for a reason traceable to this diff, that changes the picture and I would rather say so now than have it read as a clean bill. I did not run this PR's code at any point — it is an unattended CI pass, so everything behavioural above comes from reading the diff and from the PR's own CI, and the author's macOS-only local run is their claim, not evidence I reproduced.

中文说明

Confidence: 2/5 —— 单看代码我会给到 4 分;分数低是因为一个必需门禁因本 PR 自己的依赖改动而变红,所以现状无法合并。只要重新生成一个生成文件,这就是一次完全不同的审查。

退一步看整体:这种 PR 既容易被随手放过,也容易被一口否掉,我两边都尽量没做。

让我相信功能部分是真实工作、而不是"看起来像样"的,是测试套件。九个单测用例加十个浏览器场景,钉住了那些我本来只能选择相信的行为——聚合会在标点、单元格和行内代码边界处停止;未定义的引用在定义流入前保持字面文本;选中的来源在文档增长时保留、被移除时干净回退;两条复用相同脚注 id 的消息各自保有隔离的回链目标。这些恰恰是草率实现会做错的地方,也是有人在提交前就写下来的地方。不安全 URL 的测试喂进 javascript:data: URI 并断言两者都被丢弃,说明安全路径是事先想过的,而不是事后补的。Test (ubuntu-latest) 是绿的,所以这九个用例在 CI 里通过,而不只是在作者机器上通过。

我在重复 id 这个问题上花的时间最多,因为 AST 改写确实把四个节点映射到了同一个命名空间 id。它之所以安全,仅仅是因为 FootnoteSup 在渲染卡片时丢弃了 children——保留下来的锚点只存在于 hast 中以保证表格复制的保真度,从不进入 DOM。这个不变量并不显然,而且是未来重构会悄无声息破坏掉的那类东西。该给的肯定要给:代码给"保留 children"这个技巧写了注释,id 唯一性测试也能捕捉回归。

我还想记录一个我在门禁里提出、随后被事实回答了的顾虑。我当时指出这个改动一次性对所有共用的交互式 Markdown 界面生效,所以要盯的失败模式是别处出现非预期的视觉变化。Capture web-shell visuals 返回绿色。对的检查回答了对的担心,这一项现在是已关闭,而不是被假定没问题。

阻塞它的只有一个文件,而且是机械性的。 Lint & Static 失败是因为 packages/vscode-ide-companion/NOTICES.txt 过期——少了 27 行许可声明。因果链很短也很明确:PR 新增 @types/hast,依赖树随之改变,NOTICES.txt 由依赖树生成,而它没有被重新生成。我没有想当然,而是对照了 mainLint & Static 在本 PR 所基于的同一个提交 3733c8eb 上是绿的,而且同一个任务里紧邻的 settings.schema.json 漂移检查是通过的。所以这既不是主干已有的腐化,也不是 runner 抖动,就是这个 diff 造成的。执行 npm run generate:notices --workspace=qwen-code-vscode-ide-companion 并提交即可解决。

这和第一个提交的疏漏是同一个,只是沿生成物链条又往前走了一步:package.json 变了,package-lock.jsonpnpm-lock.yaml 在约二十分钟内由 c3338de1 双双重新生成,第三个生成文件被漏掉了。建议把它内化成一条规则而不是三次修补——依赖一变,所有由依赖树派生的东西都要动:两个锁文件,加上 notices。

我仍有保留意见的地方是范围,我想直说而不是把它埋在阻塞项下面。我自己对这个问题的答案是:在现有共享 Popover 上做一个带数量的胶囊,不做翻页,不做缩略图。这里的管线部分与我的直觉完全一致——useId() 命名空间、既有 URL 策略、ShadowRoot 感知的链接处理——但卡片走得远得多:218 行的 hover/focus/touch 状态机,包含延迟打开、延迟关闭、逐页翻页、焦点移入弹层,以及加载失败时收起的缩略图。这些都有测试、行为都正确。但相对于"不用跳到文末就能看来源"这个需求,它用到的机械结构确实更多,而 AGENTS.md 把简单优先列为最看重的原则是有道理的。如果 maintainer 的直觉是要求先做一个更小的版本,这是合理的诉求,而且这个 PR 经得起这样的裁剪。但这不是我要求修改的原因。

保留意见的另一半是我没能解决的,它是产品问题而不是代码问题:我在 packages/corepackages/cli 里没有找到任何生成脚注 Markdown 的代码,所以这个功能只在模型自己选择写脚注时才会激活。我无法从仓库确认实践中这发生的频率。让它仍然可以落地的原因是:提前布局的失败模式是有界且已被测试覆盖的——没有脚注就没有标注,渲染出的是普通 <sup>,与今天完全一致。判断错的代价是每次解析多一次树遍历,而不是回归。

两件我建议趁分支还开着就修掉的小事,单独看都不值得来回一轮:用现有的 cssUrlValue 辅助函数生成遮罩 URL,而不是手工拼接两遍;@types/hast 考虑写 ^3.0.4,这样 npm 会去重成一份,两个锁文件也能描述同一棵树——顺带还能让 notices 的差异变小。

结论:要求修改,只为过期的 NOTICES.txt,不涉及其他。我写这段时 web-shell E2E Smoke 仍在运行;如果它变红且原因可追溯到本 diff,那会改变整体判断,我宁愿现在就把话说清楚,也不想让这份结论被读成一张干净的健康证明。我全程没有执行本 PR 的代码——这是无人值守的 CI 审查,所以上面所有行为层面的结论都来自阅读 diff 和本 PR 自己的 CI,作者仅在 macOS 上的本地运行属于其本人声明,不是我复现的证据。

Qwen Code · qwen3.8-max-2026-09-02

Reviewed at c3338de142f28152f3517fb1bab38f8c92191b35 · re-run with @qwen-code /triage

@qwen-code-ci-bot qwen-code-ci-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One mechanical blocker, and nothing else — the feature work itself is clean and well tested. Full notes are in my review comment above.

Lint & Static is red because packages/vscode-ide-companion/NOTICES.txt is stale (27 missing lines). Adding @types/hast changes the dependency tree, and that file is generated from it:

npm run generate:notices --workspace=qwen-code-vscode-ide-companion

then commit the result. I checked this against main rather than assuming it: Lint & Static is green at 3733c8eb, the same base this PR sits on, and the adjacent settings.schema.json drift check in the same job passed — so it is this diff, not pre-existing rot.

It is the same miss as the first commit's, one step further along the chain: c3338de1 regenerated both lockfiles but not the notices. Worth treating as one rule — when a dependency changes, everything derived from the tree moves.

Two optional, non-blocking items while you are in there: use the existing cssUrlValue helper from client/utils/cssUrlVar.ts for the mask URL instead of hand-building url("…") twice in FootnoteCard.tsx; and consider ^3.0.4 for @types/hast so npm dedupes to a single copy and both lockfiles describe the same tree.

The scope question in my comment (the card is a 218-line state machine for one affordance) is a genuine question for a maintainer, not a reason for this review.

中文说明

只有一个机械性阻塞项,别无其他——功能部分本身干净且测试充分。完整意见见上方的审查评论。

Lint & Static 变红是因为 packages/vscode-ide-companion/NOTICES.txt 过期(缺 27 行)。新增 @types/hast 改变了依赖树,而该文件正是由依赖树生成的:

npm run generate:notices --workspace=qwen-code-vscode-ide-companion

然后提交结果。我没有想当然,而是对照了 mainLint & Static 在本 PR 所基于的同一个提交 3733c8eb 上是绿的,同一任务里紧邻的 settings.schema.json 漂移检查也通过了——所以是这个 diff 造成的,不是主干已有的腐化。

这和第一个提交的疏漏是同一个,只是沿链条又往前一步:c3338de1 重新生成了两个锁文件,但没有重新生成 notices。建议当成一条规则来处理——依赖一变,所有由依赖树派生的东西都要动。

顺手有两个可选、不阻塞的项:用 client/utils/cssUrlVar.ts 里现成的 cssUrlValue 辅助函数生成遮罩 URL,而不是在 FootnoteCard.tsx 里手工拼接两遍 url("…")@types/hast 考虑写 ^3.0.4,这样 npm 会去重成一份,两个锁文件也能描述同一棵树。

评论里那个范围问题(卡片为一个交互用了 218 行状态机)是给 maintainer 的真实提问,不是本次要求修改的原因。

Qwen Code · qwen3.8-max-2026-09-02

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Qwen Code review timed out. Qwen review timed out after 21600 seconds (of the 360-minute budget). This run already used the maximum 360 minute timeout. See workflow logs.

@callmeYe callmeYe changed the title feat(web-shell): add grouped footnote previews feat(web-shell): add source footnote citation cards Sep 10, 2026
@callmeYe
callmeYe dismissed qwen-code-ci-bot’s stale review September 10, 2026 11:21

Dismissed as stale after 58c8de6 regenerated NOTICES.txt for @types/hast and passed repeat generation, lockfile checks, build, typecheck, 230 focused tests, and source/production browser E2E. The review identified no other blocker.

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Qwen Code review did not complete successfully. The review pipeline failed before a review could be posted. A transient error is retried automatically; if you are seeing this, retry with @qwen-code /review. See workflow logs.

@callmeYe callmeYe changed the title feat(web-shell): add source footnote citation cards feat(web-shell): add grouped footnote previews and host icons Sep 11, 2026
@callmeYe

Copy link
Copy Markdown
Collaborator Author

E2E verification for commit 79752adbab42f3a87ef31723992d3084fee9d724 (macOS, Node 22, Chromium):

  • Focused unit tests: 274 passed across FootnoteCard (33), AssistantMessage (26), Markdown (85), and EnhancedMarkdownTable (130). The final test-fixture-only adjustment was followed by another 33/33 FootnoteCard run.
  • Development: 13/13 passed using the updated generic footnote fixture. After the final literal-ID fix, paragraph/table aggregation plus the host demo passed again (2/2).
  • Production: 11/11 applicable tests passed. The two source-only entrypoints (Shadow DOM harness and interactive host demo) were explicitly skipped in production and passed in development. The served JS and local build matched byte-for-byte.
  • Root npm run build, subsequent npm run typecheck, npm run bundle, focused ESLint and Prettier checks passed. Typecheck was rerun sequentially after build because the initial concurrent attempt encountered regenerated dependency declarations.

Verified behaviors: numeric/named/Chinese IDs, plain and multiline notes, whitespace grouping and ID deduplication, literal percent IDs staying distinct, full transformed definition Markdown, safe public payloads, separate inline/Assistant callback lists, independent/default/invalid/throwing icon callbacks, SVG resource loading and centering, scrollable complete descriptions, hover-only action footer, pinned pagination, keyboard/touch, streaming, message isolation, host link handoff, preserved navigation targets, Markdown copy and static document footnotes.

Reproduction from packages/web-shell:

npx vitest run client/components/messages/FootnoteCard.test.tsx client/components/messages/AssistantMessage.test.tsx client/components/messages/Markdown.test.ts client/components/messages/EnhancedMarkdownTable.test.tsx
PLAYWRIGHT_PORT=5187 npx playwright test client/e2e/web-shell.footnotes.spec.ts --config playwright.config.ts --project chromium --grep 'footnote source previews|source locator demo' --workers=1

For the production run, build first, start npx vite preview --host 127.0.0.1 --port 5188, then repeat the Playwright command with FOOTNOTE_BUILT=1 PLAYWRIGHT_PORT=5188. A live preview must be running so the Playwright development fallback is not used.

The demo uses deterministic locator fixtures and a mocked host panel. Live BFF knowledge retrieval and OpenCode's production URL builder are outside this verification.

中文说明

本次提交 79752adbab42f3a87ef31723992d3084fee9d724 已完成 macOS、Node 22、Chromium 验收:

  • 4 个文件共 274 项定向单测通过(FootnoteCard 33、AssistantMessage 26、Markdown 85、表格 130)。最后仅调整测试 fixture 后,FootnoteCard 33/33 再次通过。
  • 开发完整用例 13/13;最终脚注逻辑 ID 修正后,正文/表格聚合和宿主 Demo 2/2 再次通过。
  • 生产 11/11 适用场景通过;两个开发专用入口(Shadow DOM、交互 Demo)明确跳过且已在开发环境通过。HTTP JS 资产与本地构建逐字节一致。
  • 全仓构建、构建后的全仓类型检查、bundle、定向 ESLint 和 Prettier 通过。初次并行类型检查与依赖声明重新生成产生竞争,构建结束后已顺序重跑通过。

已验证:数字/命名/中文 ID,纯文本和多行脚注,空白聚合与 ID 去重,字面百分号 ID 不混淆,transform 后完整原文,公开数据不含内部节点,两个函数的完整列表及独立性,空值/无效/异常回退,SVG 加载与居中,长说明滚动,Hover 操作栏,分页保持打开,键盘/触屏,流式更新,消息隔离,宿主链接接管,未转换引用目标,Markdown 复制和静态脚注导出。

复现命令见上方。生产运行需先构建并在 5188 启动 vite preview,再设置 FOOTNOTE_BUILT=1 PLAYWRIGHT_PORT=5188,确保没有误走 Playwright 的开发服务器回退。

Demo 为确定性 locator fixture 和模拟宿主面板,不包含真实 BFF 检索或 OpenCode 生产 URL 拼接的验证。

@callmeYe

Copy link
Copy Markdown
Collaborator Author

Follow-up fix f4d3a8465c: the demo citation resource and Copy both had 14px CSS boxes, but their painted artwork differed. The original citation was 3.10px wider, 2.12px taller and its alpha-weighted visual center was 1.06px lower. The earlier element-box assertions did not detect this.

The demo now supplies a dedicated SVG with normalized padding, geometry and stroke weight. Public icon sizing and host callback behavior are unchanged. A new browser regression measures actual painted bounds and visual centroids from the DOM Copy SVG and the citation's resolved mask URL.

Verification: painted width/height differences are now 0.027/0.137 CSS px; bounding-box/visual-center differences are below 0.3px. The updated host demo E2E passed (1/1); both message-hover and direct button-hover screenshots were inspected. An isolated negative control serving the old SVG failed the same measurement check. Web Shell build/typecheck, root bundle, focused lint and formatting passed. This asset is a development-demo resource; production rendering code is unchanged.

中文说明

补充修复 f4d3a8465c:Demo 引用图标和复制图标虽然都使用 14px CSS 容器,实际图形却不同。旧引用图形宽 3.10px、高 2.12px,视觉重心低 1.06px,之前只检查元素盒的测试没有捕获问题。

Demo 现使用单独校正留白、几何尺寸和线宽的 SVG。公共图标尺寸与宿主回调行为不变。新增浏览器回归会从实际 DOM 复制 SVG 和引用 mask URL 测量实绘边界及视觉重心。

验证:实绘宽/高差已降为 0.027/0.137 CSS px,边界中心和视觉重心差均小于 0.3px;宿主 Demo E2E 1/1 通过,并人工查看了消息 Hover 与直接按钮 Hover 截图。独立负对照替换回旧 SVG 后,同一检查明确失败。Web Shell 构建/类型检查、根目录 bundle、定向 lint 和格式检查通过。该资源只用于开发 Demo,生产渲染代码未变。

@callmeYe callmeYe changed the title feat(web-shell): add grouped footnote previews and host icons feat(web-shell): add grouped footnote previews and host customization Sep 11, 2026
@callmeYe

Copy link
Copy Markdown
Collaborator Author

Current-page content slot verification for bb7a72257d60098e2cb038baa3e598f9bbc70809:

  • 288 focused unit tests passed, including 14 new slot tests covering public data, current page/full list consistency, stable updates during paging and report growth, close/unmount disposal, StrictMode, renderer replacement/return, null/undefined fallback, mount/update/dispose failures, invalid handles, host source-link interception, cross-message isolation and static export.
  • Development Chromium: 14/14 passed. This includes default cards, custom DOM current-page content, original icon alignment, stable pagination, keyboard/narrow viewport and switching back to defaults.
  • Production app Chromium: 11/11 applicable cases passed. Three development-only entries were explicitly skipped and covered separately.
  • Independent production host: 2/2 passed. Its build imported dist/transcript.js, with module-graph assertions excluding the source transcript component. HTTP JS bytes matched the generated assets. The native DOM host kept its mount identity while paging and opened the host panel through the Qwen-managed source link.
  • Synchronous layout probe passed: the actual mount callback receives a connected, visible container with width 328px. Initial mounting, retries after decline, renderer cycles and StrictMode replay are guarded by regression assertions.
  • Root build and subsequent root typecheck passed. After final corrections, Web Shell was rebuilt and typechecked again, followed by root bundle. Focused lint, formatting and diff checks passed. Final independent lifecycle review had no remaining findings.

Source unchanged during final browser acceptance. Earlier interrupted browser runs were caused by root build temporarily rebuilding SDK output. An exit-animation locator race was corrected by awaiting close before another interaction. The independent consumer initially inlined SVGs; its ignored build config was corrected to emit resource URLs, preserving Web Shell's existing SVG-data-URL policy.

Public API: markdown.mountFootnotePreview(container, info) returns { update(info), dispose() } or declines with null/undefined. It replaces only the current page's content; Qwen retains aggregation, trigger icons/counts, popup state/placement, keyboard behavior and pagination. Hosts place info.sourceLink into their layout to retain components.a; metadata remains free of React/HAST objects. Native DOM and SolidJS hosts do not need a React element factory. Hosts own cleanup when mounting throws before returning a handle and must handle their asynchronous failures.

中文说明

提交 bb7a72257d60098e2cb038baa3e598f9bbc70809 的当前页内容插槽已完成验证:

  • 288 项定向单测通过,其中 14 项插槽测试覆盖公开数据、当前页/完整列表、翻页与报告增长时复用视图、关闭/卸载清理、StrictMode、替换渲染器再切回、空值回退、挂载/更新/清理失败、无效句柄、来源接管、消息隔离及静态导出。
  • 开发 Chromium 14/14 通过,覆盖默认内容、原生 DOM 自定义内容、图标对齐、稳定分页、键盘/窄屏和切回默认。
  • 生产应用 Chromium 11/11 适用用例通过;三个开发专用入口明确跳过并单独验证。
  • 独立生产宿主 2/2 通过。模块图断言直接导入 dist/transcript.js 并排除源码组件,HTTP JS 与构建产物字节一致。原生 DOM 宿主翻页时保持同一挂载实例,并通过 Qwen 管理的来源链接打开宿主面板。
  • 同步布局探针通过:真实挂载回调取得已连接、可见、宽 328px 的容器;首次挂载、拒绝后重试、渲染器往返切换和 StrictMode 重放均有回归断言。
  • 全仓构建及随后全仓类型检查通过。最终修正后重新构建并检查 Web Shell 类型,再执行根目录 bundle。定向 lint、格式和差异检查通过,最后独立生命周期审查无剩余问题。

最终浏览器验收期间源码保持不变。早期中断来自全仓构建重建 SDK 产物;退出动画导致的定位器冲突通过等待关闭后再切换交互修正。独立宿主构建首次内联 SVG,随后仅修正其忽略目录内的构建配置以输出资源 URL,没有放宽 Web Shell 的 SVG data URL 策略。

公开接口为 markdown.mountFootnotePreview(container, info),返回 { update(info), dispose() },或返回 null/undefined 使用默认内容。只替换当前页内容,聚合、图标/数量、浮层状态/定位、键盘及分页继续由 Qwen 管理。宿主将 info.sourceLink 放入布局即可保留 components.a;元数据不含 React/HAST 对象。原生 DOM 和 SolidJS 接入无须创建 React 元素。挂载尚未返回句柄就抛错时由宿主清理已有资源,异步失败也由宿主处理。

@callmeYe callmeYe changed the title feat(web-shell): add grouped footnote previews and host customization feat(web-shell): add footnote previews and per-turn sources Sep 11, 2026
@callmeYe

Copy link
Copy Markdown
Collaborator Author

E2E report for 12a9a52058c0e2d38cb4363fbc3f98bd306ea00e (macOS, Chromium):

  • Development browser suite: 18/18 passed.
  • Built production application: 14/14 applicable cases passed; four explicitly source-only cases were skipped here and covered by development or the compiled host run.
  • Separately compiled transcript consumer: 3/3 passed. Module-graph inspection confirms it imports the built transcript export rather than its source component. Served app/consumer assets match the files on disk.
  • Focused unit/DOM tests: 324 passed (261 footnote/source/message tests, 7 existing source-preview tests, 56 App source/attachment tests).
  • Root build, full workspace and integration typecheck, bundle, changed-file ESLint and Prettier checks passed. After replacing an E2E selector that Tailwind misinterpreted, Web Shell and static-export artifacts were rebuilt and Web Shell typecheck/bundle passed again; the introduced CSS warning is gone.

Verified behavior: source totals are independent from footnotes; a previously registered source counts in both explicitly associated turns; deleting associated sources updates the open list and count without borrowing unrelated sources; asynchronous inventories update without an extra click. An attachment-only daemon shows the first turn's attachment and later reused/new attachments with the environment panel closed. Source rows use the existing preview; body footnote grouping, pagination persistence, custom DOM content lifecycle, host links, icon geometry, keyboard/touch, streaming and isolated message previews remain covered.

The demo deliberately has 3 footnotes / 4 current-turn sources / 5 session sources. Screenshots were manually checked for footer icon alignment and list layout. Browser fixtures control daemon events and source inventories; this does not claim a live BFF/knowledge-retrieval integration test or new Core source-use tracking.

Two clean source-review passes completed after the async hydration and attachment-loading fixes. No Core/daemon protocol or dependency changes are included. Latest-main merge-tree check is clean.


中文验收:开发版 18/18、生产应用 14/14(4 项源码专用场景明确跳过)、独立编译宿主 3/3,定向单元/DOM 测试 324 项通过;完整构建、全仓及集成类型检查、打包和格式/lint 检查通过。生产资源已核对 HTTP 与磁盘内容一致,宿主实际消费编译后的 transcript 导出。

已验证本轮来源与脚注数量独立、旧来源跨轮复用、删除后实时更新、异步加载无需点击、环境面板关闭时附件来源仍显示与刷新,以及现有来源预览、正文分页/宿主内容插槽/键盘触屏/流式与跨消息隔离。Demo 为 3 个脚注、4 个本轮来源、5 个会话来源,截图已检查图标对齐。浏览器使用可控 daemon 与来源数据,不将结果描述为真实 BFF/知识检索端到端验证,也未新增 Core 来源使用追踪协议。修复后完成两轮无新发现审查,与最新 main 的合并检查无冲突。

@callmeYe

Copy link
Copy Markdown
Collaborator Author

Post-merge validation for 03bcd18f9e5f49e3d398882de76081a145f10c89:

The first CI attempt stopped at the lint-gate freshness guard because main had updated its filename rules. Main was merged normally in e373d24777, without conflicts; the current lint-gate commit is now an ancestor of this branch. Both clean merge audits confirmed our Sources/footnote behavior and main's source-panel, preview, notification and timing changes are preserved.

  • Fresh npm ci, root build, full workspace/integration typecheck and bundle passed.
  • Focused tests: 335 passed (279 component/selector/preview cases plus 56 App source/attachment cases).
  • PR-file ESLint and supported-file Prettier checks passed; worktree is clean.
  • Development browser 18/18, built application 14/14 applicable (+4 explicit source-only skips), compiled transcript consumer 3/3 passed again after merging main.

Main now sends a same-origin image CSP in development. The thumbnail fixture was moved to a same-origin intercepted test asset so both successful loading and failed-image fallback are tested under that policy. No product/CSP policy was weakened. An old Vite optimizer process was restarted after the dependency installation. The final app and compiled-consumer HTTP assets were checked against disk; test artifacts and screenshots from both pre-merge and post-merge runs are preserved locally.

中文:已正常合并 main,解决首次 CI 的 lint 规则过旧阻塞,无合并冲突。合并后的全仓构建、类型检查、打包和 335 项定向测试通过,开发 E2E 18/18、生产应用 14/14(4 项明确跳过)、独立编译宿主 3/3 再次通过。上游新增同源图片 CSP 后,仅将测试样图调整为同源资源,没有降低产品安全策略;依赖同步后已重启旧 Vite 优化器。当前提交为上述精确 SHA,新一轮 GitHub CI/评审独立跟进。

@qwen-code-ci-bot qwen-code-ci-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Partially reviewed — gaps disclosed.

1 Suggestion-level finding(s) this review confirmed are already reported on this PR and are not repeated:

  • R1-19 @types/hast version range in packages/web-shell/package.json (anchor package-lock.json:31124) — already reported (issue comment 5602755982, non-blocking item 3)

Not explored to full depth (tool budget reached): chunk 17: none of these e2e specs were executed (they need a vite dev server plus Playwright browsers); every claim above is from reading the spec against the component, …; "agent reverse-audit (round 2)": did not execute the shadow test under a live Playwright dev server, so the timeout is argued from the accessible-name and menu-item evidence rather than observe…; chunk 13: tracing how core's llmContent for record_source is mapped by the ACP bridge into the daemon transcript block ( content text vs rawOutput ) — the first fin…; "agent reverse-audit (round 1)": the doc's number: number = "Footnote number in first-reference order" — I could not confirm the footnote <li> order the numbering is read from ( rehype-foot…; "agent reverse-audit (round 1)": the doc's "Stable component identities preserve open cards through streaming updates" — I did not trace React reconciliation of the sup / section components a…, and 6 more.

Not reviewed: reverse audit — stopped before round 3 by the review time budget.

中文说明

仅完成部分审查,审查缺口已披露。

本轮确认的 1 条建议级发现已在 PR 上报告过,不再重复发布(列表见上方英文部分)。

未探索到全部深度(达到工具调用预算):chunk 17:none of these e2e specs were executed (they need a vite dev server plus Playwright browsers); every claim above is from reading the spec against the component, …"agent reverse-audit (round 2)"did not execute the shadow test under a live Playwright dev server, so the timeout is argued from the accessible-name and menu-item evidence rather than observe…;chunk 13:tracing how core's llmContent for record_source is mapped by the ACP bridge into the daemon transcript block ( content text vs rawOutput ) — the first fin…"agent reverse-audit (round 1)"the doc's number: number = "Footnote number in first-reference order" — I could not confirm the footnote <li> order the numbering is read from ( rehype-foot…"agent reverse-audit (round 1)"the doc's "Stable component identities preserve open cards through streaming updates" — I did not trace React reconciliation of the sup / section components a…,另有 6 条。

未审查:反向审计——评审时间预算不足,未能开始第 3 轮。

— qwen3.8-max via Qwen Code /review (v0.23.3)

Comment thread packages/web-shell/client/components/sources/TurnSources.tsx
Comment thread packages/web-shell/client/e2e/web-shell.footnotes.spec.ts
Comment thread docs/design/markdown-footnote-cards.md
Comment thread packages/web-shell/client/App.tsx
Comment thread packages/web-shell/client/App.tsx
Comment thread packages/web-shell/client/e2e/web-shell.turn-sources.spec.ts
Comment thread packages/web-shell/client/e2e/web-shell.turn-sources.spec.ts
Comment thread packages/web-shell/client/e2e/web-shell.turn-sources.spec.ts
Comment thread packages/web-shell/client/i18n.tsx
Comment thread pnpm-lock.yaml
@callmeYe

Copy link
Copy Markdown
Collaborator Author

Verification for c68a32ee4a124580392e55d8f61b7d6fcb2ada01 after the scoped review fixes:

  • Production fixes are limited to two files: restore source activation focus (including shadow portals, while respecting host-directed focus), preserve clicked popovers when clicking does not focus, remove the new source-list overflow, and retain nested footnote definitions/forward paths.
  • 342 focused unit/DOM tests passed (283 component/selector/preview tests +59 App source/attachment tests).
  • 19/19 development E2E, 15/15 production App E2E (+4 explicit source-only skips), and 3/3 separately compiled transcript-consumer E2E passed. Served app/consumer bytes match disk; the consumer loads the built transcript export. Additional actual-browser probes cover the focus transitions and nested paths.
  • Root install/build/bundle, full workspace/integration typecheck, changed-file ESLint and Prettier passed. Main was synchronized to satisfy its changed lint gate. The PR-specific test fixtures now intercept GET /workspace/models from the new upstream UI; no global harness/product change or console-error suppression was used.

Observed before/after: activating a source previously left focus on BODY and the next Tab reached New task; it now returns to the source trigger, including in a shadow portal. A host that explicitly focuses its preview keeps that focus. Hover-only closing and clicking an external textbox retain their existing focus behavior. A focus-neutral trigger click now remains open after pointer leave and resets after dismissal. Scroll width changed from334/client328 to340/client340. Nested notes retain both definitions and all forward paths, while flat notes still aggregate and paginate.

The ordinary nested-link external opener was also reproduced with the exact merge-base Markdown component and was deliberately left unchanged. The Shadow DOM selector Critical was disproved by the actual accessible-name tree and an unchanged passing test. Readonly source content remains accessible via Tab and PageDown; no unrelated keyboard redesign was added.

Regression tests for ordinary/shadow activation focus and nested definitions were observed failing before their corresponding corrections, then passing. Two clean scoped source audits completed after the shadow-focus correction. Worktree is clean and pushes are normal (no force push).

@callmeYe

Copy link
Copy Markdown
Collaborator Author

Scoped review dispositions

The user explicitly requires that this follow-up fix only defects introduced by this PR or necessary compatibility consequences, and forbids opportunistic fixes to pre-existing behavior. The production patch is confined to two files; accompanying tests and bilingual documentation cover those changes. Main was synchronized to incorporate its current lint gate; the PR tests also provide a GET-only model-list fixture for the new upstream request, without changing the global harness or production code.

Fix confirmed PR regressions

R1-33, R1-3, R1-35, R1-4. Source activation focus (including shadow portals), focus-neutral click persistence, source-list overflow, and nested-definition forward paths. Targeted tests were red before the fixes and browser probes verify the repaired behavior.

Not reproduced as reported

R1-34, R1-38. The unchanged Shadow DOM scenario passes; the composed accessible name selects the session row. The readonly source list is reachable with Tab and scrolls with PageDown; changing ArrowDown behavior is not necessary for this fix.

Exclude pre-existing behavior

R1-2. The same outer desktop opener fires with the exact merge-base Markdown component. Per the user instruction, ordinary link opening is not modified.

Retain the accepted contract; defer API or contract refinements

R1-1, R1-6, R1-8, R1-18, R1-57, R1-60, R1-66. The turn footer is independent of the environment-panel layout; host source identity and link resolution remain explicit. New global opt-outs, sentinel/relative-URL heuristics, shared Core protocols, API shape changes and summary semantics are outside this regression patch. Extra public-type documentation can be addressed separately; the existing bilingual design documents the identity requirements.

Defer performance changes

R1-5, R1-54, R1-55, R1-67. These are cache/identity/allocation or unused extraction-work improvements, not a demonstrated new correctness failure in this run. No cache refactor or document-mode architecture change is included.

Defer presentation/accessibility refinements

R1-7, R1-56, R1-61, R1-62, R1-64, R1-65, R1-37. These propose additional readonly-row, hover, tooltip, label or announcement behavior. They are non-blocking refinements and require their own validation; this patch retains the accepted UI apart from the reproduced new defects.

Retain for further investigation, without claiming fixed

R1-70, R1-71, R1-68. Advanced-table clone IDs, advanced-mode/custom-component remounting and custom demo reparenting need separate focused investigation. They were not promoted to Critical or treated as verified fixes, and no broad table/render/host-lifecycle changes are included.

Defer additional test coverage

R1-13, R1-15, R1-58, R1-14, R1-16, R1-42, R1-43, R1-39, R1-47, R1-23, R1-24, R1-52, R1-50, R1-51, R1-9, R1-53. These request additional cases or stronger witnesses, rather than identifying broken production guards in the current patch. Only regression coverage necessary for the confirmed fixes is added; no claim is made that existing tests detect every proposed mutation.

Defer fixture, documentation and cleanup changes

R1-12, R1-11, R1-36, R1-41, R1-44, R1-45, R1-22, R1-25, R1-10, R1-20, R1-21. These concern optional simplification, test/demo typing, example wording, runner documentation, unused labels/helpers or dependency resolution cleanup. The local production runner explicitly supplies FOOTNOTE_BUILT, and dependency ranges are validated by frozen-install CI. No dependency deduplication, broad fixture cleanup or unrelated code removal is included.

Closing non-blocking threads records these explicit dispositions; it does not claim deferred work has been implemented. The blocking focus fix has now been pushed and verified at c68a32ee4a124580392e55d8f61b7d6fcb2ada01. The old formal review will be reconsidered against that exact head, without bypassing a current human objection or required approval.

@callmeYe
callmeYe dismissed qwen-code-ci-bot’s stale review September 14, 2026 03:40

Superseded by c68a32e: R1-33 fixed and verified in ordinary/shadow portals; R1-34 disproved by exact-head browser/ARIA evidence. Other findings have explicit non-blocking scoped dispositions (issuecomment-5658612838); no unresolved threads or live human objections remain. This removes the stale blocker, not a fresh approval; CI and required reviews remain in force.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants