From b1fe88285ddad5e8f47ad519c7aa4d73484aeb91 Mon Sep 17 00:00:00 2001 From: A-znk <145305286+Yuki-zik@users.noreply.github.com> Date: Wed, 8 Jul 2026 11:14:02 +0800 Subject: [PATCH] fix(highlights): prevent gray decoration box in Cursor Co-locate borderRadius with the border/background it rounds in the light/dark theme objects and inject a transparent background whenever a glass theme carries a border or borderRadius without an explicit one. This stops Cursor from painting a fallback gray box on the decoration base rule (microsoft/vscode#175819, wayou/vscode-todo-highlight#434). VS Code appearance is unchanged. Fixes #1 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- agent/tasks.md | 12 +-- agent/timeline.md | 1 + src/highlights.js | 40 ++++++--- test/highlights.decorations.tests.js | 129 +++++++++++++++++++++++++++ 4 files changed, 164 insertions(+), 18 deletions(-) create mode 100644 test/highlights.decorations.tests.js diff --git a/agent/tasks.md b/agent/tasks.md index a208c23..3eafb9f 100644 --- a/agent/tasks.md +++ b/agent/tasks.md @@ -9,11 +9,13 @@ ## Session Summary -- Active focus: Reanalyzed the AI context feature's data flow, product logic, and agent handoff contract after sidecar write hardening. -- Verification: read-only source/docs/tests review plus `git --no-pager ls-files .taskvision .gitignore`; no test suite was run because no runtime code changed. +- Active focus: Fixed issue #1 — Cursor rendered a solid gray background block behind the `neon+glass` tag highlight where VS Code showed the intended translucent glass box. +- Root cause: `buildGlassDecorationOptions` in `src/highlights.js` placed `borderRadius` on the decoration base rule while `backgroundColor`/`border` lived only in `light`/`dark`; a border/borderRadius base rule without a co-located background triggers Cursor's fallback gray fill (microsoft/vscode#175819, wayou/vscode-todo-highlight#434). +- Fix: re-scoped `borderRadius` into `light`/`dark` alongside the border and injected `backgroundColor: 'transparent'` on any themed object that carries a border/borderRadius without an explicit background; VS Code appearance unchanged. +- Verification: `npx qunit test/highlights.decorations.tests.js` (5 passing), `npm test` (124 passing), `npm run webpack` (success). Cursor is not installed in this environment, so final visual confirmation in Cursor requires a maintainer. ## Active Session Task -| Priority | Task | Status | Owner | Due | -| -------- | ----------------------------- | --------- | ----- | ---------- | -| P1 | Reanalyze AI context logic | Completed | AI | 2026-04-29 | +| Priority | Task | Status | Owner | Due | +| -------- | ---------------------------------------------------------- | --------- | ----- | ---------- | +| P1 | Fix Cursor gray highlight background block (issue #1) | Completed | AI | 2026-07-08 | diff --git a/agent/timeline.md b/agent/timeline.md index 55e172f..3881b3f 100644 --- a/agent/timeline.md +++ b/agent/timeline.md @@ -2,6 +2,7 @@ | 日期时间 | 任务/变更 | 修改文件 | 实现逻辑 | 修改动机 | 结果/备注 | | ---------------- | --------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| 2026-07-08 11:05 | 修复 Cursor 中标签高亮出现灰色背景块(Issue #1) | src/highlights.js, test/highlights.decorations.tests.js, agent/tasks.md, agent/timeline.md | 重写 `buildGlassDecorationOptions`:把 `borderRadius` 从装饰顶层移入 `light`/`dark` 主题对象并与 `border` 同层;对任何带 `border`/`borderRadius` 但缺少显式背景的主题对象注入 `backgroundColor: 'transparent'`;导出 `_buildGlassDecorationOptions` 供行为测试;新增测试断言不再有顶层 `borderRadius`、`borderRadius` 与背景同层、border-only 时回退透明背景 | Cursor(VS Code 派生版)对同时带 `border`+`borderRadius` 但基础 CSS 规则无显式背景的装饰会填充默认灰色块(microsoft/vscode#175819、wayou/vscode-todo-highlight#434);原实现把顶层 `borderRadius` 与 `light`/`dark` 背景拆分到不同规则,导致基础规则只有圆角无背景被 Cursor 灰填充,而 VS Code 正常。重新归位后取值不变,VS Code 外观保持一致 | 已完成;验证执行 `npx qunit test/highlights.decorations.tests.js`(5 passing)、`npm test`(124 passing)、`npm run webpack`(成功);Cursor 未安装于当前环境(`code` 存在、`cursor` 缺失),最终 Cursor 视觉确认需维护者复核;已开 PR(未合并),`Fixes #1` | | 2026-04-29 20:45 | 重新分析 AI 上下文功能逻辑与协作思路 | agent/tasks.md, agent/timeline.md | 只读梳理 `aiContext` 导出器、扩展命令链路、task/context/session sidecar store、稳定 ID 生成、README 协议说明、配置 schema 与测试覆盖,并确认 `.taskvision` 生成文件当前被 Git 跟踪 | 用户要求重新检查 AI 上下文功能部分的逻辑与思路是否合理,需要在继续实现前判断产品定位、数据源边界、会话语义和协作契约是否自洽 | 已完成分析;结论为“外部 agent 交接包”的核心方向合理,但 implementation session 未进入导出、sidecar/source 权责边界、跟踪生成文件、状态报告语义和 Extension Host 集成测试仍需收敛;验证执行只读源码/文档/测试核查与 `git --no-pager ls-files .taskvision .gitignore`,未运行测试套件 | | 2026-04-29 20:18 | 加固 AI sidecar JSON 写入与损坏恢复 | src/jsonStore.js, src/taskMetaStore.js, src/contextStore.js, src/changeSessionStore.js, test/dataModel.tests.js, test/extension.tests.js, agent/tasks.md, agent/timeline.md | 新增共享 `jsonStore`,用同目录临时文件加 `rename` 替换写入 JSON;读取 malformed JSON 时先重命名为 `.invalid` 备份再恢复为空 store;任务与上下文 store 在写入失败后失效内存 cache,避免未落盘变更被下一次保存带回;补充 malformed 备份、原子替换失败和 cache 丢弃回归测试 | 用户要求开始实现 AI 可靠性改进,需要优先降低 `.taskvision` sidecar 被半写、损坏或静默覆盖时的协作风险 | 已完成;验证执行 `npx qunit test/dataModel.tests.js test/aiContext.tests.js test/extension.tests.js`、`npm test`(119 passing)、`npm run webpack`、`git diff --check -- agent/tasks.md agent/timeline.md src/jsonStore.js src/taskMetaStore.js src/contextStore.js src/changeSessionStore.js test/dataModel.tests.js test/extension.tests.js` | | 2026-04-29 19:00 | 修复深度 review 中确认的问题 | buildCodiconNames.js, package.json, package.nls.json, package.nls.zh-cn.json, src/config.js, src/extension.js, src/icons.js, src/taskMetaStore.js, test/aiContext.tests.js, test/extension.tests.js, test/schemes.tests.js, agent/tasks.md, agent/timeline.md | 为 AI 输出目录增加 workspace 内约束与 schema 校验,修正 `file excludes` 枚举判断,移除 regex 配置变更早退并让 opacity 变更清高亮缓存,合并字体样式配置写入,修正 TaskVision 内置图标校验条件,补齐命令 NLS key,并让 codicon 生成失败时中止构建而不是写入极小 fallback;同时补充对应回归测试 | 用户要求开始修复全系统深度 review 中已确认的问题,需要用测试先行方式把行为锁住并避免误改未验证项 | 已完成;验证执行 `npx qunit test/aiContext.tests.js test/extension.tests.js test/schemes.tests.js`、`npm test`(112 passing)、`npm run webpack`、`git diff --check -- agent/tasks.md agent/timeline.md buildCodiconNames.js package.json package.nls.json package.nls.zh-cn.json src/config.js src/extension.js src/icons.js src/taskMetaStore.js test/aiContext.tests.js test/extension.tests.js test/schemes.tests.js` | diff --git a/src/highlights.js b/src/highlights.js index 35b3b38..f99a57c 100644 --- a/src/highlights.js +++ b/src/highlights.js @@ -355,31 +355,44 @@ function getTagPlan(tag) { return plan; } +function applyGlassTheme(target, source, borderRadius) { + if (source.backgroundColor !== undefined) { + target.backgroundColor = source.backgroundColor; + } + if (source.border !== undefined) { + target.border = source.border; + } + + // Keep borderRadius co-located with the border/background it rounds instead of + // leaving it on the decoration's base rule. + if (borderRadius !== undefined && (target.border !== undefined || target.backgroundColor !== undefined)) { + target.borderRadius = borderRadius; + } + + // Cursor (and VS Code, see microsoft/vscode#175819 & wayou/vscode-todo-highlight#434) + // paints a fallback gray box when a decoration carries a border or borderRadius + // without an explicit, co-located backgroundColor. Guarantee a transparent + // background in that case so the glass box stays see-through in every fork. + if ((target.border !== undefined || target.borderRadius !== undefined) && target.backgroundColor === undefined) { + target.backgroundColor = 'transparent'; + } +} + function buildGlassDecorationOptions(plan) { var glassChannel = plan.channels.glass; var lightStyle = glassChannel.style.light || {}; var darkStyle = glassChannel.style.dark || {}; + var borderRadius = glassChannel.style.borderRadius; var options = { rangeBehavior: vscode.DecorationRangeBehavior.ClosedClosed, isWholeLine: glassChannel.rangeType === 'whole-line' || glassChannel.rangeType === 'line', - borderRadius: glassChannel.style.borderRadius, light: {}, dark: {} }; - if (lightStyle.backgroundColor !== undefined) { - options.light.backgroundColor = lightStyle.backgroundColor; - } - if (darkStyle.backgroundColor !== undefined) { - options.dark.backgroundColor = darkStyle.backgroundColor; - } - if (lightStyle.border !== undefined) { - options.light.border = lightStyle.border; - } - if (darkStyle.border !== undefined) { - options.dark.border = darkStyle.border; - } + applyGlassTheme(options.light, lightStyle, borderRadius); + applyGlassTheme(options.dark, darkStyle, borderRadius); return options; } @@ -811,3 +824,4 @@ module.exports.triggerHighlight = triggerHighlight; module.exports.clearCache = clearCache; module.exports._getTagPlan = getTagPlan; module.exports._buildTextSegments = buildTextSegments; +module.exports._buildGlassDecorationOptions = buildGlassDecorationOptions; diff --git a/test/highlights.decorations.tests.js b/test/highlights.decorations.tests.js new file mode 100644 index 0000000..cc361cf --- /dev/null +++ b/test/highlights.decorations.tests.js @@ -0,0 +1,129 @@ +var Module = require('module'); + +// highlights.js pulls in vscode-dependent modules (config.js, icons.js) but only +// touches the vscode API inside functions. Stub 'vscode' just long enough to require +// the module, then restore the original require so other test files are unaffected. +function loadHighlightsWithVscodeStub() { + var vscodeStub = { + DecorationRangeBehavior: { ClosedClosed: 0, ClosedOpen: 1, OpenClosed: 2, OpenOpen: 3 }, + OverviewRulerLane: { Left: 1, Center: 2, Right: 4, Full: 7 }, + ThemeColor: function (id) { this.id = id; }, + Range: function () { }, + Position: function () { }, + Uri: { file: function (p) { return { fsPath: p }; } }, + window: { createTextEditorDecorationType: function (options) { return options; } }, + workspace: { getConfiguration: function () { return { get: function () { return undefined; } }; } } + }; + + var originalRequire = Module.prototype.require; + Module.prototype.require = function (name) { + if (name === 'vscode') { return vscodeStub; } + return originalRequire.apply(this, arguments); + }; + + try { + delete require.cache[require.resolve('../src/highlights.js')]; + return require('../src/highlights.js'); + } finally { + Module.prototype.require = originalRequire; + } +} + +var highlights = loadHighlightsWithVscodeStub(); + +function glassPlan(style, rangeType) { + return { + tag: 'TODO', + channels: { + glass: { + kind: 'glass', + enabled: true, + rangeType: rangeType || 'whole-line', + style: style + } + } + }; +} + +QUnit.test('glass decoration exposes an internal builder', function (assert) { + assert.strictEqual(typeof highlights._buildGlassDecorationOptions, 'function'); +}); + +QUnit.test('glass decoration co-locates borderRadius and preserves the translucent background', function (assert) { + var opts = highlights._buildGlassDecorationOptions(glassPlan({ + light: { backgroundColor: 'rgba(92,107,192,0.15)', border: '1px solid rgba(92,107,192,0.6)' }, + dark: { backgroundColor: 'rgba(92,107,192,0.15)', border: '1px solid rgba(92,107,192,0.6)' }, + borderRadius: '6px' + }, 'whole-line')); + + // The base CSS rule must NOT carry borderRadius: Cursor paints it gray when the + // base rule has border-radius without a co-located background (microsoft/vscode#175819). + assert.strictEqual(opts.borderRadius, undefined, 'no top-level borderRadius'); + + assert.strictEqual(opts.light.borderRadius, '6px', 'borderRadius co-located in light'); + assert.strictEqual(opts.dark.borderRadius, '6px', 'borderRadius co-located in dark'); + + assert.strictEqual(opts.light.backgroundColor, 'rgba(92,107,192,0.15)', 'light background preserved'); + assert.strictEqual(opts.dark.backgroundColor, 'rgba(92,107,192,0.15)', 'dark background preserved'); + assert.strictEqual(opts.light.border, '1px solid rgba(92,107,192,0.6)', 'light border preserved'); + assert.strictEqual(opts.dark.border, '1px solid rgba(92,107,192,0.6)', 'dark border preserved'); + + assert.strictEqual(opts.isWholeLine, true, 'whole-line range renders whole line'); +}); + +QUnit.test('glass decoration injects a transparent background for border-only configs', function (assert) { + var opts = highlights._buildGlassDecorationOptions(glassPlan({ + light: { border: '1px solid #ffffff' }, + dark: { border: '1px solid #000000' }, + borderRadius: '6px' + }, 'tag')); + + assert.strictEqual(opts.borderRadius, undefined, 'no top-level borderRadius'); + assert.strictEqual(opts.light.backgroundColor, 'transparent', 'transparent injected for border-only light'); + assert.strictEqual(opts.dark.backgroundColor, 'transparent', 'transparent injected for border-only dark'); + assert.strictEqual(opts.light.borderRadius, '6px', 'borderRadius co-located with border (light)'); + assert.strictEqual(opts.dark.borderRadius, '6px', 'borderRadius co-located with border (dark)'); + assert.strictEqual(opts.isWholeLine, false, 'tag range is not whole line'); +}); + +QUnit.test('no glass theme carries a border or borderRadius without an explicit background', function (assert) { + var styles = [ + { + light: { backgroundColor: 'rgba(1,2,3,0.15)', border: '1px solid rgba(1,2,3,0.6)' }, + dark: { backgroundColor: 'rgba(1,2,3,0.15)', border: '1px solid rgba(1,2,3,0.6)' }, + borderRadius: '6px' + }, + { + light: { border: '1px solid #abcdef' }, + dark: { border: '1px solid #abcdef' }, + borderRadius: '6px' + }, + { + light: { border: '1px solid #abcdef' }, + dark: { border: '1px solid #abcdef' } + } + ]; + + styles.forEach(function (style, index) { + var opts = highlights._buildGlassDecorationOptions(glassPlan(style, 'whole-line')); + ['light', 'dark'].forEach(function (theme) { + var themed = opts[theme]; + if (themed.border !== undefined || themed.borderRadius !== undefined) { + assert.strictEqual(typeof themed.backgroundColor, 'string', + 'style #' + index + ' ' + theme + ' with border/radius declares an explicit background'); + } + }); + }); +}); + +QUnit.test('glass decoration leaves background-only configs untouched', function (assert) { + var opts = highlights._buildGlassDecorationOptions(glassPlan({ + light: { backgroundColor: 'rgba(1,2,3,0.15)' }, + dark: { backgroundColor: 'rgba(1,2,3,0.15)' } + }, 'text')); + + assert.strictEqual(opts.light.backgroundColor, 'rgba(1,2,3,0.15)', 'background preserved'); + assert.strictEqual(opts.light.border, undefined, 'no border fabricated'); + assert.strictEqual(opts.light.borderRadius, undefined, 'no borderRadius fabricated'); + assert.strictEqual(opts.dark.backgroundColor, 'rgba(1,2,3,0.15)', 'dark background preserved'); +});