@vernikr/size-report 2.8.3 → 2.8.4

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/README.md CHANGED
@@ -10,7 +10,7 @@ shows.
10
10
 
11
11
  ## Status
12
12
 
13
- **Release 2.8.3 (2026-09-18).** The tool lives as a package of its own: the registry name is
13
+ **Release 2.8.4 (2026-09-18).** The tool lives as a package of its own: the registry name is
14
14
  `@vernikr/size-report` (published by tag from CI, with no secret). A project may keep no settings at
15
15
  all: without a config file the tool derives them from the project itself and says so in one line,
16
16
  and `--init` pins what was derived into a file. The report is **one file**, the self-contained page
@@ -19,13 +19,28 @@ is installed and on the first run. The version is in the manifest, and every rel
19
19
  the journal — `worklog/` for today's entries, `worklog/archive/WORKLOG.md` for the earlier ones:
20
20
  what changes in the numbers is measured rather than retold.
21
21
 
22
+ 2.8.4 is about the volume of the tree rather than of a report, and it is a sweep — no feature. **The six probes that
23
+ measured the page's steps are archived** (`probes/archive/`, 1 458 lines): the plan they served is settled, its figures
24
+ stand in `plans/2026-09-17-page-perf/` and in the worklog, so the instruments stay as the record of how the numbers were
25
+ taken, with every reference following them. **The open notes are closed**: the help's continuation lines are no longer
26
+ read as commands (the list falls from ten words to six), an advice without a flag is a command again, two assertions
27
+ that could not fail are gone (`test/check.test.js`, `test/gates-verify.test.js`), the last two Russian comments left
28
+ `src/` (the locale's strings stay — they are data), and `tools/run-tests.js` hands its extra flags to `node --test`
29
+ **before** the file, where node 22 reads them. **The copies below the sensors' thresholds went too**: one helper
30
+ spawns the frozen copy instead of two builders' own, one function sets the identity git refuses to commit without
31
+ (three sites had grown the loop), and a suite's temporary directory removes itself when the process ends — 25 suites
32
+ opened with the same pair of lines. Nine `export` keywords on names nothing outside their module reads left the
33
+ surface, and `TODO.md` fell from 96 lines to 31. The checks are at **87** in the fast profile and **192** in the full
34
+ one, none added and none removed; the code of the tree changed by −44 lines. The figures and the reasoning stand in
35
+ `worklog/0213-the-volume-swept-and-the-probes-archived.md`.
36
+
22
37
  2.8.3 is about what a sideways scroll of the report costs, and its figures are measured rather than retold. **A step
23
38
  sideways moves the window instead of building it:** a row is made once and lives through the move, keeping its node and
24
39
  its numbers — the window's columns are the range of that row's numbers, so the columns that entered it are made and the
25
40
  ones that left are dropped, the header's line of names and its line of metrics by the same rule. A switch of the
26
41
  reader's own and the shell's resize are the one thing that cannot be a move — a column that is not built cannot be
27
42
  shown — and they build the window from nothing as they did. **Measured on this repository's own report in live Chrome**
28
- (`probes/step-12-window.mjs`, two rounds, 1440 × 900): the page's own handling of one step **10.4 → 0.6 ms** (worst
43
+ (`probes/archive/step-12-window.mjs`, two rounds, 1440 × 900): the page's own handling of one step **10.4 → 0.6 ms** (worst
29
44
  19.2 → 3.6) and the layout the step forces **14.4 → 5.9 ms**; over a sweep of forty steps the browser's own accounting
30
45
  gives layout **0.605 → 0.246 s**, style recalculation 0.169 → 0.056 s and task time **1.281 → 0.350 s**. The checks go
31
46
  86 → **87** in the fast profile and 191 → **192** in the full one: `test/page-grid.test.js` holds the rule by node
@@ -104,7 +119,7 @@ class changes are about 2 µs a node; on this page the whole Table of Contents o
104
119
  the same click in 37 slices paid that relayout 37 times — **169 layouts and 151.9 s of pure layout time against
105
120
  1.04 s**, with the tab growing to gigabytes of repaint and the reader waiting four minutes for a switch that
106
121
  costs him less than a second unsliced. Within one task nothing can repaint, so a bar that filled would be a bar
107
- nobody could read: the stripe travels and says the work is going on (`probes/step-12-columns.mjs`, the records in
122
+ nobody could read: the stripe travels and says the work is going on (`probes/archive/step-12-columns.mjs`, the records in
108
123
  `worklog/0207-page-choice-bar.md`). The order of the columns did not change there: what the last commit touched
109
124
  still stands first, the rest after it in the settings' order.
110
125
 
@@ -409,7 +424,7 @@ left it and what entered it. The header sticks to the top of the shell and the c
409
424
  number belongs to and the file it stands under are always in sight. The price of a table is what this step removed:
410
425
  238 500 cells were 253 770 nodes in the document and 377 396 in the browser, **1.46 GB** of a fresh Chrome's memory at
411
426
  rest, and any change of a column's visibility meant the browser laying the whole of it out again — 234.5 ms of layout for
412
- a *single* cell of this repository's report, 539 ms for 6 000 (`probes/step-12-columns.mjs`). A window of the same
427
+ a *single* cell of this repository's report, 539 ms for 6 000 (`probes/archive/step-12-columns.mjs`). A window of the same
413
428
  table is **2 460 nodes and 0.15 GB**, a hundredth of the nodes and a tenth of the memory, and the whole-table scroll
414
429
  pass that took 36 s of task time over 60 steps takes **1.1 s over 251** (`worklog/0208-table-window.md`). **The columns
415
430
  are one width and fixed, 70px each**: the numbers are short and of one kind, and a width that came out of the text is a
@@ -423,7 +438,7 @@ and in the script that counts the window's ordinals in them, and `test/page-grid
423
438
  together. **A step sideways moves that window rather than building it:** the rows live through the move and only the
424
439
  columns that entered are made, at 10.4 → **0.6 ms** for the page's own handling of the step, 14.4 → **5.9 ms** for the
425
440
  layout it forces and 1.281 → **0.350 s** of task time over a sweep of forty steps
426
- (`probes/step-12-window.mjs`).
441
+ (`probes/archive/step-12-window.mjs`).
427
442
 
428
443
  **A click builds the window again, and that is cheap now.** A file switched off is simply not among the columns that
429
444
  are built, and a metric switched off not among the metrics: there is nothing to hide and nothing to carry, and the
@@ -602,7 +617,7 @@ acceptance for each.
602
617
  | `tools/parity-freeze.js` | Takes the parity reference (`pnpm run parity`): with the frozen copy, at the project revision from the manifest — `--json`, the config, the artifact's hash, the tool's hash |
603
618
  | `tools/make-fixture.js` | Assembles the synthetic fixture (`pnpm run fixture`): a deterministic history with traps plus the reference numbers |
604
619
  | `tools/synthetic/` | The subjects of that assembly, one per matter: `repo.js` — how git is spoken to (pinned time, author, settings), `content.js` — what the files hold, `history.js` — which commits come of it, `note.js` — the fixture's note with the list of traps |
605
- | `probes/` | The scripted measurements behind `plans/2026-09-17-page-perf/`: one file per step, run by hand against live Chrome at the debug port — the fixed layout, where `content-visibility` acts at all, the price of a switch and of a step sideways (`probes/README.md`). Outside the sensors' paths on purpose: they measure the product rather than being part of it, and a suite cannot see layout, paint or a browser's own skipping |
620
+ | `probes/` | The archived scripted measurements behind `plans/2026-09-17-page-perf/` (`probes/archive/`, one file per step, run by hand against live Chrome at the debug port — the fixed layout, where `content-visibility` acts at all, the price of a switch and of a step sideways) and their index (`probes/README.md`). Kept as the record of how the plan's figures were taken; outside the sensors' paths on purpose: they measure the product rather than being part of it, and a suite cannot see layout, paint or a browser's own skipping |
606
621
  | `tools/parity-live.js` | Compares the engine with the live project on a clone: the numbers and the self-contained report at the path the consumer's settings give (`pnpm run parity:live`) |
607
622
  | `tools/pack-check.js` | Assembles the tarball and checks that everything works from it: all sources arrived, the numbers and the report as from the repository (`pnpm run pack:check`) |
608
623
  | `tools/check-standards.js` | Checks that both references reproduce: a re-take goes nowhere and is compared with what is committed (our files byte for byte, the bundle by content), and that the live-history bundle carries `HEAD` (`pnpm run check:standards`) |
@@ -771,7 +786,7 @@ The same release can be taken by a reference to the repository — installation
771
786
  registry, but stays tied to a revision:
772
787
 
773
788
  ```bash
774
- pnpm add -D github:vernikr/size-report#v2.8.3
789
+ pnpm add -D github:vernikr/size-report#v2.8.4
775
790
  ```
776
791
 
777
792
  With no network (or nothing to fetch from codeload) — the tarball: `pnpm pack` in the package clone, then
@@ -783,7 +798,7 @@ the branch moves on the installation fails with `Could not resolve <sha> to a co
783
798
  observation rather than reasoning: the short pin `6530237` installed while `main` stood on it and stopped
784
799
  working at the very next commit, while the same sha in full installed. A branch name (`#main`) and a tag
785
800
  are both accepted, but a branch is a moving target and a tag is constant: this release stands on the tag
786
- `v2.8.3`, which is also the one in the example (forty characters work as well, but they have to be copied
801
+ `v2.8.4`, which is also the one in the example (forty characters work as well, but they have to be copied
787
802
  out of the history by eye).
788
803
 
789
804
  The revision in the example is a part of the claim rather than decoration: what is described below is
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vernikr/size-report",
3
- "version": "2.8.3",
3
+ "version": "2.8.4",
4
4
  "author": "vernikr",
5
5
  "repository": {
6
6
  "type": "git",
@@ -65,7 +65,7 @@
65
65
  "report"
66
66
  ],
67
67
  "devDependencies": {
68
- "@vernikr/size-report": "2.8.2",
68
+ "@vernikr/size-report": "2.8.3",
69
69
  "c8": "10",
70
70
  "dependency-cruiser": "17",
71
71
  "eslint": "^9.18.0",
package/src/config.js CHANGED
@@ -76,7 +76,7 @@ export function gitRoot() {
76
76
  * request for that very file, so its absence stays a refusal (otherwise a typo in the path would
77
77
  * silently yield someone else's settings). `path` is given in words rather than as a path: there is no
78
78
  * file, and "edit <path>" would lead the reader to something the project does not have. */
79
- export function derivedConfig(root) {
79
+ function derivedConfig(root) {
80
80
  const cfg = derivedProfile(root);
81
81
  cfg.path = 'derived from the project';
82
82
  cfg.derived = true;
package/src/hook.js CHANGED
@@ -2,10 +2,9 @@ import fs from 'fs';
2
2
  import path from 'path';
3
3
  import { fileURLToPath } from 'url';
4
4
  import { EXIT, Refusal, cliCommand, refuseCause } from './refusal.js';
5
- import { installSpec } from './tool.js';
5
+ import { TOOL_PKG, installSpec } from './tool.js';
6
6
  import { git, gitTry } from './git.js';
7
7
  import { loadConfig } from './config.js';
8
- import { TOOL_PKG } from './tool.js';
9
8
  import { rebuild } from './artifact.js';
10
9
 
11
10
  /* The `post-commit` and `post-merge` hooks: after every commit and merge the report rebuilds itself and, when it is tracked
@@ -120,6 +119,16 @@ function script(entry) {
120
119
  + 'exec node ' + entry.quoted + ' hook-run\n';
121
120
  }
122
121
 
122
+ /* Writing the hook files, executable: the named command and the silent install write the same bytes, since a difference
123
+ * between them would surface only after somebody's commit. */
124
+ function installFiles(hooks, entry, files) {
125
+ fs.mkdirSync(hooks.dir, { recursive: true });
126
+ files.forEach((file) => {
127
+ fs.writeFileSync(file, script(entry));
128
+ fs.chmodSync(file, 0o755);
129
+ });
130
+ }
131
+
123
132
  /* The hook's state for `size doctor`: whether it is installed and how its last run ended. It counts nothing and commits
124
133
  * to nothing. */
125
134
  export function hookStatus(root) {
@@ -180,11 +189,7 @@ export function installHook(root, cfg) {
180
189
  ' to remove it: ' + cliCommand('uninstall-hook')
181
190
  ] };
182
191
  }
183
- fs.mkdirSync(hooks.dir, { recursive: true });
184
- files.forEach((file) => {
185
- fs.writeFileSync(file, script(entry));
186
- fs.chmodSync(file, 0o755);
187
- });
192
+ installFiles(hooks, entry, files);
188
193
 
189
194
  const lines = [
190
195
  '✓ hook: ' + rels.join(', '),
@@ -219,11 +224,7 @@ export function autoInstall(root, cfg) {
219
224
  const files = HOOKS.map((name) => path.join(hooks.dir, name));
220
225
  if (files.some((f) => fs.existsSync(f) && !isOurs(f))) return null;
221
226
  if (files.every(isOurs)) return null;
222
- fs.mkdirSync(hooks.dir, { recursive: true });
223
- files.forEach((file) => {
224
- fs.writeFileSync(file, script(entry));
225
- fs.chmodSync(file, 0o755);
226
- });
227
+ installFiles(hooks, entry, files);
227
228
  return files.map((f) => path.relative(root, f));
228
229
  } catch (_e) {
229
230
  /* Not a git repository, no rights on `.git`, a foreign format — all of it means one thing: there will be no automation
package/src/locales.js CHANGED
@@ -48,9 +48,6 @@ export const LOCALES = {
48
48
  + 'что выбрано в этом браузере.',
49
49
  linkExtra: 'Часть ссылки пропущена: в этом отчёте нет {n} названных имён. '
50
50
  + 'Остальное применено.',
51
- /* Данные отчёта лежат в файле сжатыми, и распаковывает их браузер. Текст называет причину
52
- * (браузера нет в списке тех, что умеют `DecompressionStream`) и что с этим делать — пустая
53
- * таблица не сказала бы читателю ничего. */
54
51
  unpack: 'Данные отчёта сжаты, а браузер не умеет их распаковать: в нём нет DecompressionStream. '
55
52
  + 'Обновите браузер — таблицу показывать не из чего.',
56
53
  note: 'Абсолютные размеры стоят один раз — в строке «{now}». Строк: {rows}: коммиты без '
package/src/metrics.js CHANGED
@@ -58,9 +58,6 @@ const STYLES = {
58
58
  }
59
59
  };
60
60
 
61
- /* Метрика объявляет, нужен ли ей текст блоба: `needsText: false` вместе с
62
- * `fromSize: true` означает «хватит размера объекта», и тогда содержимое не
63
- * читается вовсе (`git cat-file --batch` не вызывается). */
64
61
  export const METRICS = {
65
62
  raw: {
66
63
  label: 'raw',
@@ -208,7 +205,7 @@ function esbuildLoader(file, cfg) {
208
205
  /* The engine actually in force: the requested one may be unavailable, in which case the metric
209
206
  * counts another way — and that is announced (`sensorGaps`), or a different count would travel
210
207
  * into CI as the requested one. */
211
- export function minEngine(cfg) {
208
+ function minEngine(cfg) {
212
209
  if (cfg.minify.engine !== 'esbuild') return 'strip';
213
210
  return minifier().tool === null ? 'strip' : 'esbuild';
214
211
  }
package/src/page/table.js CHANGED
@@ -8,8 +8,8 @@ import { appData, appUi, appView } from './state.js';
8
8
  *
9
9
  * **Why not a `<table>`.** Measured on this repository's own report (238 500 cells = 370 758 nodes, a table 71 712 ×
10
10
  * 5 982 px): about 1.4 GB of a browser's memory, of which roughly half the nodes and half the painted area, and a
11
- * browser's relayout of it costs close to a second on any switch (`probes/step-12-columns.mjs`). `content-visibility:
12
- * auto`, the cheap way out, is ignored on a table row by Chrome 153 (`probes/step-10-tables.mjs`), so the answer is
11
+ * browser's relayout of it costs close to a second on any switch (`probes/archive/step-12-columns.mjs`). `content-visibility:
12
+ * auto`, the cheap way out, is ignored on a table row by Chrome 153 (`probes/archive/step-10-tables.mjs`), so the answer is
13
13
  * to build less rather than to promise the browser will skip it. A grid of `position: absolute` rows has no layout to
14
14
  * be redone: a row is placed by its `top`, a column by the `left` of the group of cells that starts it, and the
15
15
  * browser never measures a cell to decide a width — every column is `--col` wide (70px), which is what the numbers
@@ -36,7 +36,7 @@ import { appData, appUi, appView } from './state.js';
36
36
  * numbers, so a step sideways makes the columns that entered and drops the ones that left (`appStrip`) — a cell per
37
37
  * row, the header's two lines included. A row that left the window vertically is dropped and made again when the
38
38
  * reader comes back to it, which is what makes a jump down the table cost the same as a step. Building the rows again
39
- * on every sideways scroll — the first version of this step — is what `probes/step-12-window.mjs` measures against the
39
+ * on every sideways scroll — the first version of this step — is what `probes/archive/step-12-window.mjs` measures against the
40
40
  * splice (`worklog/0211-sideways-step.md`).
41
41
  *
42
42
  * The choice is applied by building the window again: the columns of a switched-off file are simply not among the
package/src/project.js CHANGED
@@ -204,7 +204,7 @@ function columnsOf(files, journal) {
204
204
  }
205
205
 
206
206
  export function projectConfig(root) {
207
- const output = outputOf(root);
207
+ const output = outputOf();
208
208
  const journal = journalOf(root);
209
209
  const files = allPaths(root);
210
210
  /* Only what git tracks becomes a column: a file absent at HEAD has nothing to measure (it would be
package/src/strip/js.js CHANGED
@@ -59,16 +59,21 @@ function regexAllowed(last, word) {
59
59
  return '([{,;:=!&|?+-*%~^<>'.indexOf(last) !== -1;
60
60
  }
61
61
 
62
- function takeRegex(s) {
63
- if (s.src[s.i] !== '/' || !regexAllowed(s.last, s.word)) return false;
64
- const end = endOfRegex(s.src, s.i);
62
+ /* A token taken whole: it goes into the output and the pass state is advanced over it — the token itself, not the word
63
+ * before it, decides where a regex may stand next, and the word being read ends with it. */
64
+ function take(s, end, last) {
65
65
  s.out += s.src.slice(s.i, end);
66
66
  s.i = end;
67
- s.last = '/';
67
+ s.last = last;
68
68
  s.word = '';
69
69
  return true;
70
70
  }
71
71
 
72
+ function takeRegex(s) {
73
+ if (s.src[s.i] !== '/' || !regexAllowed(s.last, s.word)) return false;
74
+ return take(s, endOfRegex(s.src, s.i), '/');
75
+ }
76
+
72
77
  function endOfRegex(src, start) {
73
78
  let i = start + 1;
74
79
  let inClass = false;
@@ -88,12 +93,7 @@ function endOfRegex(src, start) {
88
93
  function takeString(s) {
89
94
  const quote = s.src[s.i];
90
95
  if (quote !== '"' && quote !== "'" && quote !== '`') return false;
91
- const end = endOfString(s.src, s.i, quote);
92
- s.out += s.src.slice(s.i, end);
93
- s.i = end;
94
- s.last = quote;
95
- s.word = '';
96
- return true;
96
+ return take(s, endOfString(s.src, s.i, quote), quote);
97
97
  }
98
98
 
99
99
  function endOfString(src, start, quote) {
package/src/tokens.js CHANGED
@@ -28,7 +28,7 @@ export const CHARS_PER_TOKEN = 3;
28
28
  * and the tokenizer would split them into anything at all, giving a random number. The list
29
29
  * exists so that the metric says this in words instead of passing such a count off as counted.
30
30
  * SVG is deliberately not here: it is text, and its tokens are meaningful. */
31
- export const BINARY_EXTS = [
31
+ const BINARY_EXTS = [
32
32
  '.png', '.jpg', '.jpeg', '.gif', '.webp', '.ico', '.avif',
33
33
  '.woff', '.woff2', '.ttf', '.otf', '.eot',
34
34
  '.pdf', '.zip', '.gz', '.tar', '.mp4', '.mp3', '.mov'