@young1lin/dsh-ui-gitworkbench 0.1.8 → 0.1.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +39 -0
- package/CHANGELOG.md +26 -0
- package/CHANGELOG_EN.md +26 -0
- package/lib/client.js +874 -533
- package/package.json +1 -1
- package/src/client/CodeEditor.tsx +174 -48
- package/src/client/FileBrowser.tsx +27 -25
- package/src/client/GitWorkbenchPanel.module.css +3 -0
- package/src/client/GitWorkbenchPanel.tsx +38 -20
- package/src/client/cm-diff.ts +24 -2
- package/src/client/cm-tokens.ts +34 -14
- package/src/client/highlight.ts +158 -61
- package/src/client/idle-value.ts +0 -21
- package/src/client/locales.ts +1 -3
- package/src/client/token-cache.ts +0 -0
package/AGENTS.md
CHANGED
|
@@ -33,6 +33,45 @@ This split shapes everything else:
|
|
|
33
33
|
- `@Remote` method params must be bare identifiers with `signal` last (the gateway reads `Function.prototype.toString`), and return values must be JSON-safe — no `undefined` property values, omit the key entirely.
|
|
34
34
|
- Host git calls use `ctx.subprocess.spawn` with `stdout: 'pipe'`, accumulating chunks manually — never `ctx.shell` (PTY scrollback silently truncates large output: lost branch headers, lost files).
|
|
35
35
|
|
|
36
|
+
## Performance first, and no leaks
|
|
37
|
+
|
|
38
|
+
This is the drawer's first requirement, ahead of features. It reads
|
|
39
|
+
repositories that are large, files that are long, and diffs that are dense, and
|
|
40
|
+
every one of those has, at some point, frozen it for seconds. The rules below
|
|
41
|
+
are what those freezes cost to find.
|
|
42
|
+
|
|
43
|
+
- **Nothing may be proportional to the file, the diff, or the repository.**
|
|
44
|
+
Work is proportional to the VIEWPORT: the rows on screen, plus a bounded
|
|
45
|
+
overscan. `row-window.ts` decides which rows are in the DOM; `token-cache.ts`
|
|
46
|
+
decides which lines get tokenized. A new pane wires into both — it does not
|
|
47
|
+
invent a third answer, and it does not render "just this once" over
|
|
48
|
+
everything.
|
|
49
|
+
- **A cap that turns a feature off is not a fix.** The Files tab used to stop
|
|
50
|
+
colouring past 2,000 lines and print a notice about it; that is a freeze
|
|
51
|
+
traded for a missing feature. Bound the WORK, keep the feature.
|
|
52
|
+
- **Never put an O(document) pass on the keystroke path.** The live diff tint
|
|
53
|
+
cost 709ms per keystroke at 4,000 lines. Map what is already computed through
|
|
54
|
+
the change and recompute when the typing stops (`IdleLayer` in
|
|
55
|
+
`CodeEditor.tsx`).
|
|
56
|
+
- **Caches are bounded, evicting, and able to report their own size**, so a
|
|
57
|
+
test can prove the bound instead of trusting it — `ChunkedTokens.size()`,
|
|
58
|
+
`LineTokens.size()`, `CommitPayloadCache`. An unbounded cache is a leak with
|
|
59
|
+
a nicer name.
|
|
60
|
+
- **Everything acquired is released**: `addEventListener` /
|
|
61
|
+
`removeEventListener`, `setInterval` / `clearInterval`, every observer
|
|
62
|
+
`disconnect()`ed, every timer cleared in the view's `destroy()` or the
|
|
63
|
+
effect's cleanup. `tests/no-leaks.test.ts` counts the pairs per file, so an
|
|
64
|
+
unbalanced one fails the suite.
|
|
65
|
+
- **Measure before and after, on real code, and put the numbers in the commit
|
|
66
|
+
message.** Generated fixtures lie: 4,000 identical lines with one change
|
|
67
|
+
opened in 200ms while the reporter's 800-line file took two seconds. The
|
|
68
|
+
live probes under `scripts/` (`verify_perf_profile.py`,
|
|
69
|
+
`verify_paint_window.py`) take a CDP CPU profile and count painted spans, so
|
|
70
|
+
the answer is a function name and a span count, not an impression. A
|
|
71
|
+
"performance fix" with no before/after number is a guess.
|
|
72
|
+
- **A fast pane that shows nothing is not fast.** Every perf probe asserts what
|
|
73
|
+
is ON SCREEN as well as what it cost.
|
|
74
|
+
|
|
36
75
|
## Testing pattern
|
|
37
76
|
|
|
38
77
|
Pure rules live in React/CSS-free modules so vitest can load them — `src/client/stage-tree.ts`, `diff-model.ts`, `worktree-view.ts`, `commit-graph.ts`, `op-feedback.ts`, `themes.ts`, `highlight.ts` (client); `worktree.ts`, `style-store.ts`, `atomic-json.ts`, `commit-cache.ts`, `git-ops.ts`, `git-log.ts` (host). React state stays in `GitWorkbenchPanel.tsx` (~3400 lines: chip + drawer + diff rendering). New behavior = pure helper + unit tests first, component wiring after.
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,32 @@
|
|
|
2
2
|
|
|
3
3
|
本文件记录面向使用者的变更。格式参考 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/),版本号遵循语义化版本。
|
|
4
4
|
|
|
5
|
+
## [0.1.10] - 2026-08-20
|
|
6
|
+
|
|
7
|
+
性能专项:抽屉的开销从此与文件长度无关。
|
|
8
|
+
|
|
9
|
+
### 性能
|
|
10
|
+
|
|
11
|
+
- **打开长文件不再卡,而且长文件重新有了语法着色**。此前编辑器(Files 页签,以及变更页进入编辑时)会把**整个文件**交给 shiki 分析一遍——1837 行真实 TypeScript 实测 1637ms,而超过 2000 行的文件干脆**不上色**,并弹一条提示说明原因。那不是修好,那是拿"少一个功能"换"不卡"。现在编辑器只要它**即将显示的那几十行**,跟着 CodeMirror 自己的视口走,2000 行上限和那条提示一起删了。实测 1837 行:**1461ms、连续 1274ms 画面不动**变成 **254ms、60ms**;6065 行的文件此前"很快但没有颜色",现在 **255ms 且有颜色**。
|
|
12
|
+
- **同一行永远不分析第二遍**。此前窗口每移动一次,都要重新扫描 240 行上下文加窗口内的行,什么都不记——连滚十屏 3021ms。新的 `token-cache.ts` 把文档切成 128 行一块,每块只分析一次,并且**接着前一块结束时的语法状态**继续(shiki 4.3 会把这个状态和 token 一起交回来,也接受它作为入参,所以续接是精确的,不是靠上下文猜的);只有从中间直接跳入时才读一次 240 行上下文。diff 视图那第二遍"逐行单独重新分析"——它必须存在,因为一个 hunk 不是真文件,否则新增的语句会被涂成对象键——只取决于该行文本,所以按文本缓存。**往回滚实测 77ms、零掉帧**。两个缓存都是 LRU、按**行数**限量、能自报大小,测试直接验证这个上限而不是相信它。
|
|
13
|
+
- **换引擎没有用,这条是实测排除的**。试了五种 shiki 引擎配置(预编译、ES2024 / ES2018 目标、内部 cache),300 行真实代码全部落在 130–180ms。每行半毫秒是这个分析器的地板,所以唯一的解法是少分析、并且记住——上面两条。
|
|
14
|
+
|
|
15
|
+
### 修复
|
|
16
|
+
|
|
17
|
+
- **打开第二个文件卡 7 到 9 秒**(这也是此前"从 22 行文件点到 4000 行文件几乎卡死"的真正原因)。CPU 采样直接点名 `@codemirror/merge` 的 diff。切换文件时,新文件的正文和"被比较的另一侧"是**两个独立的事务**进入编辑器的,中间那一帧里,**新文件的正文正对着旧文件的另一侧**——两份毫不相干的文档,做一次没有上限的 diff。现在实时底色只在两边都到齐之后才重算(而那时两边通常完全相同,第一行就返回了),并且给 `presentableDiff` 加上了 CodeMirror 自己的合并视图所用的上限(`scanLimit: 500`,外加 100ms 超时)。去掉这个上限,新增测试里那一对文本要跑 **292 秒**。实测:切换文件 **6952–8974ms 变成 270–357ms**,且文字落地即有颜色。
|
|
18
|
+
- **打字时不再有整文档级别的计算**。实时底色此前在**每一个事务**上重算全文 diff——4000 行的文件每敲一个键 709ms。现在着色和底色都先跟着改动平移(颜色跟着文字走,不会糊),停手之后再重算。
|
|
19
|
+
|
|
20
|
+
### 内部
|
|
21
|
+
|
|
22
|
+
- 新增 `tests/no-leaks.test.ts`:逐个客户端源文件核对 `addEventListener`/`removeEventListener`、`setInterval`/`clearInterval`、observer/`disconnect` 的配对,以及 CodeMirror 图层那个 React 清理够不着的延时器是否在 `destroy()` 里清除。扫描前先剥掉注释和字符串字面量。
|
|
23
|
+
- `AGENTS.md` 新增「性能与内存优先」一节:不许有正比于文件或仓库的开销;用上限关掉功能不算修好;按键路径上不许有整文档级别的计算;缓存必须有界、会淘汰、能自报大小;性能改动必须有真实代码上的前后数字。
|
|
24
|
+
|
|
25
|
+
## [0.1.9] - 2026-08-20
|
|
26
|
+
|
|
27
|
+
### 性能
|
|
28
|
+
|
|
29
|
+
- **History 和 Compare 里打开长 diff 也不再卡了**。0.1.8 治的是变更页的左右并排视图,History 与 Compare 用的是上下统一视图,当时**有意留到了后面**——它有一模一样的两笔开销:所有行都进 DOM,shiki 把所有行逐行重新分析。现在同样只渲染视口内的行、两遍 shiki 也只在窗口内跑。实测:一个 5365 行的 diff,**157ms、零掉帧、DOM 里 81 行**;滚动条仍然报完整的 107304px。作为对照,同一个面板此前在 6000 行左右的 diff 上会卡住四秒以上。
|
|
30
|
+
|
|
5
31
|
## [0.1.8] - 2026-08-20
|
|
6
32
|
|
|
7
33
|
紧急修复版:一次点击卡住三秒半,以及 Compare 里看不到内容的文件。
|
package/CHANGELOG_EN.md
CHANGED
|
@@ -2,6 +2,32 @@
|
|
|
2
2
|
|
|
3
3
|
User-facing changes, newest first. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versioning follows SemVer.
|
|
4
4
|
|
|
5
|
+
## [0.1.10] - 2026-08-20
|
|
6
|
+
|
|
7
|
+
A performance release: what the drawer costs no longer depends on how long the file is.
|
|
8
|
+
|
|
9
|
+
### Performance
|
|
10
|
+
|
|
11
|
+
- **Long files open without freezing, and long files have syntax colour again.** The editor — the Files tab, and the side pane once editing is armed — used to hand shiki the WHOLE file: 1,637ms on 1,837 lines of real TypeScript, and files past 2,000 lines were simply not coloured, with a notice explaining why. That is not a fix; it is a freeze traded for a missing feature. The editor now asks only for the lines it is about to show, driven by CodeMirror's own viewport, and the 2,000-line cap went with the notice. Measured at 1,837 lines: **1,461ms with a 1,274ms frozen frame becomes 254ms with 60ms**. A 6,065-line file used to be fast and colourless; it is now **255ms, with colour**.
|
|
12
|
+
- **No line is ever tokenized twice.** Every window move used to re-lex a 240-line lead-in plus its rows and remember nothing — ten screenfuls cost 3,021ms. The new `token-cache.ts` cuts a document into 128-line chunks, tokenizes each once, and **continues from the grammar state the previous chunk ended in** (shiki 4.3 returns that state with the tokens and accepts it back, so the continuation is exact rather than a lead-in's guess). Only a cold jump into the middle of a file reads a lead-in, and only once. The diff panes' second pass — re-lexing each line alone, which has to exist because a hunk is not a real file and would otherwise paint added statements as object keys — depends on nothing but the line's text, so it is cached by it. **Scrolling back over a file: 77ms, no dropped frame.** Both caches are LRU, bounded in LINES, and report their own size, so the tests prove the bound rather than trusting it.
|
|
13
|
+
- **Changing the engine does not help — that was measured, not assumed.** Five shiki engine configurations (eager compilation, ES2024 and ES2018 targets, an internal cache) all landed between 130ms and 180ms for 300 lines of real code. Half a millisecond per line is this tokenizer's floor, so the only lever is to tokenize less and remember it — the two entries above.
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
|
|
17
|
+
- **Opening a second file froze for seven to nine seconds** — which is also what was really behind "clicking a 4,000-line file after a 22-line one nearly locks up". A CPU profile named `@codemirror/merge`'s diff. Switching files replaces the buffer and the side it is compared against in two separate transactions, so for one frame the new file's text sits opposite the OLD file's: two unrelated documents, diffed with no ceiling. The live tint now recomputes only once both sides have settled (by which point they are usually identical and it returns immediately), and `presentableDiff` is given the bound CodeMirror's own merge view uses (`scanLimit: 500`, plus a 100ms timeout). Without that bound, the pair in the new test takes **292 seconds**. Measured: a file switch goes from **6,952-8,974ms to 270-357ms**, and the text arrives painted.
|
|
18
|
+
- **Nothing document-sized runs on the keystroke path any more.** The live tint used to diff the whole document on EVERY transaction — 709ms per keystroke at 4,000 lines. Colour and tint now map through the edit (so they ride along with the text instead of smearing) and recompute once the typing stops.
|
|
19
|
+
|
|
20
|
+
### Internal
|
|
21
|
+
|
|
22
|
+
- `tests/no-leaks.test.ts`: per client source, every `addEventListener` has a `removeEventListener`, every `setInterval` a `clearInterval`, every observer a `disconnect`, and the CodeMirror layer's deferred timer — the one React's cleanup cannot reach — is cleared in `destroy()`. Comments and string literals are stripped before counting.
|
|
23
|
+
- `AGENTS.md` gains a "Performance first, and no leaks" section: nothing proportional to the file or the repository; a cap that turns a feature off is not a fix; nothing document-sized on the keystroke path; caches bounded, evicting, and able to report their size; and a performance change ships with before/after numbers taken on real code.
|
|
24
|
+
|
|
25
|
+
## [0.1.9] - 2026-08-20
|
|
26
|
+
|
|
27
|
+
### Performance
|
|
28
|
+
|
|
29
|
+
- **Long diffs in History and Compare no longer freeze either.** 0.1.8 fixed the side-by-side view in Changes; History and Compare render a unified view, which was left out of that change deliberately and carried the same two costs — every row in the DOM, and Shiki re-lexing every row. Both are windowed now. Measured: a 5,365-row diff costs **157ms, no dropped frame, and 81 rows in the DOM**, while the scroller still reports the full 107,304px. For comparison, the same pane froze for over four seconds on a diff of about 6,000 rows.
|
|
30
|
+
|
|
5
31
|
## [0.1.8] - 2026-08-20
|
|
6
32
|
|
|
7
33
|
An urgent fix release: one click that froze the page for three and a half
|