Commit 2ed3077
authored
feat: per-stack restart commands and a hover-only status bar (#4)
* feat: per-stack restart commands and hover-only status bar
Restart is now a shell concern with two levels: rstack.restart ("Relaunch
Extension") tears down every controller, re-runs detection and registers
again from scratch; the new rstack.<stack>.restart commands do the same
for a single stack. The Rslint stack's own LSP-only restart is removed —
bouncing just the server kept the controller's stale package resolution
and version check, which is exactly what these commands exist to clear.
Its settings listener now routes through the shell command instead.
The status bar QuickPick is gone: the hover card is the single surface.
It renders all six rows in one table (aligned state/label/action columns),
colours state icons with theme variables, moves state text into the icon's
native tooltip, and stacks the three global actions under a divider.
Clicking the item opens the extension log directly.
Unit tests cover the per-stack restart scope and the manifest contract
(rstack.restart stays palette-unconditional; per-stack restarts gate on
their context keys).
* refactor: derive stack command ids and give stacks a restart seam
Three follow-ups from a cleanup pass over the restart work:
- `stackCommand(stack, verb)` in types.ts is now the single place a
per-stack command id is spelled. The shell registers through it and the
status bar links through it, so the two OUTPUT_COMMANDS/RESTART_COMMANDS
tables are gone — they had become hand-copied templates whose drift from
the registration site would not be a type error.
- `StackContext.requestRestart(reason)` replaces the Rslint stack reaching
back through the command registry. Restart was already a shell service;
it is now injected like every other one, and the reason reaches the log
so a settings-triggered restart says what moved.
- `dispose()` tears controllers down in parallel, matching the restart
path, and drops `clearProjectModuleCache` which had no callers.
Also folds the status bar's two anchor builders into one so escaping is a
property of the markup rather than a per-call-site obligation, inlines
`#canRestart`, and trims the rejected-alternative journal from render().
* docs: state what a restart cannot recover
A restart re-resolves binaries and package versions and respawns every
tool process, but the config-loader and eslint-plugin modules imported
from the project stay in Node's ESM registry for the lifetime of the
window. Verified: clearing the local memo in projectModules.ts hands back
the identical module object, and a cache-busting query reloads only the
entry module — relative specifiers inside it do not inherit the query, so
the result is a fresh entry over stale dependencies.
Records the limit in the README and the reasoning in AGENTS.md so nobody
adds an invalidation hook that cannot work.
* fix: run shell teardown on the shared queue
`retire` drops a controller from the shell's map before awaiting its
teardown, so a restart in flight leaves a window where the map is already
empty and the Rslint client is still shutting down. `dispose()` walked
that map, found nothing, and went on to dispose the channels out from
under the running teardown — letting deactivate() resolve while the child
processes were still alive.
Putting the teardown pass on the same queue as reconciles and restarts
makes "whatever was in flight has finished" something dispose can wait
for; the `#disposed` flag it sets first still stops that pass from
rebuilding anything on its way out.
The regression test asserts on the output channels rather than on
deactivate's promise: "has not resolved yet" races the microtask queue,
whereas "the channel this teardown still logs to is alive" is a fact.
Confirmed to fail against the previous dispose().
* docs: add a roadmap to the README
States, per config source, what the extension supports today and what is
still coming, plus the cross-cutting items (re-detection command,
bounded version ranges, retiring the standalone extensions).
* fix: run the activation reconcile on the shared queue
`registerCommands()` runs before activation's first await, so the palette
can reach a restart while activation is still bringing stacks up. The
activation reconcile called `reconcile()` directly rather than through
`enqueue`, so that restart ran straight through it and could retire a
controller whose `register()` had not returned — the retired controller
then went on to publish its exports and set `active` to true.
Every pass now rides the one queue, which is what AGENTS.md already says.
The regression test blocks the Rslint controller's `register()`, invokes
the restart, and asserts no teardown begins for a stack whose register is
still in flight. Confirmed to fail against the direct call.
* refactor(vscode): make the shell's queue discipline structural
The two concurrency fixes in this PR both came from a pass that bypassed
the shell's single queue, so encode the rule instead of restating it:
- `reconcile()` is now the queued wrapper and `runReconcile()` the body,
matching the existing `restart()`/`runRestart()` pair. The `run*`
prefix marks "already owns the queue" at every call site.
- `retireAll()` replaces the two hand-rolled `Promise.allSettled` loops
in `runRestart` and `dispose`.
- `dispose()` drops the detection service before waiting on the queue,
so a file touched during shutdown cannot arm a fresh pass behind it.
The reconcileStack comments still described the pre-fix model where a
teardown could run beside a reconcile; they now describe what actually
happens. On the test side: drop the dead `blockDetection` field, rebuild
harness state from a factory so a new field cannot leak between tests,
and use `Promise.withResolvers`.1 parent 3a9e224 commit 2ed3077
14 files changed
Lines changed: 994 additions & 190 deletions
File tree
- packages/vscode
- src
- stacks/lint
- tests/e2e/suite
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
8 | 8 | | |
9 | 9 | | |
10 | 10 | | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
11 | 23 | | |
12 | 24 | | |
13 | 25 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
19 | 19 | | |
20 | 20 | | |
21 | 21 | | |
| 22 | + | |
| 23 | + | |
22 | 24 | | |
23 | 25 | | |
24 | 26 | | |
| |||
30 | 32 | | |
31 | 33 | | |
32 | 34 | | |
| 35 | + | |
33 | 36 | | |
34 | 37 | | |
35 | 38 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
26 | 26 | | |
27 | 27 | | |
28 | 28 | | |
29 | | - | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
30 | 32 | | |
31 | 33 | | |
32 | 34 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
43 | 43 | | |
44 | 44 | | |
45 | 45 | | |
46 | | - | |
47 | | - | |
48 | | - | |
49 | | - | |
50 | | - | |
51 | 46 | | |
52 | 47 | | |
53 | 48 | | |
54 | 49 | | |
55 | 50 | | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
56 | 57 | | |
57 | 58 | | |
58 | 59 | | |
| |||
65 | 66 | | |
66 | 67 | | |
67 | 68 | | |
68 | | - | |
| 69 | + | |
69 | 70 | | |
70 | 71 | | |
71 | 72 | | |
| |||
74 | 75 | | |
75 | 76 | | |
76 | 77 | | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
77 | 84 | | |
78 | 85 | | |
79 | 86 | | |
| |||
107 | 114 | | |
108 | 115 | | |
109 | 116 | | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
110 | 123 | | |
111 | 124 | | |
112 | 125 | | |
| |||
347 | 360 | | |
348 | 361 | | |
349 | 362 | | |
| 363 | + | |
| 364 | + | |
| 365 | + | |
| 366 | + | |
350 | 367 | | |
351 | 368 | | |
352 | 369 | | |
353 | 370 | | |
| 371 | + | |
| 372 | + | |
| 373 | + | |
| 374 | + | |
354 | 375 | | |
355 | 376 | | |
356 | 377 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
10 | 10 | | |
11 | 11 | | |
12 | 12 | | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
13 | 21 | | |
14 | 22 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
| 2 | + | |
| 3 | + | |
2 | 4 | | |
3 | 5 | | |
4 | 6 | | |
5 | 7 | | |
6 | | - | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
7 | 11 | | |
8 | | - | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
9 | 42 | | |
10 | 43 | | |
11 | 44 | | |
12 | 45 | | |
13 | 46 | | |
14 | 47 | | |
| 48 | + | |
15 | 49 | | |
16 | 50 | | |
17 | 51 | | |
| |||
160 | 194 | | |
161 | 195 | | |
162 | 196 | | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
240 | 240 | | |
241 | 241 | | |
242 | 242 | | |
243 | | - | |
| 243 | + | |
| 244 | + | |
| 245 | + | |
244 | 246 | | |
245 | 247 | | |
246 | 248 | | |
| |||
301 | 303 | | |
302 | 304 | | |
303 | 305 | | |
| 306 | + | |
| 307 | + | |
| 308 | + | |
| 309 | + | |
| 310 | + | |
304 | 311 | | |
305 | 312 | | |
306 | 313 | | |
307 | 314 | | |
308 | | - | |
309 | | - | |
310 | 315 | | |
311 | 316 | | |
312 | 317 | | |
| |||
0 commit comments