Skip to content

Commit 19a7f30

Browse files
eliaweb-flow
andcommitted
Added agent skills for selectFromList and performance
Co-Authored-By: GitHub Copilot <noreply@github.com>
1 parent 0ba5792 commit 19a7f30

File tree

2 files changed

+166
-0
lines changed

2 files changed

+166
-0
lines changed
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
---
2+
name: improving-performance
3+
description: Process for systematically finding and fixing performance bottlenecks in code. Use when asked to improve performance, speed up code, or investigate slowness — covers analysis, prioritization, incremental delivery via PRD loop.
4+
---
5+
6+
# Improving Performance
7+
8+
A systematic process for identifying and fixing performance bottlenecks, delivered as incremental commits.
9+
10+
## Process
11+
12+
### 1. Analyze the Hot Path
13+
14+
Read the full code and trace the critical path end-to-end. Identify what runs on every user action (keystroke, scroll, click). Write findings as comments at the top of the main file:
15+
16+
```
17+
// BOTTLENECK 1: [description] — [impact] — Fix: [approach]
18+
// BOTTLENECK 2: ...
19+
// PRIORITY ORDER: list by biggest impact first
20+
```
21+
22+
This serves as both documentation and a working checklist.
23+
24+
### 2. Write a PRD
25+
26+
Create `doc/specs/<feature>-performance.md` with checkbox tasks grouped by category:
27+
28+
- **Hot loop optimizations** — algorithmic improvements in the innermost loops
29+
- **Rendering optimizations** — reduce DOM work, skip unnecessary operations
30+
- **Structural changes** — virtual scrolling, caching, architecture shifts
31+
32+
Order categories from easiest/safest to most impactful/risky.
33+
34+
### 3. Execute via Ralph Loop
35+
36+
One commit per category using fresh subagent sessions. Each iteration:
37+
38+
1. Launch subagent with specific tasks and coding style rules
39+
2. Verify changes (lint, read key sections)
40+
3. Commit with descriptive message
41+
4. Update PRD checkboxes and progress log
42+
43+
### 4. Archive
44+
45+
Move completed PRD to `doc/specs/completed/`, remove progress file.
46+
47+
## Common Bottleneck Patterns
48+
49+
### Allocation in hot loops
50+
51+
Creating arrays/objects per iteration causes GC pressure. Fix: pre-allocate and reuse.
52+
53+
```js
54+
// Bad: fresh array per call, called 10k times
55+
let matrix = Array(n)
56+
.fill()
57+
.map(() => Array(m).fill(0))
58+
59+
// Good: module-level typed array, grown as needed
60+
let _matrix = new Float64Array(maxN * maxM)
61+
```
62+
63+
### Repeated string operations
64+
65+
`toLowerCase()`, `trim()`, regex in inner loops. Fix: pre-compute once, pass through.
66+
67+
```js
68+
// Bad: lowercased per-character per-item per-keystroke
69+
if (needle[i].toLowerCase() === hay[j].toLowerCase())
70+
71+
// Good: pre-lowercase at init, compare directly
72+
item._lower = item.label.toLowerCase() // once
73+
if (needle[i] === hayLower[j]) // hot loop
74+
```
75+
76+
### Full DOM rebuild
77+
78+
`replaceChildren()` with all elements when only a slice is visible. Fix: virtual scrolling.
79+
80+
### Unnecessary work
81+
82+
Sorting when all scores are equal, scanning full list when filter only grew. Fix: conditional skip, incremental narrowing.
83+
84+
### Regex in tight loops
85+
86+
`/pattern/.test(ch)` per character. Fix: charCode comparisons.
87+
88+
```js
89+
// Bad
90+
static isAlnum(ch) { return /[a-zA-Z0-9]/.test(ch) }
91+
92+
// Good
93+
static isAlnum(ch) {
94+
let c = ch.charCodeAt(0)
95+
return (c >= 48 && c <= 57) || (c >= 65 && c <= 90) || (c >= 97 && c <= 122)
96+
}
97+
```
98+
99+
## Subagent Prompt Template
100+
101+
When delegating to a subagent, include:
102+
103+
1. **File to edit** — absolute path
104+
2. **Tasks** — numbered list with before/after code snippets
105+
3. **Coding style rules** — project conventions (indent, quotes, semicolons, `let` vs `const`)
106+
4. **Explicit instruction** to make ALL changes and return a summary
107+
108+
Keep task descriptions concrete with code examples, not abstract descriptions.
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
---
2+
name: refactoring-select-from-list
3+
description: Domain knowledge for the selectFromList webview component in vscode-textmate. Use when modifying, debugging, or extending the fuzzy-search selector UI — covers architecture, data flow, key files, and known constraints.
4+
---
5+
6+
# selectFromList Architecture
7+
8+
A webview-based fuzzy-search selector that replaces VS Code's native QuickPick for richer interaction (multi-select, range select, alternate actions).
9+
10+
## Key Files
11+
12+
| File | Role |
13+
| ------------------------------- | -------------------------------------------------------------------------------------------------------- |
14+
| `src/selectFromList.js` | Extension-side provider — creates/reuses webview, posts items, resolves promises |
15+
| `src/selectFromList/main.js` | Webview-side — `List` class (virtual scrolling, selection), `FuzzySearch` class (TextMate-style ranking) |
16+
| `src/selectFromList/index.html` | HTML shell with inline CSS, search input, CSP nonces |
17+
| `src/selectFromList.json` | Manifest fragment (contributes views, commands, configuration) |
18+
19+
## Data Flow
20+
21+
```
22+
Command invokes showSelectFromList(items, options)
23+
→ chooseItems() on provider
24+
→ createWebviewPanel() (reuses if alive) or sidebar path
25+
→ reveal() panel (so it's visible before init)
26+
→ await _isReady (resolves when webview posts "ready")
27+
→ postMessage({ type: "init", items, requestId })
28+
→ webview receives init
29+
→ pre-lowercases labels (_lowerLabel, _lowerDescription)
30+
→ computeVisible() → render() (virtual scrolling)
31+
→ focus search input
32+
→ user types → 30ms debounce → computeVisible() → render()
33+
→ user submits (Enter / double-click)
34+
→ postMessage({ type: "submit", indexes, requestId })
35+
→ provider resolves promise with selected items
36+
```
37+
38+
## FuzzySearch (TextMate port)
39+
40+
- `rank(filter, candidate, candidateLower, out)` — entry point, filter is already lowercase
41+
- `calculateRank(lhs, rhs, rhsLower, out)` — matrix-based scoring from TextMate's `ranker.cc`
42+
- `rankFile(filter, filename, directory, filenameLower, directoryLower)` — tries filename first, falls back to full path
43+
- `isSubset(needle, haystackLower)` — fast pre-check before full ranking
44+
- Pre-allocated typed arrays (`_matrix`, `_first`, `_last`, `_capitals`) avoid GC pressure
45+
46+
## Performance Characteristics
47+
48+
- **Virtual scrolling** — only ~60 DOM nodes regardless of list size (20-row overscan)
49+
- **Incremental filtering** — typing more chars narrows from previous matches, not full list
50+
- **Pre-lowercased labels**`toLowerCase()` called once at init, not per-char in hot loop
51+
- **Panel reuse**`retainContextWhenHidden: true`, panel is reused across opens
52+
53+
## Constraints
54+
55+
- The `FuzzySearch.calculateRank` algorithm is a TextMate port — modify with extreme care
56+
- `requestId` prevents stale responses from previous invocations
57+
- `close()` disposes panel (panel path) or hides sidebar — different teardown per render mode
58+
- The `_isReady` promise pattern: created fresh when panel/sidebar is new, stays resolved when reusing

0 commit comments

Comments
 (0)