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
10 changes: 10 additions & 0 deletions agent/tasks.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,16 @@

## Session Summary

- 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 | Fix Cursor gray highlight background block (issue #1) | Completed | AI | 2026-07-08 |
- Active focus: Fixed GitHub issue #4 — `ripgrep.search` now returns the partial results it collected (with a one-time warning) instead of rejecting the whole search when stdout exceeds `maxBuffer`, and raised the default `taskvision.ripgrep.ripgrepMaxBuffer` from 200 KB to 20480 KB (20 MB).
- Verification: `npm test` (120 passing, including a new truncation/partial-results regression test and the unchanged SIGINT interrupted test) and `npm run webpack` (build succeeded).

Expand Down
1 change: 1 addition & 0 deletions agent/timeline.md
Original file line number Diff line number Diff line change
Expand Up @@ -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-07-08 11:05 | 修复 issue #4:ripgrep 输出超过 maxBuffer 时返回部分结果而非整体失败 | src/ripgrep.js, src/extension.js, package.json, package.nls.json, package.nls.zh-cn.json, test/ripgrep.tests.js, agent/tasks.md, agent/timeline.md | 将 `taskvision.ripgrep.ripgrepMaxBuffer` 默认值从 200 提升到 20480(20 MB)并同步中英文 markdownDescription;`ripgrep.search` 超过 maxBuffer 时不再 reject,而是置 `truncated` 标记、SIGINT 停止进程,并在 close 处理器优先 `resolve` `formatResults` 的部分结果(数组附带 `truncated`/`maxBuffer`),保留真实 SIGINT interrupted 与退出码分支;extension.js `search()` 检测 `matches.truncated` 后调用一次性 `notifyBufferTruncated` 警告(复用 `OPEN_SETTINGS_BUTTON` 模式),并在 `rebuild()` 重置该一次性标记;新增部分结果回归测试 | 大型仓库因 ripgrep stdout 超过 200 KB 默认缓冲导致整体 reject、树视图零结果(GitHub issue #4),需要在提高上限的同时对超限做优雅降级,保证已收集的标签仍能展示 | 已完成;验证执行 `npm test`(120 passing,含新增 truncation 用例且既有 `ripgrep.kill forwards SIGINT` interrupted 用例未回归)、`npm run webpack`(构建成功) |
| 2026-07-08 11:05 | 支持 `%` 注释风格下的 TODO/FIXME 识别 (issue #3) | package.json, src/extension.js, reproduce_issue_v9.js, test/tests.js, CHANGELOG.md, agent/tasks.md, agent/timeline.md | 在默认检测正则 `taskvision.regex.regex` 的注释前缀选择组中加入 `%`(`(//\|#\|<!--\|;\|/\*\|%\|^\|...)`),并同步更新 `src/extension.js` 中“重置为默认”命令与 `reproduce_issue_v9.js` stub 中的同一字面量;新增断言 `% TODO fix this`/`% FIXME` 被默认正则识别、且 `//`、`#` 前缀仍生效的 QUnit 用例,并在 CHANGELOG 增加 Unreleased 记录 | LaTeX/Matlab/Erlang 使用 `%` 作为行注释符,原正则缺少 `%`,导致这些注释中的 TODO/FIXME 无法被检测(GitHub issue #3 反馈,报告者建议将 `%` 加入检测正则) | 已完成;验证执行 `npm ci`、`npm test`(120 passing, 0 failing,新增用例通过)、`npm run webpack`(构建成功)、`node -e` 校验 package.json 为合法 JSON 且默认正则含 `%` |
| 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`,未运行测试套件 |
Expand Down
40 changes: 27 additions & 13 deletions src/highlights.js
Original file line number Diff line number Diff line change
Expand Up @@ -441,31 +441,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',
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;
}
Expand Down Expand Up @@ -897,3 +910,4 @@ module.exports.triggerHighlight = triggerHighlight;
module.exports.clearCache = clearCache;
module.exports._getTagPlan = getTagPlan;
module.exports._buildTextSegments = buildTextSegments;
module.exports._buildGlassDecorationOptions = buildGlassDecorationOptions;
129 changes: 129 additions & 0 deletions test/highlights.decorations.tests.js
Original file line number Diff line number Diff line change
@@ -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');
});
Loading