Skip to content

feat(web-shell): add browser tab favicon - #6091

Merged
wenshao merged 1 commit into
QwenLM:mainfrom
wenshao:feat/web-shell-favicon
Jul 1, 2026
Merged

feat(web-shell): add browser tab favicon#6091
wenshao merged 1 commit into
QwenLM:mainfrom
wenshao:feat/web-shell-favicon

Conversation

@wenshao

@wenshao wenshao commented Jun 30, 2026

Copy link
Copy Markdown
Collaborator

What this PR does

Adds a browser-tab favicon to the Web Shell. The HTML shell (packages/web-shell/client/index.html) previously had a <title> but no <link rel="icon">, so the daemon-served Web Shell tab fell back to the browser's generic page glyph. This inlines the Qwen mark as a data: URI so the tab now shows the brand icon.

The icon is inlined rather than shipped as a /favicon.svg file on purpose: the daemon's static server (packages/cli/src/serve/web-shell-static.ts) only exposes GET /assets/* and GET /, so a file at the dist root would never be reachable without also adding a new served route. The encoding (encodeURIComponent(svg)) and artwork match the existing packages/web-templates/src/export-html favicon, and the data: URI is already permitted by the shell's CSP (img-src 'self' data:). The #6D44E8 purple brand fill stays legible on both light and dark browser tab bars.

Why it's needed

A missing favicon makes the Web Shell tab look unfinished and hard to pick out among many open tabs, and because there is no icon link the browser fires a default GET /favicon.ico probe that the daemon can only answer with a 404 (that path matches neither /assets/* nor the exact / route). The repo already carries this exact Qwen mark as the favicon for the desktop webui and the HTML export, so this just brings the Web Shell in line with the rest of the product at near-zero cost.

Reviewer Test Plan

How to verify

  1. cd packages/web-shell && npx vite build — the build succeeds and the inlined <link rel="icon"> is copied verbatim into dist/index.html (Vite leaves data: URIs untouched).
  2. Decode the href and confirm it is the brand mark byte-for-byte:
    node -e 'const fs=require("fs");const h=fs.readFileSync("packages/web-shell/client/index.html","utf8");const u=h.match(/href="(data:image\/svg\+xml,[^"]*)"/)[1];const d=decodeURIComponent(u.replace(/^data:image\/svg\+xml,/,""));const o=fs.readFileSync("packages/desktop/apps/webui/src/public/favicon.svg","utf8").trim();console.log("round-trips to brand SVG:", d===o)'
    # -> round-trips to brand SVG: true
    
  3. Run the Web Shell suite: npx vitest run → 540/540 pass (incl. index.test.tsx).
  4. End-to-end: qwen serve --web, open the printed URL, and confirm the browser tab shows the purple Qwen mark instead of the generic page glyph.

Evidence (Before & After)

A browser-tab favicon lives in browser chrome, not the page viewport, so it cannot be captured by a headless/terminal screenshot. The verifiable artifact evidence:

Before — no icon link in index.html:

<title>Qwen Code Web chat</title>
</head>

Browser shows the generic page glyph; the default GET /favicon.ico probe has no daemon route and 404s.

After — inline data: URI favicon in the built dist/index.html:

<title>Qwen Code Web chat</title>
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,%3Csvg%20...fill%3D%22%236D44E8%22...%3C%2Fsvg%3E" />

Decodes byte-for-byte to the shipping Qwen mark (packages/desktop/apps/webui/src/public/favicon.svg); no extra request, CSP-permitted, no more /favicon.ico probe.

Tested on

OS Status
🍏 macOS
🪟 Windows ⚠️
🐧 Linux ⚠️

Change is a platform-independent static HTML string served by the same Express handler on every OS, so the macOS verification generalizes; Windows/Linux not run locally.

Environment (optional)

npx vite build + npx vitest run in packages/web-shell on macOS (Node from the repo toolchain).

Risk & Scope

  • Main risk or tradeoff: none of consequence — adds one static <link> to the HTML shell. The ~1.2 KB inline data: URI is the same trade-off the HTML export already makes.
  • Not validated / out of scope: no real-browser tab screenshot (favicon renders in browser chrome, not capturable headlessly); no change to the daemon static routes or CSP (the data: URI already fits img-src 'self' data:).
  • Breaking changes / migration notes: none.

Linked Issues

None.

中文说明

这个 PR 做了什么

给 Web Shell 加上浏览器标签页图标(favicon)。HTML 外壳(packages/web-shell/client/index.html)此前只有 <title>、没有 <link rel="icon">,所以 daemon 提供的 Web Shell 标签页只能显示浏览器默认的通用页面图标。这里把 Qwen logo 以 data: URI 内联进去,标签页现在会显示品牌图标。

之所以内联而不是放一个 /favicon.svg 文件:daemon 的静态服务器(packages/cli/src/serve/web-shell-static.ts)只暴露 GET /assets/*GET /,放在 dist 根目录的文件不额外加路由根本访问不到。编码方式(encodeURIComponent(svg))和图案与现有的 packages/web-templates/src/export-html favicon 一致,而 data: URI 已被外壳的 CSP(img-src 'self' data:)放行。紫色 #6D44E8 品牌填充色在浅色和深色标签栏上都清晰可辨。

为什么需要

缺少 favicon 会让 Web Shell 标签页显得没做完,多标签时也不好辨认;而且因为没有 icon link,浏览器会自动发一个默认的 GET /favicon.ico 探测请求,daemon 只能回 404(该路径既不匹配 /assets/* 也不匹配精确的 /)。仓库里 desktop webui 和 HTML 导出已经在用这枚 Qwen logo 作 favicon,这个改动只是让 Web Shell 与产品其余部分对齐,成本几乎为零。

评审验证计划

如何验证

  1. cd packages/web-shell && npx vite build —— 构建成功,内联的 <link rel="icon"> 会被原样拷进 dist/index.html(Vite 不会改写 data: URI)。
  2. 解码 href,确认与品牌图标逐字节一致(命令见上方英文)→ 输出 round-trips to brand SVG: true
  3. 跑 Web Shell 测试:npx vitest run → 540/540 通过(含 index.test.tsx)。
  4. 端到端:qwen serve --web,打开打印出的 URL,确认浏览器标签显示紫色 Qwen logo 而非通用图标。

证据(前后对比)

标签页 favicon 显示在浏览器**外框(chrome)**里、不在页面视口内,无法用无头/终端截图捕获。可核验的产物证据:

Beforeindex.html 无 icon link,浏览器显示通用图标,默认 GET /favicon.ico 探测无路由 → 404。
After:构建后的 dist/index.html 含内联 data: URI favicon,解码后与仓库现有 Qwen logo(packages/desktop/apps/webui/src/public/favicon.svg)逐字节一致;不产生额外请求,CSP 已放行,也不再有 /favicon.ico 探测。

测试平台

仅在 macOS 本地跑了 vite build + vitest。改动是各平台一致、由同一个 Express handler 以字符串形式返回的静态 HTML,因此 macOS 的验证可推广;Windows/Linux 未本地运行。

风险与范围

  • 主要风险/取舍:基本没有 —— 只是往 HTML 外壳加一个静态 <link>。约 1.2 KB 的内联 data: URI 与 HTML 导出已有的取舍相同。
  • 未验证/范围外:没有真实浏览器标签截图(favicon 渲染在浏览器外框、无头无法截);未改 daemon 静态路由或 CSP(data: URI 已符合 img-src 'self' data:)。
  • 破坏性改动/迁移说明:无。

关联 Issue

无。

image

@qwen-code-ci-bot

qwen-code-ci-bot commented Jun 30, 2026

Copy link
Copy Markdown
Collaborator

Thanks for the PR, @wenshao!

Template looks good ✓

On direction: this is a small, well-scoped polish fix — the Web Shell tab shows a generic browser glyph and fires a useless GET /favicon.ico probe that 404s. Adding a favicon brings the Web Shell in line with the desktop webui and HTML export, which already carry the same Qwen mark. No direct Claude Code CHANGELOG reference, but favicon polish is a standard product-detail concern and the motivation is clear.

On approach: the scope is exactly right — one <link> tag with an inlined data: URI, no new routes or files. The reasoning for inlining (daemon only serves /assets/* and /) is sound, and the technique mirrors the existing packages/web-templates/src/export-html/build.mjs pattern. Nothing to cut, nothing extraneous.

Moving on to code review. 🔍

中文说明

感谢贡献!

模板完整 ✓

方向:这是一个小而精准的打磨修复——Web Shell 标签页目前显示浏览器通用图标,还会触发一个无用的 GET /favicon.ico 探测请求导致 404。加上 favicon 让 Web Shell 与 desktop webui 和 HTML 导出保持一致(它们已在使用同一枚 Qwen logo)。Claude Code CHANGELOG 中没有直接参考,但 favicon 打磨是标准的产品细节关注点,动机清晰。

方案:范围恰好——一个 <link> 标签加内联 data: URI,没有新路由或文件。内联的理由(daemon 只提供 /assets/*/)合理,手法与现有 packages/web-templates/src/export-html/build.mjs 模式一致。没有需要砍掉的部分,也没有多余内容。

进入代码审查 🔍

Qwen Code · qwen3.7-max

@qwen-code-ci-bot

qwen-code-ci-bot commented Jun 30, 2026

Copy link
Copy Markdown
Collaborator

Code Review

Independent proposal: add a <link rel="icon"> with an inline data: URI to packages/web-shell/client/index.html, since the daemon's static server only serves /assets/* and / — a separate favicon file would need a new route. Reuse the existing Qwen mark SVG from packages/desktop/apps/webui/src/public/favicon.svg.

The PR matches this exactly. One <link> tag, the correct SVG, proper encodeURIComponent encoding, and a well-reasoned comment explaining the design choice. No bugs, no security issues, no AGENTS.md violations.

Minor observation: the HTML comment block is quite long (~10 lines) for a single <link> tag. It's informative and references the relevant source files, so it's not a blocker — just noting that a shorter comment would suffice.

Testing

Favicon renders in browser chrome (tab bar), not the page viewport — can't be captured by terminal/headless screenshots. Verified via build artifact inspection and unit tests instead:

=== PR #6091 Favicon Verification ===

1. Source index.html contains favicon link:
1

2. SVG data URI round-trip check:
  round-trips to brand SVG: true

3. Built dist/index.html contains favicon:
1

4. CSP permits data: URIs (img-src):
  "img-src 'self' data: blob:",

5. Static routes (no /favicon.ico handler):
  No favicon.ico route found (expected — confirms 404 gap)

6. Unit tests: 567 passed, 5 pre-existing failures (build-artifact.test.ts — same on main, unrelated to this PR)

All checks green. The favicon data URI decodes byte-for-byte to the shipping Qwen mark, Vite preserves it in the built output, and the CSP already permits data: in img-src.

中文说明

代码审查

独立方案:在 packages/web-shell/client/index.html 中添加一个带内联 data: URI 的 <link rel="icon">,因为 daemon 的静态服务器只提供 /assets/*/——单独的 favicon 文件需要新路由。复用 packages/desktop/apps/webui/src/public/favicon.svg 中现有的 Qwen logo SVG。

PR 与方案完全一致。一个 <link> 标签、正确的 SVG、正确的 encodeURIComponent 编码,以及一段解释设计选择的注释。没有 bug、没有安全问题、没有违反 AGENTS.md。

小观察:HTML 注释块对一个 <link> 标签来说有点长(约 10 行)。内容信息丰富且引用了相关源文件,不是阻断性问题——只是提一下更短的注释也够用。

测试

Favicon 渲染在浏览器外框(标签栏),不在页面视口内——无法通过终端/无头截图捕获。改为通过构建产物检查和单元测试验证:favicon data URI 逐字节解码为现有的 Qwen logo,Vite 在构建输出中保留了它,CSP 已在 img-src 中放行 data:

Qwen Code · qwen3.7-max

@qwen-code-ci-bot

qwen-code-ci-bot commented Jun 30, 2026

Copy link
Copy Markdown
Collaborator

This is about as clean as it gets. One file, 16 lines added, solves a real cosmetic gap — the Web Shell tab showed a generic browser glyph and fired a useless GET /favicon.ico that 404'd. The approach matches what I'd independently propose: inline the existing Qwen mark as a data: URI (same technique already used by web-templates/src/export-html), no new routes or files needed.

The build and verification confirm everything: SVG round-trips byte-for-byte, Vite preserves it in dist/index.html, CSP already permits data: in img-src, and all 567 relevant unit tests pass. The 5 build-artifact.test.ts failures are pre-existing on main and unrelated.

The HTML comment block is a touch long for a single <link> tag, but it's genuinely informative (explains the routing constraint, references the source files, notes CSP compatibility). Not worth asking to trim.

LGTM. ✅

中文说明

这个 PR 非常干净。一个文件,加了 16 行,解决了一个实际的细节缺口——Web Shell 标签页显示浏览器通用图标,还会触发一个无用的 GET /favicon.ico 404 请求。方案与我独立设想的一致:内联现有 Qwen logo 为 data: URI(与 web-templates/src/export-html 已有的技术相同),不需要新路由或文件。

构建和验证确认一切正常:SVG 逐字节还原,Vite 在 dist/index.html 中保留了它,CSP 已在 img-src 中放行 data:,567 个相关单元测试全部通过。5 个 build-artifact.test.ts 失败在 main 分支上预先存在,与本 PR 无关。

HTML 注释块对一个 <link> 标签来说稍长,但确实信息丰富(解释了路由限制、引用了源文件、注明了 CSP 兼容性)。不值得要求缩减。

LGTM. ✅

Qwen Code · qwen3.7-max

@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.

LGTM, looks ready to ship. ✅

Comment thread packages/web-shell/client/index.html
Comment thread packages/web-shell/client/index.html
The Web Shell HTML shell shipped without a <link rel="icon">, so the
browser tab fell back to the generic page glyph and every load fired a
404 for /favicon.ico (the daemon static server only exposes /assets/*
and /, so a dist-root favicon file is unreachable).

Inline the Qwen mark as a data: URI in index.html instead of adding a
file + a new served route. The encoding mirrors
packages/web-templates/src/export-html (encodeURIComponent(svg)) and the
data: URI is already permitted by the shell CSP (img-src 'self' data:).
The purple #6D44E8 brand fill stays legible on both light and dark
browser tab bars.
@github-actions

github-actions Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Please do not rebase or force-push to an active PR as it invalidates existing review comments. Note for future reference, the bots always squash all changes into a single commit automatically as part of the integration.

中文

请勿对活跃的 PR 执行 rebase 或 force-push,因为这会使已有的评审评论失效。另外,供日后参考:作为集成流程的一部分,机器人始终会自动将所有改动压缩(squash)为单个提交。

Comment thread packages/web-shell/client/index.html
@wenshao

wenshao commented Jul 1, 2026

Copy link
Copy Markdown
Collaborator Author

Noted on the force-push — my mistake; I amended to keep a single commit, but since integration auto-squashes that was unnecessary. I'll append commits from here on rather than rewrite history.

For reassurance: nothing was lost by the amend — all three review threads re-anchored to the current commit 371d16066 and are addressed/resolved (the favicon fix is unchanged; the one code edit since was the comment-attribution correction). Current state: 1 file, +16/-0, mergeable: true.

中文

force-push 这点收到了 —— 是我的疏忽;我 amend 是想保持单个提交,但既然集成会自动 squash,这一步没必要。之后我会追加提交、不再改写历史。

补充说明:这次 amend 没有丢失任何东西 —— 三条评审 thread 都重新锚定到当前提交 371d16066 并已处理/resolve(favicon 本身未变;期间唯一的代码改动是注释来源归属的更正)。当前状态:1 文件、+16/-0mergeable: true

@wenshao

wenshao commented Jul 1, 2026

Copy link
Copy Markdown
Collaborator Author

@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.

LGTM, looks ready to ship. ✅

@wenshao
wenshao added this pull request to the merge queue Jul 1, 2026
Merged via the queue into QwenLM:main with commit c9e1546 Jul 1, 2026
61 checks passed
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.

4 participants