Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions packages/web-shell/client/build-artifact.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -217,4 +217,36 @@ describe('build artifact — package boundary', () => {
});
expect(unscoped).toEqual([]);
});

it('ships the ::selection highlight for message content in the lib bundle (#8214)', () => {
// The defensive ::selection rule must reach embedded deployments -
// i.e. it must be in the component-scoped CSS injected into dist/index.js,
// not only the standalone app's standalone.css. Asserting the rule is
// present and scoped under the WebShell root pins the lib-bundle fix.
let matched: Rule | undefined;
postcss.parse(readInjectedCss()).walkRules((rule) => {
// Match the effect (selectable rows get a ::selection rule scoped to
// the WebShell root), not the exact notation - a maintainer changing
// `background` to `background-color` (the CSS Pseudo-Elements-4 name)
// should not break this pin while the e2e one stays green.
if (
rule.selector.includes('[data-user-selectable]') &&
rule.selector.includes('::selection')
) {
matched = rule;
}
});
expect(
matched,
'::selection rule for [data-user-selectable] missing from lib bundle',
).toBeDefined();
expect(matched?.selector).toContain('[data-web-shell-root]');
expect(
matched?.nodes.some(
(n) =>
n.type === 'decl' &&
(n.prop === 'background' || n.prop === 'background-color'),
),
).toBe(true);
Comment on lines +226 to +250

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.

[Suggestion] R6-1: The lib-bundle pin matches the rule by an exact selector substring (including the space before ::selection) and an exact declaration prop name (background), coupling the test to the workaround's current notation instead of its effect — probe-verified against the real built artifact: rewriting the declaration to background-color: fails the prop assertion with an unannotated expected false to be true while the e2e pin stays green; removing the space from the selector fails with "rule missing from lib bundle" although the rule is still present. — Failure scenario: a maintainer makes an innocuous notation edit (e.g. background:background-color:, the property CSS Pseudo-Elements-4 lists for ::selection); the rule still ships and the e2e pin still passes, but this test fails with a misleading message, so the two pins added by this PR give contradictory signals and the maintainer debugs a nonexistent bundling regression.

Suggested change
let matched: Rule | undefined;
postcss.parse(readInjectedCss()).walkRules((rule) => {
if (rule.selector.includes('data-user-selectable] ::selection')) {
matched = rule;
}
});
expect(
matched,
'::selection rule for [data-user-selectable] missing from lib bundle',
).toBeDefined();
expect(matched?.selector).toContain('[data-web-shell-root]');
expect(
matched?.nodes.some((n) => n.type === 'decl' && n.prop === 'background'),
).toBe(true);
let matched: Rule | undefined;
postcss.parse(readInjectedCss()).walkRules((rule) => {
if (
rule.selector.includes('[data-user-selectable]') &&
rule.selector.includes('::selection')
) {
matched = rule;
}
});
expect(
matched,
'::selection rule for [data-user-selectable] missing from lib bundle',
).toBeDefined();
expect(matched?.selector).toContain('[data-web-shell-root]');
expect(
matched?.nodes.some(
(n) =>
n.type === 'decl' &&
(n.prop === 'background' || n.prop === 'background-color'),
),
).toBe(true);
中文说明

lib bundle 固定测试通过精确的选择器子串(包括 ::selection 前的空格)和精确的声明属性名(background)来匹配规则,使测试耦合于 workaround 的当前写法而非其效果——已对真实构建产物做探针验证:将声明改写为 background-color: 时,属性断言以无注释的 expected false to be true 失败,而 e2e 固定测试仍然通过;去掉选择器中的空格时,会报出误导性信息 "rule missing from lib bundle",而规则实际仍在 bundle 中。— 失败场景:维护者做一次无害的写法调整(例如 background:background-color:,即 CSS Pseudo-Elements-4 为 ::selection 列出的属性);规则仍然生效、e2e 固定测试仍然通过,但本测试以误导性信息失败——本 PR 新增的两个固定测试给出矛盾信号,维护者会去排查一个并不存在的打包回归。

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

});
});
24 changes: 24 additions & 0 deletions packages/web-shell/client/e2e/web-shell.smoke.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,30 @@ test('loads replayed transcript and connects to fake daemon @smoke', async ({
await expect(page.locator('[data-web-shell-message-list]')).toContainText(
'Hello from fake daemon',
);

// #8214: pin the explicit ::selection rule on message content. This
// asserts the rule is present and matches every [data-user-selectable]
// wrapper row (user and assistant alike), not just the first one; it
// does not verify the Firefox paint effect itself (this repo's Playwright
// projects are chromium-only).
const selectionBackgrounds = await page.evaluate(() => {
// Match the wrapper rows themselves, not their descendants - a single
// row renders many descendant elements, so counting descendants does
// not enforce the "both roles present" invariant.
const rows = document.querySelectorAll('[data-user-selectable]');
return Array.from(rows, (row) => {
// ::selection applies to the element's text content; sample the first
// text-bearing descendant (or the row itself if it has none).
const target = row.querySelector('*') ?? row;
return getComputedStyle(target, '::selection').backgroundColor;
});
});
// The fixture renders both a user and an assistant message, so there must
// be at least two selectable rows and every one must carry the rule.
expect(selectionBackgrounds.length).toBeGreaterThanOrEqual(2);
Comment on lines +61 to +63

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.

[Suggestion] The assertion counts element descendants matched by [data-user-selectable] *, not the [data-user-selectable] wrapper rows themselves, so it does not enforce the "at least two selectable rows" invariant the comment claims — one user row alone renders 4+ element descendants. — Failure scenario: a future MessageItem.tsx edit drops/bypasses the selectableSafeBody wrapper for one role (e.g. assistant rows) → that row's nodes leave the querySelectorAll sample, the remaining row still yields >= 2 descendants, the length check and per-node color loop both stay green, and the row silently loses the ::selection highlight in Firefox — a re-recurrence of the #8214 symptom this pin was written to catch. Probe-confirmed on the real component: removing the assistant wrapper's attribute leaves descendants=4 with the assertion still green, while counting wrappers flips 2 → 1.

Suggested change
// The fixture renders both a user and an assistant message, so there must
// be at least two selectable rows and every one must carry the rule.
expect(selectionBackgrounds.length).toBeGreaterThanOrEqual(2);
// The fixture renders both a user and an assistant message, so there must
// be at least two selectable rows and every one must carry the rule.
expect(selectionBackgrounds.length).toBeGreaterThanOrEqual(2);
expect(
await page.locator('[data-user-selectable]').count(),
).toBeGreaterThanOrEqual(2);
中文说明

建议:该断言统计的是 [data-user-selectable] * 匹配到的元素后代节点,而不是 [data-user-selectable] 包装行本身,因此并未落实注释所声称的“至少两个可选区行”这一不变量——仅一个用户行就能渲染出 4 个以上的元素后代节点。失败场景:未来对 MessageItem.tsx 的修改移除/绕过了某一角色(如助手行)的 selectableSafeBody 包装 → 该行的节点会离开 querySelectorAll 的采样集合,而剩余行仍能提供 >= 2 个后代节点,长度检查与逐节点颜色循环都保持为绿,该行在 Firefox 中悄悄失去 ::selection 高亮——恰好复发本固定测试要防范的 #8214 症状。已在真实组件上探针验证:移除助手行包装的属性后 descendants=4,断言仍为绿;改为统计包装数则由 2 → 1,可捕获该回归。

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

for (const bg of selectionBackgrounds) {
expect(bg).toBe('rgba(0, 128, 255, 0.3)');
}
});

test('submits a prompt and renders a streamed assistant response @smoke', async ({
Expand Down
22 changes: 22 additions & 0 deletions packages/web-shell/client/styles/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -132,3 +132,25 @@
*::-webkit-scrollbar-thumb:hover {
background: var(--scrollbar-thumb-hover);
}

/*
* Defensive selection-highlight rule for message content. #8214.
*
* Firefox is suspected of not painting the default selection highlight for
* text whose element chain passes through a `display: contents` element (the
* `data-user-selectable` wrapper on MessageItem). The logical selection
* (copy, selectionchange popup) works fine - only the visual highlight is
* missing. An explicit `::selection` background makes Firefox paint the
* highlight where the default painting may fail. This is a low-risk
* workaround, not a confirmed root-cause fix: the wrapper is shared by user
* and assistant rows alike, and the reporter's environment appears to be an
* embedding page, so its own CSS may also need adjustment.
*
* This rule lives in the component-scoped stylesheet (loaded by App.tsx and
* WebShellTranscript.tsx) rather than standalone.css so it ships with the
Comment on lines +149 to +150

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.

[Suggestion] The PR description is stale: "What changed" still says the rule is added to standalone.css ("Added one CSS rule to standalone.css" / "在 standalone.css 中添加一条 CSS 规则"), but this round moved it to globals.css — verified at HEAD: the rule lives just below this comment, and standalone.css contains no ::selection rule at all.

Failure scenario: a maintainer verifying the round-3 move from the PR description checks the wrong file; since this repo squash-merges, the commit message built from this body permanently records the wrong stylesheet, misleading anyone later bisecting or changelogging the ::selection rule.

Suggested fix: update both language sections of the PR description's "What changed" to say the rule was moved from standalone.css to client/styles/globals.css so it ships with the npm package. (PR body edit only — no code change needed.)

中文说明

PR 描述已过时:"改动"部分仍说规则加在 standalone.css("Added one CSS rule to standalone.css" / "在 standalone.css 中添加一条 CSS 规则"),但本轮已把规则移到 globals.css —— 已在 HEAD 核实:规则就在本注释下方,standalone.css 中已无任何 ::selection 规则。

失败场景:维护者按 PR 描述去验证第 3 轮的移动时会查错文件;本仓库使用 squash 合并,由该描述生成的提交信息会把错误的样式表永久记录下来,误导后续 bisect 或编写 changelog 的人。

建议修复:更新 PR 描述两个语言版本的"改动"部分,说明规则已从 standalone.css 移到 client/styles/globals.css,以便随 npm 包发布。(仅需修改 PR 描述,无需改代码。)

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

* npm package and applies to embedded deployments, not just the standalone
* app.
*/
[data-user-selectable] ::selection {
background: hsl(210 100% 50% / 30%);
}
Loading