@vintasoftware/pr-review-canvas 0.4.0 → 0.5.0

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.
Files changed (83) hide show
  1. package/README.md +27 -8
  2. package/docs/reference.md +247 -62
  3. package/package.json +1 -1
  4. package/pr-review.config.example.yml +38 -4
  5. package/prompts/generation-format.md +120 -26
  6. package/prompts/generation-strict-incremental.md +53 -0
  7. package/prompts/generation-strict.md +1 -27
  8. package/prompts/generation-surfacing-incremental.md +56 -0
  9. package/prompts/generation-surfacing.md +1 -58
  10. package/prompts/judging-strict.md +27 -0
  11. package/prompts/judging-surfacing.md +58 -0
  12. package/skills/pr-review-canvas/SKILL.md +13 -4
  13. package/src/acpx/acpx.ts +98 -5
  14. package/src/acpx/models.ts +43 -0
  15. package/src/chat/chat-manager.ts +27 -1
  16. package/src/cli.ts +58 -1
  17. package/src/commands.ts +5 -1
  18. package/src/contract/api.ts +26 -1
  19. package/src/contract/canvas-manifest.ts +5 -0
  20. package/src/contract/generation-context.ts +52 -1
  21. package/src/contract/keys.ts +1 -0
  22. package/src/contract/pending.ts +49 -0
  23. package/src/contract/review-artifact.ts +50 -7
  24. package/src/contract/reviews.ts +10 -1
  25. package/src/contract/settings.ts +5 -0
  26. package/src/contract/state.ts +23 -12
  27. package/src/contract/validation.ts +1 -0
  28. package/src/github/post-review.ts +62 -6
  29. package/src/gitlab/post-review.ts +48 -9
  30. package/src/gitlab/publish-drafts.ts +69 -0
  31. package/src/host/client.ts +3 -2
  32. package/src/host/host.ts +23 -5
  33. package/src/project-config.ts +15 -2
  34. package/src/review/carry-marks.ts +131 -0
  35. package/src/review/doctor.ts +60 -26
  36. package/src/review/incremental.ts +107 -0
  37. package/src/review/normalize.ts +14 -4
  38. package/src/review/prepare.ts +47 -0
  39. package/src/review/prompt.ts +112 -5
  40. package/src/review/publish.ts +1 -0
  41. package/src/review/test-paths.ts +44 -4
  42. package/src/review/validate-folds.ts +348 -23
  43. package/src/review/validate.ts +10 -1
  44. package/src/server/bundle.ts +14 -2
  45. package/src/server/html.ts +4 -4
  46. package/src/server/routes/chat-routes.ts +15 -6
  47. package/src/server/routes/pages.ts +4 -1
  48. package/src/server/routes/review-routes.ts +202 -42
  49. package/src/store/canvas-store.ts +3 -0
  50. package/src/store/settings-store.ts +9 -1
  51. package/src/store/state-store.ts +69 -4
  52. package/src/upgrade.ts +338 -0
  53. package/static/js/api.js +55 -1
  54. package/static/js/app.js +28 -7
  55. package/static/js/chat-panel.js +32 -9
  56. package/static/js/chat.js +27 -4
  57. package/static/js/code-folds.js +171 -44
  58. package/static/js/composer.js +109 -4
  59. package/static/js/contract-types.d.ts +4 -0
  60. package/static/js/diff-decorations.js +67 -1
  61. package/static/js/empty-state.js +17 -0
  62. package/static/js/fold-levels.js +176 -0
  63. package/static/js/header.js +36 -9
  64. package/static/js/interactions.js +273 -44
  65. package/static/js/keyboard.js +4 -1
  66. package/static/js/keys.js +12 -0
  67. package/static/js/layers.js +292 -29
  68. package/static/js/nav.js +22 -4
  69. package/static/js/pending.js +161 -0
  70. package/static/js/points.js +69 -9
  71. package/static/js/progress.js +4 -5
  72. package/static/js/quick-questions.js +15 -2
  73. package/static/js/reading-level.js +97 -0
  74. package/static/js/review-session.js +106 -27
  75. package/static/js/settings.js +53 -23
  76. package/static/js/signoff.js +75 -5
  77. package/static/js/skin.js +2 -2
  78. package/static/styles/chat-panel.css +22 -24
  79. package/static/styles/chat.css +4 -0
  80. package/static/styles/header.css +21 -0
  81. package/static/styles/pending.css +102 -0
  82. package/static/styles/review.css +4 -0
  83. package/static/styles.css +1 -0
package/README.md CHANGED
@@ -63,6 +63,18 @@ Install the command globally once, for use in any project:
63
63
  npm install -g @vintasoftware/pr-review-canvas
64
64
  ```
65
65
 
66
+ ### Upgrade
67
+
68
+ Run this from a project that has the skill installed:
69
+
70
+ ```bash
71
+ pr-review upgrade
72
+ ```
73
+
74
+ It updates pr-review, acpx, and the project's skill copies, after showing the plan and asking.
75
+ Commit and push any refreshed skill copies so your team uses the same skill. `--yes` skips the
76
+ question.
77
+
66
78
  ## Set up a project
67
79
 
68
80
  ```bash
@@ -73,9 +85,8 @@ pr-review doctor --all-checks
73
85
 
74
86
  `install-skill` sets up **both Claude Code and Codex** in one command: `.claude/skills/pr-review-canvas`
75
87
  and `.agents/skills/pr-review-canvas`, respectively. These are portable copies you can commit to Git.
76
- Re-run `pr-review install-skill` after upgrading the CLI to refresh them. It also adds `.pr-review/settings.yml` to the
77
- project's `.gitignore`. Restart your coding agent if the skill
78
- does not appear. Repeat this setup for each project you want to review.
88
+ `pr-review upgrade` refreshes them after the CLI changes. It also adds `.pr-review/settings.yml` to the
89
+ project's `.gitignore`. Restart your coding agent if the skill does not appear. Repeat this setup for each project you want to review.
79
90
 
80
91
  `doctor` checks Git, your GitHub or GitLab remote, the matching CLI (`gh` or `glab`) and its login,
81
92
  write access to the local canvas directory, and whether installed skills match the current package.
@@ -91,7 +102,7 @@ or modified. The warning includes the reinstall command and does not block start
91
102
  To ask questions about a PR inside the canvas, install `acpx` globally:
92
103
 
93
104
  ```bash
94
- npm install -g acpx
105
+ npm install -g acpx@latest
95
106
  acpx --version
96
107
  pr-review doctor --all-checks
97
108
  ```
@@ -131,17 +142,25 @@ merged `main` in without touching the changed files, for example, the canvas sti
131
142
  and the page says so. Set `canvas.keepForIdenticalDiff: false` in the project config to treat
132
143
  every commit as a new head.
133
144
 
145
+ Regenerating for a new head updates the previous canvas. Content for untouched files is kept,
146
+ and your review progress on them follows. `--force` starts from a blank page, and
147
+ `canvas.incremental: false` turns this off. See [Incremental canvases](docs/reference.md#incremental-canvases).
148
+
134
149
  ## Configuration
135
150
 
136
151
  ### User-local preferences
137
152
 
138
- Your appearance and AI Chat preferences are saved in `.pr-review/settings.yml` in your local
139
- project directory. This file is ignored by Git, so each teammate can use their own settings.
153
+ Your appearance, reading level, and AI Chat preferences are saved in `.pr-review/settings.yml`
154
+ in your local project directory. This file is ignored by Git, so each teammate can use their own
155
+ settings.
140
156
 
141
- Use **skin** and **theme** in the header to change the appearance.
157
+ Use **skin** and **theme** in the header to change the appearance, and **Hide code by default**
158
+ in the settings dialog to choose how much code a review hides when it opens.
142
159
 
143
160
  For AI Chat, open **settings**, choose Claude Code or Codex, and optionally enter a model ID.
144
- Leave the model blank to use the agent's default. You can also adjust the reply timeout and
161
+ Leave the model blank to use the agent's default. A model ID runs as the newest model of its family
162
+ (`claude-opus-4-8` runs as `opus`); write `pin:claude-opus-4-8` to use that exact model. See
163
+ [Model families](docs/reference.md#model-families). You can also adjust the reply timeout and
145
164
  maximum turns. Click **Test agent** to check the connection, then **save**.
146
165
 
147
166
  Switching agents starts a new thread and keeps earlier threads. Server flags `--agent` and
package/docs/reference.md CHANGED
@@ -15,15 +15,15 @@ For setup and the basic review workflow, see the [README](../README.md).
15
15
 
16
16
  ### Repository and runtime options
17
17
 
18
- | Option | Applies to | Default and behavior |
19
- | -------------------------------- | -------------------------- | ---------------------------------------------------------------------------------------------------------------- |
20
- | `--repo <dir>` | All commands | Uses the current directory when omitted; resolves the repository root from there |
21
- | `--data-dir <dir>` | All except `install-skill` | Overrides `PR_REVIEW_DATA_DIR`, then the default `<main checkout>/.pr-review` |
22
- | `--port <n>` | `serve` | Overrides `PR_REVIEW_PORT`, then `3010`; accepts 1–65535 |
23
- | `--agent claude\|codex` | `serve` | Overrides the saved chat agent for this run |
24
- | `--model <id>` | `serve` | Overrides the saved chat model for this run |
25
- | `--fixture-canvas <review.json>` | `serve` | Development preview: uses the supplied canvas for every requested PR, with its head replaced by the live PR head |
26
- | `PR_REVIEW_HOST=gitlab` | Environment | Treats a non-github.com origin as GitLab (self-hosted hosts whose name does not contain `gitlab`) |
18
+ | Option | Applies to | Default and behavior |
19
+ | -------------------------------- | ---------------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
20
+ | `--repo <dir>` | All commands | Uses the current directory when omitted; resolves the repository root from there |
21
+ | `--data-dir <dir>` | All except `install-skill` and `upgrade` | Overrides `PR_REVIEW_DATA_DIR`, then the default `<main checkout>/.pr-review` |
22
+ | `--port <n>` | `serve` | Overrides `PR_REVIEW_PORT`, then `3010`; accepts 1–65535 |
23
+ | `--agent claude\|codex` | `serve` | Overrides the saved chat agent for this run |
24
+ | `--model <id>` | `serve` | Overrides the saved chat model for this run |
25
+ | `--fixture-canvas <review.json>` | `serve` | Development preview: uses the supplied canvas for every requested PR, with its head replaced by the live PR head |
26
+ | `PR_REVIEW_HOST=gitlab` | Environment | Treats a non-github.com origin as GitLab (self-hosted hosts whose name does not contain `gitlab`) |
27
27
 
28
28
  Repository operations require an `origin` remote on **github.com** or **GitLab** (gitlab.com, a
29
29
  hostname that contains `gitlab`, or any host with `PR_REVIEW_HOST=gitlab`). GitHub Enterprise Server
@@ -91,7 +91,9 @@ pr-review prepare --base origin/main --head HEAD
91
91
  ```
92
92
 
93
93
  `validate` checks the supplied file against the context in `--canvas`. By default it returns
94
- `{ ok, errors }`; `--human` prints readable diagnostics. `--fix` edits overlong titles by removing
94
+ `{ ok, errors }`; `--human` prints readable diagnostics. A `review.json` is checked for
95
+ correctness only. The folding rules (`FOLD_MISSING`, a test file collapsed at `light`) apply only
96
+ to a `model.json`, because older canvases predate them. `--fix` edits overlong titles by removing
95
97
  the explanation after the first `:` or `—` and reports the changes. Titles that still exceed the
96
98
  limit and overlong prose require rewriting.
97
99
 
@@ -188,14 +190,45 @@ Each installed `SKILL.md` records `metadata.body-sha256` in its YAML frontmatter
188
190
  covers the body after the closing frontmatter delimiter, with CRLF normalized to LF. `doctor`
189
191
  compares the recorded hash and actual body against the skill bundled with the running CLI. Any
190
192
  outdated or modified copy in `.claude/skills` or `.agents/skills` fails the skill check, even if the
191
- other copy is current. Refresh copies with `pr-review install-skill` (repeat any custom directory
192
- flags used during installation). Automatic discovery checks the two default directories.
193
+ other copy is current. Refresh copies with `pr-review upgrade` or `pr-review install-skill` (repeat
194
+ any custom directory flags used during installation). Automatic discovery checks the two default directories.
193
195
 
194
196
  `serve` runs this skill check automatically and prints failures with a repair hint to stderr.
195
197
  Warnings do not prevent the server from starting. Use `doctor --all-checks` for full diagnostics.
196
198
  The `.gitignore` update always applies to the selected repository root, even with custom skill
197
199
  directories.
198
200
 
201
+ ### Upgrade options
202
+
203
+ ```text
204
+ pr-review upgrade [--yes] [--only package,acpx,skill] [--repo <dir>]
205
+ ```
206
+
207
+ `upgrade` checks three things, prints a plan to stderr, and asks `Proceed? [y/N]`:
208
+
209
+ | What | When it changes | How |
210
+ | ----------------- | --------------------------------------------------------------------------- | ---------------------------------------------------------- |
211
+ | pr-review | npm has a newer version, and this copy is the global npm install | `npm install -g @vintasoftware/pr-review-canvas@<version>` |
212
+ | acpx | npm has a newer version, and the acpx on PATH is the global npm install | `npm install -g acpx@<version>` |
213
+ | The project skill | A copy in `.claude/skills` or `.agents/skills` differs from the bundled one | The same copy `install-skill` makes |
214
+
215
+ When pr-review itself upgrades, the skill step covers every copy, since the new version may ship
216
+ a new skill. After installing, it runs the new version as `pr-review upgrade --yes --only <kinds>`
217
+ with the confirmed steps, so the new version copies its own skill and takes only the steps the plan
218
+ showed. If the install fails, the current version runs the remaining steps itself.
219
+
220
+ - `--only` limits the run to the named kinds. Skipped kinds are listed in `notes`.
221
+ - `upgrade` does not install a missing acpx or add a missing skill copy. It prints the command
222
+ that does.
223
+ - An unmanaged skill directory is left alone; replace it with `install-skill --force`.
224
+ - A pr-review run from a clone or through `npx` is left alone, with a note to update it the way it
225
+ was installed.
226
+
227
+ Without a terminal, `upgrade` prints the plan and changes nothing. `--yes` applies it without
228
+ asking. stdout is one JSON line with `applied` and, after applying, `ok` and each step's `status`
229
+ (`done` or `failed`, with a `detail`). The exit code is `1` when a step fails. When a skill copy
230
+ changes, stderr says to commit and push it.
231
+
199
232
  ### Output and exit codes
200
233
 
201
234
  One-shot commands normally print a JSON result on stdout. Preparation progress goes to stderr.
@@ -224,21 +257,22 @@ Lists you supply replace their defaults.
224
257
  Path patterns match repository-relative paths. `**` crosses directories; `*` and `?` match
225
258
  within one path segment.
226
259
 
227
- | Key | Default | Details |
228
- | ------------------------------- | --------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
229
- | `version` | `1` | The only supported configuration version |
230
- | `rulebook` | Unset | Path to a Markdown file of project code standards, resolved from the repository root; these standards take precedence over bundled standards |
231
- | `layers` | `[]` | Optional review guidance; each entry has `id`, `title`, `description`, and optional `paths` patterns. The agent may combine, split, or reorder groups. When omitted or empty, it chooses semantic sections from the change |
232
- | `highRisk` | `[]` | Entries with a `pattern` glob and `label`; matching changes receive risk labels and cannot go in the Other layer |
233
- | `generation.mode` | `strict` | See [generation modes](#generation-modes) |
234
- | `generation.maxRepairRounds` | `3` | Failed validation rounds allowed by the generation skill |
235
- | `generation.inlineDiffMaxLines` | `1500` | Maximum diff length to include directly in the generation prompt |
236
- | `generation.smallPrHunks` | `10` | At or below this hunk count, the prompt asks for one layer unless concerns differ |
237
- | `generation.caps` | See below | Overrides individual text limits |
238
- | `tests.patterns` | `['**/*.test.*', '**/*.spec.*', '**/__tests__/**']` | Paths treated as tests for review ordering and labels |
239
- | `chat.enabled` | `true` | Set to `false` to disable AI Chat |
240
- | `canvas.keepForIdenticalDiff` | `true` | Keep the canvas current for a later head whose diff is identical to the canvas's; see [outdated canvases](#outdated-canvases). Set to `false` to mark it outdated on every commit |
241
- | `prompts` | Bundled templates | See [prompt templates](#prompt-templates) for supported keys and behavior |
260
+ | Key | Default | Details |
261
+ | ------------------------------- | --------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
262
+ | `version` | `1` | The only supported configuration version |
263
+ | `rulebook` | Unset | Path to a Markdown file of project code standards, resolved from the repository root; these standards take precedence over bundled standards |
264
+ | `layers` | `[]` | Optional review guidance; each entry has `id`, `title`, `description`, and optional `paths` patterns. The agent may combine, split, or reorder groups. When omitted or empty, it chooses semantic sections from the change |
265
+ | `highRisk` | `[]` | Entries with a `pattern` glob and `label`; matching changes receive risk labels and cannot go in the Other layer |
266
+ | `generation.mode` | `strict` | See [generation modes](#generation-modes) |
267
+ | `generation.maxRepairRounds` | `3` | Failed validation rounds allowed by the generation skill |
268
+ | `generation.inlineDiffMaxLines` | `1500` | Maximum diff length to include directly in the generation prompt |
269
+ | `generation.smallPrHunks` | `10` | At or below this hunk count, the prompt asks for one layer unless concerns differ |
270
+ | `generation.caps` | See below | Overrides individual text limits |
271
+ | `tests.patterns` | Test directories and file-name shapes across stacks; see the example config | Paths treated as tests for review ordering, labels, and the light reading level |
272
+ | `chat.enabled` | `true` | Set to `false` to disable AI Chat |
273
+ | `canvas.keepForIdenticalDiff` | `true` | Keep the canvas current for a later head whose diff is identical to the canvas's; see [outdated canvases](#outdated-canvases). Set to `false` to mark it outdated on every commit |
274
+ | `canvas.incremental` | `true` | Regenerate a canvas for a new head by updating the newest canvas of a commit the head was built on; see [incremental canvases](#incremental-canvases). Set to `false` to generate every canvas from a blank page |
275
+ | `prompts` | Bundled templates | See [prompt templates](#prompt-templates) for supported keys and behavior |
242
276
 
243
277
  Generation's numeric options and text caps must be positive integers. An empty `layers` list
244
278
  provides no suggested groups; an empty `tests.patterns` list recognizes no files as tests.
@@ -259,14 +293,18 @@ affects canvas generation; chat answers the reviewer's selected question.
259
293
 
260
294
  The `prompts` map in `pr-review.config.yml` accepts these keys:
261
295
 
262
- | Key | Purpose |
263
- | ------------------------- | ------------------------------------- |
264
- | `generation-format.md` | Schema and output rules |
265
- | `generation-strict.md` | Instructions for strict mode |
266
- | `generation-surfacing.md` | Instructions for surfacing mode |
267
- | `quality-standards.md` | Bundled code standards |
268
- | `layering-guidance.md` | Guidance for grouping related changes |
269
- | `chat-seed.md` | Opening AI Chat instructions |
296
+ | Key | Purpose |
297
+ | ------------------------------------- | ----------------------------------------------------------- |
298
+ | `generation-format.md` | Schema and output rules |
299
+ | `generation-strict.md` | Instructions for strict mode |
300
+ | `generation-surfacing.md` | Instructions for surfacing mode |
301
+ | `generation-strict-incremental.md` | Strict mode, updating an existing canvas |
302
+ | `generation-surfacing-incremental.md` | Surfacing mode, updating an existing canvas |
303
+ | `judging-strict.md` | Strict mode's judging rules, shared by both of its tasks |
304
+ | `judging-surfacing.md` | Surfacing mode's judging rules, shared by both of its tasks |
305
+ | `quality-standards.md` | Bundled code standards |
306
+ | `layering-guidance.md` | Guidance for grouping related changes |
307
+ | `chat-seed.md` | Opening AI Chat instructions |
270
308
 
271
309
  Each configured file replaces a whole template. Paths resolve from the project root,
272
310
  including when running from a subdirectory or using `--repo`. Absolute paths work for
@@ -324,19 +362,54 @@ placed in Other while its source is in a regular layer.
324
362
 
325
363
  The data directory's `settings.yml` accepts these keys and values:
326
364
 
327
- | Key | Default | Accepted values |
328
- | ---------------- | ---------- | ------------------------------------------------ |
329
- | `version` | `1` | `1` |
330
- | `skin` | `terminal` | `terminal`, `github` |
331
- | `theme` | `auto` | `auto`, `light`, `dark` |
332
- | `agent` | `claude` | `claude`, `codex` |
333
- | `model` | `null` | A model ID, or `null` for the agent's default |
334
- | `chatTimeoutSec` | `600` | Integer seconds, 30–3600 |
335
- | `maxTurns` | `null` | Integer 1–100, or `null` for the agent's default |
365
+ | Key | Default | Accepted values |
366
+ | ---------------- | -------- | ------------------------------------------------- |
367
+ | `version` | `1` | `1` |
368
+ | `skin` | `github` | `terminal`, `github` |
369
+ | `theme` | `auto` | `auto`, `light`, `dark` |
370
+ | `foldLevel` | `light` | `light`, `moderate`, `aggressive` |
371
+ | `agent` | `claude` | `claude`, `codex` |
372
+ | `model` | `null` | A model ID, or `null` for the agent's default (1) |
373
+ | `chatTimeoutSec` | `600` | Integer seconds, 30–3600 |
374
+ | `maxTurns` | `null` | Integer 1–100, or `null` for the agent's default |
375
+
376
+ (1) A model ID names a family; see [Model families](#model-families).
336
377
 
337
378
  Invalid settings fall back to defaults. URL parameters `?skin=github&theme=light` can override
338
379
  appearance for one page load without saving it.
339
380
 
381
+ #### Model families
382
+
383
+ Each chat turn runs the newest model of the saved model's family. A trailing `[...]`, such as
384
+ `[1m]` or `[high]`, is kept.
385
+
386
+ - **Claude:** an Anthropic model ID becomes its family alias, which the `claude` CLI resolves to
387
+ its newest model. `claude-opus-4-8[1m]` runs as `opus[1m]`, and `claude-haiku-4-5-20251001` runs
388
+ as `haiku`.
389
+ - **Codex:** a GPT model follows the `upgrade` links in the Codex model catalog
390
+ (`codex debug models`) to the model that replaced it, even under a new name: `gpt-5.6-terra` runs
391
+ as `gpt-6-sol`. A model with no `upgrade` link runs as saved.
392
+ - **Blank model:** the agent's default applies. A thread still on a replaced model moves to its
393
+ replacement.
394
+
395
+ To pin an exact version, prefix the ID with `pin:`. The ID is then sent as written, for either
396
+ agent:
397
+
398
+ | Agent | Example | Runs |
399
+ | ------ | ------------------------- | -------------------------- |
400
+ | Claude | `pin:claude-opus-4-8` | Opus 4.8 |
401
+ | Codex | `pin:gpt-5.6-terra[high]` | GPT-5.6 Terra, high effort |
402
+
403
+ The agent must still offer the model. Claude Code refuses some combinations, for example
404
+ `claude-opus-4-8[1m]`, and the turn fails with the agent's error.
405
+
406
+ Bedrock and Vertex Claude IDs, such as `us.anthropic.claude-opus-4-8-v1:0` or
407
+ `claude-opus-4-8@20260801`, run as written without `pin:`. They work only when Claude Code is set
408
+ up for that provider, for example with `CLAUDE_CODE_USE_BEDROCK=1` or `CLAUDE_CODE_USE_VERTEX=1`.
409
+
410
+ Claude chat runs the `claude` CLI on PATH, or the Claude Code bundled with acpx when there is none.
411
+ To use another binary, set `CLAUDE_CODE_EXECUTABLE` before `pr-review serve`.
412
+
340
413
  By default, Git worktrees of the same clone share the main checkout's data directory. Separate
341
414
  clones have separate data. An explicit data-directory override also relocates `settings.yml`,
342
415
  canvases, review progress, and chat history.
@@ -370,6 +443,47 @@ start collapsed. Click the summary to expand them. Moves are detected within a f
370
443
  between files appear as deletions and additions. Edited moves keep their changed text visible.
371
444
  Collapsing content does not mark it reviewed.
372
445
 
446
+ ### Reading levels
447
+
448
+ The **Hide code** control, beside the review progress, sets how much of the canvas is hidden. The
449
+ levels nest: whatever `light` hides, `moderate` and `aggressive` hide too. The generator gives each
450
+ fold and each collapsed file the lowest level at which it hides.
451
+
452
+ | Level | What it hides |
453
+ | ------------ | ---------------------------------------------------------------------------------------- |
454
+ | `light` | Imports, whitespace, moves, and wholly generated files (the default) |
455
+ | `moderate` | Also test bodies under their titles, helpers, adapters, boilerplate, mappings and wiring |
456
+ | `aggressive` | Also any block its title explains, so the change reads as pseudo-code |
457
+
458
+ The control shows what the chosen level hides and how many lines that is. Each layer shows its count
459
+ in its Files heading, and the sign-off dialog records the total.
460
+
461
+ The page opens at `foldLevel` from `settings.yml` (`light` by default), which the **Hide code by
462
+ default** field in the settings dialog sets. The control and the `f` key change the level for the
463
+ current page only. Changing the level redraws the visible diffs and re-applies file collapse,
464
+ including cards opened by hand.
465
+
466
+ These always stay visible:
467
+
468
+ - Attention points, comment threads, and pending review drafts. A file with a thread or a draft
469
+ folds nothing.
470
+ - Files with an annotation or an attention point. They never collapse whole.
471
+ - Annotations, except under an `aggressive` fold that covers the whole annotation and no other. That
472
+ fold shows the annotation's text instead of its title.
473
+ - Test files at `light`, except snapshots and fixtures. From `moderate`, each test body folds under
474
+ its own title, or the file collapses whole.
475
+
476
+ Validation fails with `FOLD_MISSING` when the generator hides too little:
477
+
478
+ - A file with more than 20 changed lines outside its annotations and no attention point hides
479
+ nothing at any level.
480
+ - An open file over 60 lines folds less than half of its lines outside attention points by
481
+ `aggressive`. Annotated lines count, since an aggressive fold may hide them.
482
+ - A layer over 100 changed lines leaves more than 20 lines open at `moderate` (outside attention
483
+ points) and hides nothing more at `aggressive`. Smaller layers only need to pass the file rules.
484
+
485
+ A `light` fold may cover at most 40 lines of generated content.
486
+
373
487
  Files with patches longer than 2,000 lines wait behind **show diff**. A link into the file opens
374
488
  it automatically.
375
489
 
@@ -393,13 +507,56 @@ zone or `pr-review import <zip> --pr <n>`.
393
507
 
394
508
  You can post inline comments, replies, PR-level comments, and attention points. Inline comments
395
509
  must target lines in the diff. Posting uses your `gh` or `glab` account and remains subject to its
396
- repository permissions. On GitLab, **request changes** posts the review body as a merge request
397
- note; **approve** calls GitLab's approve API.
398
-
399
- The sign-off dialog previews an editable review body summarizing reviewed layers, dismissed
400
- attention points, and comments posted from the canvas. Approval requires every layer except
401
- **Other changes** to be reviewed for the current head. Requesting changes does not require that
402
- completion. If the head moves before submission, reload and review the current commit.
510
+ repository permissions.
511
+
512
+ Click a line number to comment on one line. Shift-click a second line number, or drag across a
513
+ range, to select several lines: the comment then covers the whole range, and posts as a multi-line
514
+ comment (`start_line` on GitHub, a `line_range` position on GitLab). A range must stay inside one
515
+ chunk of the diff.
516
+
517
+ ### Pending reviews
518
+
519
+ A pending review holds comments on your machine until you submit them together.
520
+
521
+ - **post to github** on a diff-line comment posts it at once. **start a review** adds it to a new
522
+ pending review instead.
523
+ - While a review is open, a diff-line comment can only **add review comment**, so no comment
524
+ publishes ahead of the review. Replies and pull-request comments are not part of a forge review,
525
+ so they still post at once.
526
+ - An attention point keeps both **post to github** and **add to review**, since its text is written
527
+ in advance. A point in the review shows **in your review**; edit or remove it as the draft on its
528
+ line. After submission, the point shows the comment it became.
529
+ - A bar under the progress line shows how many drafts are waiting. Each draft appears on its line
530
+ with a **pending** badge and edit and delete commands. Drafts are saved in the local review state
531
+ and survive a reload. **discard** drops the whole review; nothing was sent to the forge.
532
+ - Drafts from an earlier commit are listed separately in the bar with their original location and
533
+ commit. They are submitted only when the stored diff is identical to the current one and
534
+ `canvas.keepForIdenticalDiff` is on. Otherwise, copy the text, delete the draft, and comment on
535
+ the current code.
536
+ - A draft added or edited during submission stays pending.
537
+
538
+ **finish your review** opens the sign-off dialog, which shows how many drafts go out with the
539
+ review:
540
+
541
+ - **GitHub:** the drafts are the comments of the single call that creates the review.
542
+ - **GitLab:** the inline comments and summary are staged as draft notes and published in one batch.
543
+ Finish or discard any review already pending in GitLab first. Approval is a separate call; if it
544
+ fails after publication, the comments stay published and the page asks you to approve in GitLab.
545
+
546
+ A refused submission keeps the local drafts and removes the remote drafts that attempt staged.
547
+
548
+ ### Sign-off
549
+
550
+ Sign-off offers three verdicts: **approve**, **request changes**, and **comment**, which posts a
551
+ review with no verdict. Each opens a
552
+ dialog previewing an editable review body summarizing reviewed layers, dismissed attention points,
553
+ and comments posted from the canvas, so an approval or a rejection always carries a comment.
554
+
555
+ Approval requires every layer except **Other changes** to be reviewed for the current head.
556
+ Requesting changes and a comment-only review do not require that completion. On GitLab,
557
+ **approve** calls GitLab's approve API; **request changes** and **comment** post the review body
558
+ as a merge request note. If the head moves before submission, reload and review the current
559
+ commit.
403
560
 
404
561
  ### Outdated canvases
405
562
 
@@ -423,6 +580,33 @@ same rule, so the CLI never calls a canvas stale that the page shows as current.
423
580
  AI Chat also answers on an outdated canvas: it quotes the diff of the canvas's own commit, the
424
581
  one on screen.
425
582
 
583
+ ## Incremental canvases
584
+
585
+ When a canvas is regenerated for a new head, `pr-review prepare` starts from the **basis canvas**:
586
+ the newest canvas of a commit the head was built on. A canvas of a commit the head no longer
587
+ contains is never a basis. `--force` starts from a blank page, and `canvas.incremental: false`
588
+ turns this off for the project.
589
+
590
+ `prepare` compares the basis diff with the head diff file by file. A file whose patch is
591
+ byte-identical is untouched. The prompt tells the generator to copy, word for word:
592
+
593
+ - whole layers whose files are all untouched;
594
+ - in other layers, the notes, folds, annotations, and attention points of untouched files.
595
+
596
+ Everything else is decided again. The summary and risk tags are always rewritten. A carried
597
+ attention point keeps its kind, path, and title, so it keeps its fingerprint and any dismissal.
598
+
599
+ The canvas records only which basis it came from. Your server decides which review marks follow,
600
+ using the two canvases and your clone:
601
+
602
+ - A file's mark follows when the file is in both canvases, under the same layer key, with a
603
+ byte-identical patch.
604
+ - A layer's mark follows only when the layer has exactly the same files and none changed.
605
+
606
+ The canvas you marked is compared directly with the one on screen, so marks survive any number of
607
+ regenerations in between. When a mark follows, the page names the canvas you made it on. If your
608
+ machine lacks that canvas or cannot rebuild either diff, no marks follow.
609
+
426
610
  ## AI Chat
427
611
 
428
612
  **Ask** on a layer, file, attention point, or line selection chooses the context for your message.
@@ -481,14 +665,15 @@ sandbox for the agent. Its access also depends on the agent's own permissions. D
481
665
 
482
666
  Validation reports name the field, file, hunk, or line to fix. Common groups are:
483
667
 
484
- | Codes | What to check |
485
- | --------------------------------------------------------------- | -------------------------------------------------------------- |
486
- | `SCHEMA`, `TEXT_TOO_LONG` | Required fields, types, and text limits |
487
- | `HUNK_UNASSIGNED`, `HUNK_DUPLICATE`, `HUNK_UNKNOWN` | Each known hunk belongs to exactly one layer |
488
- | `PATH_UNKNOWN`, `TEST_PATH_UNKNOWN` | Referenced files exist in the relevant diff or PR head |
489
- | `LAYER_EMPTY`, `LAYER_KEY_DUPLICATE` | Layers contain hunks and have unique keys |
490
- | `OTHER_DUPLICATE`, `OTHER_NOT_LAST`, `RISK_IN_OTHER` | At most one Other layer, last, without risk-tagged changes |
491
- | `TEST_NOT_LAST`, `TEST_IN_OTHER` | Tests follow the code they cover and use the appropriate layer |
492
- | `ANNOTATION_OUTSIDE_HUNK`, `POINT_OUTSIDE_DIFF`, `FOLD_INVALID` | Locations and fold ranges fit the assigned diff |
493
- | `TOO_MANY_POINTS` | Count explicit points and missing-test entries together |
494
- | `LINK_UNRESOLVED`, `DIAGRAM_NODE_UNKNOWN`, `DIAGRAM_LIMIT` | Link targets, diagram node IDs, and diagram counts |
668
+ | Codes | What to check |
669
+ | --------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
670
+ | `SCHEMA`, `TEXT_TOO_LONG` | Required fields, types, and text limits |
671
+ | `HUNK_UNASSIGNED`, `HUNK_DUPLICATE`, `HUNK_UNKNOWN` | Each known hunk belongs to exactly one layer |
672
+ | `PATH_UNKNOWN`, `TEST_PATH_UNKNOWN` | Referenced files exist in the relevant diff or PR head |
673
+ | `LAYER_EMPTY`, `LAYER_KEY_DUPLICATE` | Layers contain hunks and have unique keys |
674
+ | `OTHER_DUPLICATE`, `OTHER_NOT_LAST`, `RISK_IN_OTHER` | At most one Other layer, last, without risk-tagged changes |
675
+ | `TEST_NOT_LAST`, `TEST_IN_OTHER` | Tests follow the code they cover and use the appropriate layer |
676
+ | `ANNOTATION_OUTSIDE_HUNK`, `POINT_OUTSIDE_DIFF`, `FOLD_INVALID` | Locations and fold ranges fit the assigned diff |
677
+ | `FOLD_MISSING` | A file over 20 unannotated lines hides something; an open file over 60 folds half; a layer over 100 hides more at aggressive |
678
+ | `TOO_MANY_POINTS` | Count explicit points and missing-test entries together |
679
+ | `LINK_UNRESOLVED`, `DIAGRAM_NODE_UNKNOWN`, `DIAGRAM_LIMIT` | Link targets, diagram node IDs, and diagram counts |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vintasoftware/pr-review-canvas",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "Localhost PR review canvas: layered diffs, attention points, and GitHub or GitLab comments for one PR",
5
5
  "type": "module",
6
6
  "bin": {
@@ -39,11 +39,41 @@ generation:
39
39
  # rationale: 300
40
40
  # diagram: 1500 # mermaid source, counted raw
41
41
 
42
- # Which paths count as tests, for the layering rules and the `test` pill on a file card.
43
- # Defaults to the JavaScript and TypeScript conventions below; name your own for other languages
44
- # (Python: '**/test_*.py', '**/tests/**'; Go: '**/*_test.go').
42
+ # Which paths count as tests, for the layering rules, the `test` pill on a file card, and the
43
+ # light reading level, which keeps test titles on screen. The defaults below match test
44
+ # directories and file-name shapes across stacks (JS/TS, Python, Go, Ruby, Java, Kotlin, PHP,
45
+ # C#, Swift, Elixir, Dart, Rust). A list here replaces them; add `'**/spec/**'` when that
46
+ # directory holds tests in your repository. Snapshots and fixtures inside test paths may still
47
+ # hide at light, since they are generated.
45
48
  tests:
46
- patterns: ['**/*.test.*', '**/*.spec.*', '**/__tests__/**']
49
+ patterns:
50
+ - '**/__tests__/**'
51
+ - '**/tests/**'
52
+ - '**/test/**'
53
+ - '**/*.test.*'
54
+ - '**/*.spec.*'
55
+ - '**/*_test.*'
56
+ - '**/*_spec.*'
57
+ - '**/test_*.py'
58
+ - '**/conftest.py'
59
+ - '**/*Test.java'
60
+ - '**/*Tests.java'
61
+ - '**/*Test.kt'
62
+ - '**/*Tests.kt'
63
+ - '**/*Test.scala'
64
+ - '**/*Tests.scala'
65
+ - '**/*Test.groovy'
66
+ - '**/*Tests.groovy'
67
+ - '**/*Test.cs'
68
+ - '**/*Tests.cs'
69
+ - '**/*Test.fs'
70
+ - '**/*Tests.fs'
71
+ - '**/*Test.vb'
72
+ - '**/*Tests.vb'
73
+ - '**/*Test.swift'
74
+ - '**/*Tests.swift'
75
+ - '**/*Test.php'
76
+ - '**/*Tests.php'
47
77
 
48
78
  chat:
49
79
  enabled: true # false removes the chat pane and its routes
@@ -53,3 +83,7 @@ canvas:
53
83
  # from, as after merging the base branch in. Set false to mark the canvas outdated on every new
54
84
  # commit.
55
85
  keepForIdenticalDiff: true
86
+ # Regenerating a canvas for a new head starts from the newest canvas of a commit the head was
87
+ # built on, keeping the layers, folds, notes and attention points its diff leaves untouched.
88
+ # Set false to generate every canvas from a blank page, as `--force` always does.
89
+ incremental: true