@vernikr/size-report 2.6.0 → 2.8.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.
package/README.md CHANGED
@@ -10,7 +10,7 @@ shows.
10
10
 
11
11
  ## Status
12
12
 
13
- **Release 2.6.0 (2026-09-17).** The tool lives as a package of its own: the registry name is
13
+ **Release 2.8.0 (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,6 +19,48 @@ 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.0 is about what the report costs whoever opens it, and its figures are measured rather than retold.
23
+ **The table is a window of itself:** every row and every column in sight is built, plus four beyond each edge so that
24
+ the edge of the window is never seen empty, while the rest of the table exists as the extent of the scrolled box and
25
+ nothing else. This repository's own report was a `<table>` of 238 500 cells — 253 770 nodes in the document, 377 396 in
26
+ the browser, 1.46 GB of a fresh Chrome's memory with the page open and nothing else — and the window of it is
27
+ **2 460 nodes, 7 320 and 0.15 GB**: a hundredth of the nodes and a tenth of the memory. Scrolling the whole table cost
28
+ **1.1 s of task time over 251 steps**, against **36 s over 60 steps** for the same page as a full table (a median of
29
+ 571 ms a frame, which is the freeze a reader felt). **Every column is 70px wide and fixed** — the numbers are short and
30
+ of one kind, and a width that came out of the text is a measurement of every cell of the column — while a file's name
31
+ that does not fit its group is cut with an ellipsis rather than wrapped (the whole name stands in the tooltip). **The
32
+ stripe over a drawing is gone with the freeze it was drawn for**: a switch costs a few milliseconds, so there is nothing
33
+ for an indicator to indicate, and `src/page/work.js` left the tree with it. A virtualizer library was measured and not
34
+ taken — `@tanstack/virtual-core` is ~6.7 kB gzip and headless, `virtua`'s grid is experimental and `Clusterize.js`
35
+ knows rows and not columns — because each would be vendored into the artifact, which the report then measures as its own
36
+ bytes. **The checks of the old table are deactivated while it is rebuilt:** `test/page-view.test.js`, `page-cols`,
37
+ `page-tree` and `page-choice` skip every check with its reason at the top of the file, and this document names them
38
+ where they are the promise-holders of what they described; `test/page-grid.test.js` is what guards the window meanwhile.
39
+ The figures and the reasoning stand in `worklog/0208-table-window.md`. The checks grow with the work: 81 → **88** in the
40
+ fast profile and 186 → **193** in the full one.
41
+
42
+ 2.7.0 is about the page and what a reader does with it, and its figures are measured rather than retold.
43
+ **The report's address stays clean:** a switch writes the record into the browser's memory and nothing into the
44
+ address bar — the page no longer puts a `#size-report=…` tail on the report it was opened with — while a link
45
+ made by an earlier release is still read, and a foreign or broken one is still explained in words. **A test
46
+ is a category of its own**, right after Code: the files of `test`, `tests` and `fixtures` folders wherever
47
+ they stand, and every name carrying `.test` before its extension — on this repository's own history Tests
48
+ holds **47 of 317 columns**, and the checkbox of the documentation is called **Docs**. **The tree opens
49
+ folded**, and the unfolding is what the memory keeps, under a key of its own; inside a level a hidden name —
50
+ one beginning with a dot — stands after every visible one. **The drawing of a switch is measured, and it is one
51
+ task**: the click is counted and answered, a drawing short enough to be over before the browser could paint is
52
+ done on the click, and a longer one happens in the next task with a stripe over the top edge of the window. The
53
+ price of a switch is the browser's own relayout of the table — 234.5 ms of layout for a *single* cell of this
54
+ repository's report (249 rows, 317 columns, 238 thousand cells), 287 ms for 750, 539 ms for 6 000 — while the
55
+ class changes are about 2 µs a node; on this page the whole Table of Contents of the report (73 columns) is
56
+ 867 ms blocked with a 742 ms task. A queue worked off in slices was written first and refused by measurement:
57
+ the same click in 37 slices paid that relayout 37 times — **169 layouts and 151.9 s of pure layout time against
58
+ 1.04 s**, with the tab growing to gigabytes of repaint and the reader waiting four minutes for a switch that
59
+ costs him less than a second unsliced. Within one task nothing can repaint, so a bar that filled would be a bar
60
+ nobody could read: the stripe travels and says the work is going on (`probes/step-12-columns.mjs`, the records in
61
+ `worklog/0207-page-choice-bar.md`). The order of the columns did not change there: what the last commit touched
62
+ still stands first, the rest after it in the settings' order.
63
+
22
64
  2.6.0 changes the numbers themselves, and they are measured rather than retold: on this repository's own
23
65
  history the page this engine writes is **85 955 B against 1 899 370 B** written by 2.5.0 — 22.1 times
24
66
  smaller, −95.5 % — because the data travels as one packed block (49 392 B, `base64+gzip`) instead of a list
@@ -76,7 +118,8 @@ selection" are impossible in principle. The contract tells how each number was o
76
118
  no rule of counting of its own, and the split of numbers into exact and approximate was taken out of the
77
119
  package. The page's panel is a
78
120
  tree of files by folder, with a switch per folder for the whole subtree; a reader's choice survives
79
- a revisit and travels in a linkthe page's address is the link. The contract carries the **project
121
+ a revisit in the browser's memory, and the address keeps a clean tail an address with a link of its
122
+ own is read and never written (release 2.7.0). The contract carries the **project
80
123
  catalogue** too: every path git sees, so the page's tree is the project's tree, while numbers exist
81
124
  only for the files that became columns (release 2.2.0).
82
125
 
@@ -194,8 +237,8 @@ commits and installs hooks; the reason for each expensive file is named line by
194
237
 
195
238
  | Run | Command | Checks |
196
239
  |---|---|---|
197
- | Fast — every edit | `pnpm test` | **81 of 186** |
198
- | Full — release and CI | `pnpm test:all` | **186** |
240
+ | Fast — every edit | `pnpm test` | **88 of 193** |
241
+ | Full — release and CI | `pnpm test:all` | **193** |
199
242
 
200
243
  No check is lost or weakened: the full run starts all 186 with the same files, the fast one takes part
201
244
  of them. The default is the full run — a file becomes fast only explicitly and with a reason — so new
@@ -287,7 +330,9 @@ choice. The report itself is always in the catalogue, whether or not it is track
287
330
  of the moment, and the page must not depend on it, or the first rebuild in a fresh clone would give
288
331
  different bytes. A folder whose files are only partly in the report writes its count as a fraction
289
332
  ("2/5"), and everything outside the report stands after everything inside it — folders and leaves
290
- alike — so that the report is seen at once in a list where half the rows do not switch
333
+ alike — so that the report is seen at once in a list where half the rows do not switch. The name of a level
334
+ holds a second rule: a hidden name — one beginning with a dot — stands after every visible one, the alphabet
335
+ deciding the rest, so the service files of a project do not stand in the reader's way
291
336
  (`test/page-tree.test.js`).
292
337
 
293
338
  **A folder is a switch like a file, and its sign is a decision of its own.** The checkbox of a folder
@@ -296,8 +341,10 @@ next to it. Neither a folder nor a category button keeps state of its own: both
296
341
  checkboxes, so the tree, the buttons and the table cannot drift apart. The sign beside a folder answers
297
342
  a different question — how much of the tree is visible, which is the onlooker's business rather than the
298
343
  reader's choice — so it is remembered between visits in a record of its own, under a key of its own and
299
- the same report passport, and it never goes into the link; unfolding every folder removes that record,
300
- just as turning the checkboxes back on removes the choice.
344
+ the same report passport, and it never goes into a link. The tree opens **folded**: a project's tree is
345
+ longer than the window, and the reader looks at a short list first, so what the record keeps is the
346
+ unfolding rather than the folding — folding every folder back removes the record, just as turning the
347
+ checkboxes back on removes the choice.
301
348
 
302
349
  **The list scrolls, and there is one scroll.** On a narrow window it is the file list that scrolls —
303
350
  the panel grows with the page there — while on a wide one the whole panel does: otherwise the controls
@@ -309,19 +356,36 @@ row hides it, so a click on the sign changes exactly the three things the reader
309
356
  sign and the note in the memory. What guards this is that after folding the table is the same markup
310
357
  rather than a rebuilt one (`test/page-tree.test.js`).
311
358
 
312
- **A click shows and hides rather than builds.** The table is assembled once, with every column of every
313
- file, and a switch afterwards changes only what is visible: a metric is one class on the table plus the
314
- `colSpan` of the group headings, a file's column is a class per node of it, and a folder or a category is
315
- the same for each file of its subtree. The table's own nodes stay the objects the first drawing made:
316
- `test/page-view.test.js` counts what a click appends (a metric: nothing at all; a file, a folder or a
317
- category: at most the cells of the totals, rows × metrics) and checks that the rows and the cells are
318
- still the very same objects. What makes this possible is that the order of the columns depends on the
319
- files rather than on the choice — the last commit's first, then the settings' order so a hidden column
320
- keeps its place and the visible ones do not move. The totals are the only numbers a choice changes, and
321
- they are carried rather than recounted: a sum is linear, so a file switched off subtracts exactly its own
322
- values, which costs its own rows instead of rows × files. That arithmetic is the step's one new piece,
323
- and it is held against the engine's own `rowModel` cell by cell for a mixed choice, so that counting a
324
- row stays in one place (`test/page-view.test.js`).
359
+ **The table is a window of itself, and the window is what a scroll moves.** A row stands at its own `top` inside the
360
+ scrolled content and a column at its own `left`, so scrolling costs the browser nothing but painting no layout of the
361
+ table to redo and no script to run while the page works only when the window has really moved, and then only on what
362
+ left it and what entered it. The header sticks to the top of the shell and the commit column to its left, so the commit a
363
+ number belongs to and the file it stands under are always in sight. The price of a table is what this step removed:
364
+ 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
365
+ 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
366
+ a *single* cell of this repository's report, 539 ms for 6 000 (`probes/step-12-columns.mjs`). A window of the same
367
+ 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
368
+ pass that took 36 s of task time over 60 steps takes **1.1 s over 251** (`worklog/0208-table-window.md`). **The columns
369
+ 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
370
+ measurement of every cell of the column the very cost this step removed. A file's name over its group is cut with an
371
+ ellipsis rather than wrapped (the header is one line high and the whole name stands in the tooltip). The geometry is
372
+ three figures — a column, a row, the header — written in the styling and in the script that counts the window's ordinals
373
+ in them, and `test/page-grid.test.js` reads both and holds them together.
374
+
375
+ **A click builds the window again, and that is cheap now.** A file switched off is simply not among the columns that
376
+ are built, and a metric switched off not among the metrics: there is nothing to hide and nothing to carry, and the
377
+ window is built from the choice as it is — a few hundred cells, 1–5 ms on this repository's report against the ~1 s the
378
+ same click cost as a table. The totals are the sum over the files that are on, counted by the shared `rowModel` for the
379
+ rows the window holds (`src/derived.js`), rather than kept in a running cache that a second road would have to agree
380
+ with. **There is no stripe over the page any longer, and that is a measurement rather than an omission:** it stood over
381
+ the browser's relayout of the whole table, and work that is over before a frame could paint it has no moment to be
382
+ shown in (`src/page/work.js` left the tree with the freeze it was drawn for). **Nothing of it is a library**, and that
383
+ was measured too: a virtualizer of both axes is not a solved problem for a page like this one —
384
+ `@tanstack/virtual-core` is ~6.7 kB gzip and headless (the rows and the columns are two virtualizers, and every node
385
+ is still yours to write), `virtua` calls its grid `experimental_VGrid` and has no sticky pieces, `Clusterize.js`
386
+ virtualizes rows out of a string of all of them and knows nothing of columns — and each would have to be vendored into
387
+ the artifact, whose bytes this very tool measures. What is left to write after any of them is what the chapter is: the
388
+ window, the cells, the header and the pinned column.
325
389
 
326
390
  **The columns the last commit touched come first.** The report is rebuilt after every commit, and a
327
391
  reader's first question is what that edit brought. The mark comes from the history rather than from the
@@ -331,7 +395,7 @@ the report itself, which the hook commits, is skipped, or the mark would depend
331
395
  commit, the same run would give different bytes and the hook would commit the report a second time.
332
396
  Inside each part the order stays as it comes from the settings (the sort is stable): the order of the
333
397
  columns is what the reader is used to, and his choice of files does not rearrange it
334
- (`test/page-view.test.js`, the contract's `last` field).
398
+ (`test/page-grid.test.js`, the contract's `last` field).
335
399
 
336
400
  **On a wide window the panel stands to the left of the table and takes no room from the numbers** (from
337
401
  900px, `src/page/app.css`). That is not decoration: a desktop has much side room and little vertical
@@ -372,17 +436,18 @@ neither the numbers nor the markup. The passport holds neither the tool's versio
372
436
  history, and on purpose: updating the tool does not change what a column means, while a grown history is
373
437
  the very history the reader comes back to.
374
438
 
375
- **The same choice travels as a link.** The page's address is the link: the record that goes into the
376
- browser's memory goes into the anchor too (`#size-report=…`), so the sender copies the address and the
377
- recipient sees that choice with no action at all. The link outranks the memory it is the sender's
378
- explicit choice while it does not replace the reader's own until he changes something. A foreign or
379
- broken address is not applied, and is not silent either: a line above the table names the reason ("the
380
- link was made in another report" / "the choice in the address is unreadable"), the view stays the
381
- reader's own, and the incoming address is not rewritten; names the report does not hold are reported by
382
- count, skipped, and the rest is applied. The link works on an already open page as well: the browser does
383
- not reload the document when the anchor changes, so the page reads the address itself, or a link would
384
- only work in a new tab. The page makes no request to the network at all, and that is an assertion of a
385
- check rather than a promise (`test/page-view.test.js`, `test/parity.test.js`).
439
+ **The link is read and never written.** The page's address is the report's own: a choice lives in the
440
+ browser's memory, and the page puts nothing into the address bar a report opened from disk keeps the
441
+ address it was opened with, and not one switch lands in the tab's title bar. What an earlier release sent
442
+ as a link is still read, though the record in `#size-report=…`, in the same shape the memory holds it —
443
+ and such a link outranks the memory while it does not replace the reader's own until he changes something.
444
+ A foreign or broken address is not applied, and is not silent either: a line above the table names the
445
+ reason ("the link was made in another report" / "the choice in the address is unreadable"), the view stays
446
+ the reader's own, and the incoming address is not rewritten; names the report does not hold are reported
447
+ by count, skipped, and the rest is applied. The link works on an already open page as well: the browser
448
+ does not reload the document when the anchor changes, so the page reads the address itself, or a link
449
+ would only work in a new tab. The page makes no request to the network at all, and that is an assertion of
450
+ a check rather than a promise (`test/page-view.test.js`, `test/parity.test.js`).
386
451
 
387
452
  **The `min` metric can count for real.** The way of counting is chosen in the settings:
388
453
  `"minify": {"engine": "esbuild"}` minifies JS/TS/CSS for real through an optional dependency, while
@@ -513,14 +578,14 @@ acceptance for each.
513
578
  | `src/size-table.js` | The package's entry point: a re-export of the public API (55 names) and no calculation of its own |
514
579
  | `src/derived.js` | The report's shared calculation: totals, deltas, a cell, a commit's caption — one for the engine and the page's program |
515
580
  | `src/css.js` | Reading the styling from disk: which sets of styles exist and what role each has |
516
- | `src/table.css` | The report's table: the geometry of a cell, the sticky header and commit column, the colour of deltas |
581
+ | `src/table.css` | The report's table: the geometry of the window — one width per column, one height per row, the header — the sticky header and commit column, a file's caption cut with an ellipsis, the colour of deltas |
517
582
  | `src/page/app.css` | The page's styling on top of the shared part: the panel with the file tree and its sticky row of categories (a column on the left on a wide screen, the page fitting the window), the empty states, a narrow window |
518
583
  | `src/page/payload.js` | The page's block in sparse form, and the one place that unrolls it back: the history as changes (a file's appearance, its moves, its disappearance) turned into the snapshots the calculation and the table already speak — a value that did not move is one object shared by the rows that hold it |
519
- | `src/page/state.js` | The page's state: the report's data (the block unrolled by the payload chapter), the view of the checkboxes, the pointer "which path is which column", folded folders, the record's passport, the browser's memory and the exchange by link — a chapter of the page's program |
584
+ | `src/page/state.js` | The page's state: the report's data (the block unrolled by the payload chapter), the view of the checkboxes, the pointer "which path is which column", the unfolded folders, the record's passport, the browser's memory and the link that is read out of the address — a chapter of the page's program |
520
585
  | `src/page/dom.js` | The page's nodes: the small helpers of markup (`appEl`, `appBox`) — one set for the panel and the table alike |
521
- | `src/page/panel.js` | The panel of choices: the switches of metrics and files, the categories, the tree of the project's paths (files outside the report keep a checkbox off with a reason and stand after the rest; folders carry a folding sign that hides the subtree by a class rather than by a rebuild); built once, with the fields of the switches and of the folders and categories written where they stand |
522
- | `src/page/table.js` | The page's table, built once: a cell, a commit's caption, the header, the empty states and the cache of the nodes of every column — markup over the shared calculation, with the totals carried rather than recounted |
523
- | `src/page/app.js` | Assembling and starting the page: the first drawing, then a switch that shows, hides and rewrites the totals without making a node; an anchor change; pasted into the assembled page |
586
+ | `src/page/panel.js` | The panel of choices: the switches of metrics and files, the categories, the tree of the project's paths (files outside the report keep a checkbox off with a reason and stand after the rest, and a hidden name after every visible one; the tree opens folded and folders carry a sign that hides the subtree by a class rather than by a rebuild); built once, with the fields of the switches and of the folders and categories written where they stand |
587
+ | `src/page/table.js` | The page's table as a window: the rows and the columns the reader can see (plus four beyond each edge), a cell, a commit's caption, the header, the empty states and the two figures of the window — markup over the shared calculation, with the totals counted per row from the choice, the geometry in pixels that the styling mirrors, and the reason a library was not taken |
588
+ | `src/page/app.js` | Assembling and starting the page: the first drawing, then a switch that builds the window again and writes the fields it reached without making a node of the panel; an anchor change; pasted into the assembled page |
524
589
  | `src/page/build.js` | Assembling the page: data, styling and program in one file with no external references — the pasted text is **squeezed** on the way in (comments and indentation out, the same stripping the `min` metric counts) while the sources keep them, and the result is guarded by the stripper's own `assertCompilable` |
525
590
  | `src/git.js` | The only border where git is called: the pinned settings, blobs by the batch, the history, the comparison with the working tree |
526
591
  | `src/strip.js` | Removing ballast: which form goes to which file (extension, strategy) and which strategies are minification itself — the entry to the parsing of forms |
@@ -541,7 +606,7 @@ acceptance for each.
541
606
  | `bin/postinstall.js` | Installing the hook after the package is added: it looks for the consumer project and stays silent when there is nowhere to install |
542
607
  | `src/artifact.js` | The report on disk: the only place where it becomes a file (both `--write` and the hook use it); the report is a self-contained page |
543
608
  | `src/journal.js` | The journal and links: which section a commit belongs to and where a description leads |
544
- | `src/data.js` | The file categories and the contract with the page (`--data`): the numbers, the shape of the table and the catalogue of the project's paths |
609
+ | `src/data.js` | The file categories (a test by its path — a `test`, `tests` or `fixtures` folder, or `.test` before the extension — otherwise the extension, and the settings above both) and the contract with the page (`--data`): the numbers, the shape of the table and the catalogue of the project's paths |
545
610
  | `src/config.js` | The consumer project's settings: the defaults, reading them, checking them |
546
611
  | `src/project.js` | The settings derived from the project itself (its tree and history): columns, the journal, the exceptions, the catalogue of paths for the page's tree. Without a settings file it *is* the settings; `--init` pins it as a file |
547
612
  | `src/locales.js`, `src/refusal.js`, `src/tool.js` | The report's texts; the exit codes and the help; the package's name and version |
@@ -552,7 +617,7 @@ acceptance for each.
552
617
  | `test/api.test.js` | The package's public API: the list of names is frozen, and splitting the engine may not change it |
553
618
  | `eslint.config.js` | The rules of formatting: the same as the consumer project's, plus a ban on gluing operators into one line (`pnpm run lint`, `pnpm run lint:strict`) |
554
619
  | `tools/harness.js` | The harness of the checks: paths, clones of the fixture (including one shared per suite and one with CRLF), running the tool, reading refusals, hashes |
555
- | `tools/page-harness.js` | The harness of the contract and page checks: the contract data, the assembled page, reading it in a real DOM, the panel's switches, the page's calculation and its decoder evaluated from their sources, the block unpacked, and the platform's unpacker put into jsdom (which has none) — one for six suites |
620
+ | `tools/page-harness.js` | The harness of the contract and page checks: the contract data, the assembled page, reading it in a real DOM, the panel's switches, the page's calculation and its decoder evaluated from their sources, the block unpacked, and the platform's unpacker put into jsdom (which has none) — one for seven suites |
556
621
  | `tools/suites.js` | The split of the suite: which files go into the fast run (with a reason for each) and why every dear one is in the full run |
557
622
  | `tools/run-tests.js` | Running the suite (`pnpm test`, `pnpm test:all`, `pnpm run suites:measure`): each file's duration measured on its own, and the counts of checks adding up |
558
623
  | `tools/docs-facts.js` | Reading facts out of the documentation — one layer for the four checks of the documentation guard: what a document names (paths, calls, section addresses) against what the repository holds |
@@ -568,10 +633,11 @@ acceptance for each.
568
633
  | `test/refusals-catalog.test.js` | The guard of the refusal catalogue: every refusal site in the sources has an entry, every entry declares its advice, and refusals handed to another check are really accepted by it (the named file and line are checked) |
569
634
  | `test/contract-data.test.js` | The data contract: the numbers against the reference, the set of fields against the derived quantities, the metric's method against the way the numbers were counted — and the round trip through the page's sparse block, which restores the contract whole and twice over the same bytes |
570
635
  | `test/contract-derived.test.js` | The derived quantities against the artifact's numbers: a row's totals, a cell's delta and the delta of a total — on the code that lies in the tree |
571
- | `test/page-view.test.js` | The assembled page: pasted with no copy of the calculation, self-contained, the empty states, the styling, the switches, a click that makes no table and the carried totals against the engine's own sums |
572
- | `test/page-tree.test.js` | The panel's file tree: folders by the project's paths, three states, the subtree, files and folders outside the report (a checkbox off, a place after the rest), folding without a rebuild and a scroll a click does not touch |
573
- | `test/page-choice.test.js` | The memory of the choice and the exchange by link: a revisit, someone else's report, a foreign and a broken record, an address change on an open page |
574
- | `test/page-cols.test.js` | The fixed layout the page carries: a column's width is counted from the model rather than measured in a laid-out cell, the clip keeps a caption inside its column, the sticky header and commit column keep their edges, and the table names its own width (`width: auto` would hand the layout back to the automatic algorithm) |
636
+ | `test/page-grid.test.js` | The grid of the page as a window: what the reader sees is built and no more, the geometry of the styling against the script's, the rows and the columns that a scroll builds and drops, a number under its own caption, a file and a metric switched off, and every row of the window against the engine's own calculation |
637
+ | `test/page-view.test.js` | The assembled page: pasted with no copy of the calculation, self-contained, the empty states, the styling, the switches, a click that makes no table and the carried totals against the engine's own sums **deactivated** while the table is rebuilt, every check skipped with its reason in the file |
638
+ | `test/page-tree.test.js` | The panel's file tree: folders by the project's paths, three states, the subtree, files and folders outside the report (a checkbox off, a place after the rest), the hidden names at the end of a level, the tree that opens folded and the unfolding the memory keeps, folding without a rebuild and a scroll a click does not touch — **deactivated** with the table, the checks skipped with their reason |
639
+ | `test/page-choice.test.js` | The reader's choice and the work a click starts: the memory and a revisit, someone else's report, a foreign and a broken record, an address that stays clean, a link read at opening and on an open page, and the drawing of a long switch in a task of its own behind the stripe **deactivated**: the choice and the link are the same, while the counts and the stripe are not |
640
+ | `test/page-cols.test.js` | The fixed layout the page carries: a column's width is counted from the model rather than measured in a laid-out cell, the clip keeps a caption inside its column, the sticky header and commit column keep their edges, and the table names its own width — **deactivated** with the table: the columns are one fixed width now |
575
641
  | `test/module.test.js` | A module under a `.js` extension: measured without touching the settings; the stripper's guard is alive (proved by mutation) and does not accuse the innocent |
576
642
  | `test/guard.test.js` | Parsing a module: it goes through a thread, both paths give one verdict, the fallback works with the thread's file away, and hundreds of parses are cheaper than a launch |
577
643
  | `test/runner.test.js` | Reading a process's output: chunks are glued as buffers rather than appended to a string — a multi-byte character at a chunk border does not turn into two replacement characters |
@@ -651,7 +717,7 @@ The same release can be taken by a reference to the repository — installation
651
717
  registry, but stays tied to a revision:
652
718
 
653
719
  ```bash
654
- pnpm add -D github:vernikr/size-report#v2.6.0
720
+ pnpm add -D github:vernikr/size-report#v2.8.0
655
721
  ```
656
722
 
657
723
  With no network (or nothing to fetch from codeload) — the tarball: `pnpm pack` in the package clone, then
@@ -663,7 +729,7 @@ the branch moves on the installation fails with `Could not resolve <sha> to a co
663
729
  observation rather than reasoning: the short pin `6530237` installed while `main` stood on it and stopped
664
730
  working at the very next commit, while the same sha in full installed. A branch name (`#main`) and a tag
665
731
  are both accepted, but a branch is a moving target and a tag is constant: this release stands on the tag
666
- `v2.6.0`, which is also the one in the example (forty characters work as well, but they have to be copied
732
+ `v2.8.0`, which is also the one in the example (forty characters work as well, but they have to be copied
667
733
  out of the history by eye).
668
734
 
669
735
  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.6.0",
3
+ "version": "2.8.0",
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.5.0",
68
+ "@vernikr/size-report": "2.7.0",
69
69
  "c8": "10",
70
70
  "dependency-cruiser": "17",
71
71
  "eslint": "^9.18.0",
package/src/css.js CHANGED
@@ -7,8 +7,8 @@ import fs from 'node:fs';
7
7
  *
8
8
  * There are two sets, each with a role of its own:
9
9
  *
10
- * 1. `table.css` — the **table**: cell geometry, the sticky header and commit column, a commit's caption,
11
- * the colours of the deltas.
10
+ * 1. `table.css` — the **table**: the geometry of the window (a column, a row, the header), the sticky header
11
+ * and commit column, a commit's caption, the colours of the deltas.
12
12
  * 2. `page/app.css` — the page's look **on top of the table**: the canvas, the panel of choices, the empty
13
13
  * states and the adaptation to a narrow window.
14
14
  *
package/src/data.js CHANGED
@@ -10,19 +10,35 @@ import { TOOL_PKG } from './tool.js';
10
10
  * derived quantity at all. Everything the page counts itself begins where this module ends. */
11
11
 
12
12
  /* A file's category is only for the page's quick on/off buttons for a group: it does not reach the
13
- * numbers. The rule is one: the extension gives the category, everything else counts as code; a category
14
- * set in the column's settings outranks that rule, and the data says where it came from (`categoryBy`) — a
15
- * manual decision is explainable, while a table of extensions is a guess by file name. */
13
+ * numbers. The rule is: a category set in the column's settings outranks everything, a test is
14
+ * recognised by its path rather than by its extension, and for the rest the extension gives the
15
+ * category while everything else counts as code. The data says where the category came from
16
+ * (`categoryBy`) — a manual decision is explainable, while a table of extensions is a guess by file
17
+ * name. */
16
18
  export const CATEGORY_EXTS = {
17
19
  docs: ['.md', '.markdown', '.rst', '.txt', '.adoc'],
18
20
  chore: ['.json', '.yaml', '.yml', '.toml', '.ini', '.cfg', '.conf', '.lock', '.editorconfig'],
19
21
  assets: ['.svg', '.png', '.jpg', '.jpeg', '.gif', '.webp', '.ico', '.woff', '.woff2', '.ttf', '.otf']
20
22
  };
21
- export const CATEGORY_ORDER = ['code', 'docs', 'chore', 'assets'];
23
+ /* Tests stand next to code: they are files of the same kind (`*.test.js` is JavaScript too), and a reader
24
+ * switching code off wants to see what the tests weigh separately. */
25
+ export const CATEGORY_ORDER = ['code', 'tests', 'docs', 'chore', 'assets'];
26
+
27
+ /* What makes a file a test: the folder it lies in, wherever that folder is in the project, or a name
28
+ * carrying `.test` before its extension. Both are read off the path rather than off the extension, which is
29
+ * how a test is recognised in practice — a test may be written in any language the tool can measure. */
30
+ const TEST_DIRS = ['test', 'tests', 'fixtures'];
31
+ const TEST_NAME = /\.test\.[^./]+$/;
32
+
33
+ function isTest(p) {
34
+ return p.split('/').some((part) => TEST_DIRS.indexOf(part) >= 0) || TEST_NAME.test(path.basename(p));
35
+ }
22
36
 
23
37
  export function categoryOf(col) {
24
38
  if (col.category) return { key: col.category, by: 'config' };
25
- const ext = path.extname(col.paths[col.paths.length - 1]).toLowerCase();
39
+ const last = col.paths[col.paths.length - 1];
40
+ if (isTest(last)) return { key: 'tests', by: 'auto' };
41
+ const ext = path.extname(last).toLowerCase();
26
42
  const known = CATEGORY_ORDER.find((key) => (CATEGORY_EXTS[key] || []).indexOf(ext) >= 0);
27
43
  return { key: known === undefined ? 'code' : known, by: 'auto' };
28
44
  }
package/src/locales.js CHANGED
@@ -9,7 +9,7 @@ export const LOCALES = {
9
9
  commit: 'Коммит',
10
10
  total: 'Общий объём',
11
11
  now: 'сейчас',
12
- categories: { code: 'Код', docs: 'Документация', chore: 'Служебные', assets: 'Ресурсы' },
12
+ categories: { code: 'Код', tests: 'Тесты', docs: 'Документация', chore: 'Служебные', assets: 'Ресурсы' },
13
13
  /* The page texts live inside the report file itself (a dictionary of their own, next to
14
14
  * the data), so changing a word costs a rebuild of the report — otherwise the file
15
15
  * drifts from the history. */
@@ -80,7 +80,7 @@ export const LOCALES = {
80
80
  commit: 'Commit',
81
81
  total: 'Total',
82
82
  now: 'now',
83
- categories: { code: 'Code', docs: 'Documentation', chore: 'Chores', assets: 'Assets' },
83
+ categories: { code: 'Code', tests: 'Tests', docs: 'Docs', chore: 'Chores', assets: 'Assets' },
84
84
  page: {
85
85
  metrics: 'Metrics',
86
86
  files: 'Files',
package/src/page/app.css CHANGED
@@ -116,7 +116,8 @@ h1 { margin: 0 0 3px; font-size: 21px; font-weight: 650; letter-spacing: -.012em
116
116
 
117
117
  /* The table has a frame and a scroll of its own: the header and the commit column stick to it (the rules of stickiness
118
118
  * live in the shared part) rather than to the page, so scrolling sideways shows whose row it is while scrolling down shows
119
- * which column it is. */
119
+ * which column it is. What stands inside that frame is a window of the grid rather than the whole of it
120
+ * (`src/page/table.js`). */
120
121
  .shell {
121
122
  overflow: auto;
122
123
  max-height: calc(100vh - 300px);
@@ -127,7 +128,11 @@ h1 { margin: 0 0 3px; font-size: 21px; font-weight: 650; letter-spacing: -.012em
127
128
  /* Numbers are denser than the page's text: there are more of them, they are shorter, and they are read by their digits. */
128
129
  #grid { font-size: 12.5px; }
129
130
 
130
- /* The message about a link that came in stands above the table, so that it cannot be missed, while it does not push the
131
+ /* There is no stripe of progress over the page any longer, and that is a measurement rather than an omission: it stood
132
+ * over the browser's relayout of the whole table, and the table is built as a window now — a switch costs a few
133
+ * milliseconds (`src/page/table.js`), so an indicator would be a promise the page does not keep.
134
+ *
135
+ * The message about a link that came in stands above the table, so that it cannot be missed, while it does not push the
131
136
  * table away: one line in the place of the page. Its colour is the system one (the accent), for the page keeps no colours
132
137
  * of its own. */
133
138
  .notice {
package/src/page/app.js CHANGED
@@ -1,19 +1,21 @@
1
1
  import { appUnpack } from './payload.js';
2
- import { appAddressDrop, appApply, appBoot, appData, appFoldRead, appLinkUse, appNotice, appRead, appUi, appView, appWrite } from './state.js';
3
- import { appColumn, appContribute, appMetrics, appState, appTable, appTotals, appTotalsReset } from './table.js';
2
+ import { appApply, appBoot, appData, appFoldRead, appLinkUse, appNotice, appRead, appUi, appView, appWrite } from './state.js';
3
+ import { appState, appTable, appWindow } from './table.js';
4
4
  import { appPanel, appPanelAll, appPanelState } from './panel.js';
5
5
 
6
- /* Assembling the report: the table is built once (`appTable` of the table chapter) and everything afterwards only
7
- * shows, hides and recounts. A click on any switch therefore costs a class, a number and the fields it reached — the
8
- * whole table used to be destroyed and built again, which was 81 % of the cost of a click and produced a hundred
9
- * thousand dead nodes for the collector to walk.
6
+ /* Assembling the report: the table is a window of the grid (`appWindow` of the table chapter) and nothing is built
7
+ * that the reader cannot see. Two paths and no third, as before: `appPaint` draws the whole view (the first drawing, a
8
+ * record from the browser's memory, a link in the address), while a click on a box does the same work for the choice
9
+ * it made and the difference between them is only how much of the view moved.
10
10
  *
11
- * Hence two paths and no third: `appPaint` draws the whole view (the first drawing, a record from the browser's
12
- * memory, a link in the address), while `appSwitch`, `appSwitchGroup` and `appSwitchMetric` are what one click on a
13
- * box does. Neither makes a node.
11
+ * What a click costs now: the window of rows and columns is built again — a few hundred cells, measured at 1–5 ms on
12
+ * this repository's report — instead of a class on every node of a column of a table the browser lays out whole. The
13
+ * stripe that used to stand over a long drawing (`src/page/work.js`) is gone with the reason for it: a switch no
14
+ * longer has anything to wait for, and an indicator over work that is over before it could be painted would be a
15
+ * promise the page does not keep.
14
16
  */
15
17
 
16
- // The table's cache of node references: made once, at the first drawing.
18
+ // The table's window: made once, at the first drawing.
17
19
  let appCache = null;
18
20
 
19
21
  /* The note under the table: what a row is and how the report was made. It does not depend on the choice, so it is
@@ -24,66 +26,59 @@ function appNote() {
24
26
  .replace('{command}', appData.report.fixCommand);
25
27
  }
26
28
 
27
- /* What the empty states are told: how many metrics and how many files are left. The table stands there in either
28
- * case (it is built once) — the words are about what is shown. */
29
+ /* What the empty states are told: how many metrics and how many files are left. The window stands there in either
30
+ * case — the words are about what is shown. */
29
31
  export function appCounts() {
30
32
  appState(appData.metrics.filter((m) => appView.metrics[m.key] === true).length,
31
33
  appView.files.filter((on) => on === true).length);
32
34
  }
33
35
 
34
- /* The whole view drawn: every column, the totals of the whole selection, the metrics, the empty states and the panel's
35
- * fields. This is what a link, a record from the memory and the first drawing need — and it makes no node either. */
36
+ /* The whole view drawn: the window of the grid, the metrics, the empty states and the panel's fields. This is what a
37
+ * link, a record from the memory and the first drawing need. */
36
38
  export function appPaint() {
37
- appData.files.forEach((_f, i) => appColumn(appCache, i, appView.files[i]));
38
- appTotalsReset(appCache);
39
- appMetrics(appCache);
39
+ appWindow(appCache, true);
40
40
  appCounts();
41
41
  appPanelAll();
42
42
  appWrite();
43
43
  }
44
44
 
45
- /* One file switched by the reader: the view, its column, its share of the totals and the fields it shows in — each in
46
- * its own place. The message about a link fades here: by this action the reader has read it. */
45
+ /* One file switched by the reader: the view, the window (its column is simply not among the columns that are built),
46
+ * the fields it shows in. The message about a link fades here: by this action the reader has read it. */
47
47
  export function appSwitch(i, on) {
48
48
  if (appView.files[i] === on) return;
49
49
  appView.files[i] = on;
50
- appColumn(appCache, i, on);
51
- appContribute(appCache, i, on);
52
- appTotals(appCache);
50
+ appWindow(appCache, true);
53
51
  appCounts();
54
52
  appPanelState([i]);
55
53
  appWrite();
56
54
  appNotice('');
57
55
  }
58
56
 
59
- /* A group switched at once — a folder or a category: the same work per file, then the totals once and the fields of
60
- * the files the choice really reached (switching a folder on when a part of it was already on touches only the rest,
61
- * and a field that did not move is not written). */
57
+ /* A group switched at once — a folder or a category: the same work per file, then the fields of the files the choice
58
+ * really reached (switching a folder on when a part of it was already on touches only the rest, and a field that did
59
+ * not move is not written). The columns of the whole group leave or enter the window together. */
62
60
  export function appSwitchGroup(indexes, on) {
63
61
  const touched = indexes.filter((i) => appView.files[i] !== on);
64
- touched.forEach((i) => {
65
- appView.files[i] = on;
66
- appColumn(appCache, i, on);
67
- appContribute(appCache, i, on);
68
- });
69
- appTotals(appCache);
62
+ touched.forEach((i) => { appView.files[i] = on; });
63
+ appWindow(appCache, true);
70
64
  appCounts();
71
65
  appPanelState(touched);
72
66
  appWrite();
73
67
  appNotice('');
74
68
  }
75
69
 
76
- /* One metric switched: a class on the table and the headings' `colSpan`. The totals do not move with a metric they
77
- * are sums over files and the metric's own field is the box the reader just clicked. */
70
+ /* One metric switched: the columns of that metric are not among the columns that are built any longer, so this is the
71
+ * same drawing as a file's switch. The metric's own field is the box the reader just clicked. */
78
72
  export function appSwitchMetric() {
79
- appMetrics(appCache);
73
+ appWindow(appCache, true);
80
74
  appCounts();
81
75
  appWrite();
82
76
  appNotice('');
83
77
  }
84
78
 
85
79
  /* The first drawing: the choice is already in the view (the link and the memory are applied above), the panel is
86
- * built to match it, the table is built once every column of every file and the view is painted over it. */
80
+ * built to match it, the window of the grid is built — the columns the choice leaves and the rows the shell shows
81
+ * and the panel's fields are written from the view. */
87
82
  function appFirst() {
88
83
  appPanel();
89
84
  appCache = appTable(document.getElementById('grid'));
@@ -110,19 +105,18 @@ async function appBegin() {
110
105
  return;
111
106
  }
112
107
  const appStart = appLinkUse();
113
- if (appStart === 'ours') appTransient = true;
114
- else if (appStart === 'refused') appForeign = true;
108
+ /* A link that came in is the sender's choice rather than the reader's: while it is drawn, the memory is not touched
109
+ * (`appWrite`), so opening a link does not make it the reader's own. */
110
+ appTransient = appStart === 'ours';
115
111
  if (appStart !== 'ours') {
116
112
  const appSaved = appRead();
117
113
  if (appSaved !== null) appApply(appSaved);
118
114
  }
119
- /* The folded tree is the onlooker's memory rather than the reader's choice: it comes back even when someone
115
+ /* The unfolded tree is the onlooker's memory rather than the reader's choice: it comes back even when someone
120
116
  * else's link is open (otherwise a link sent over would unfold the tree again on every visit). */
121
117
  appFoldRead();
122
118
  appFirst();
123
119
  appBooted = true;
124
- appStartup = false;
125
- appForeign = false;
126
120
  appTransient = false;
127
121
  }
128
122
 
@@ -138,13 +132,10 @@ window.addEventListener('hashchange', () => {
138
132
  /* The first drawing has not happened yet: the address the page was opened with is the business of that drawing,
139
133
  * and a change that arrives before it has drawn nothing to replace. */
140
134
  if (!appBooted) return;
141
- /* An address that came in from outside is read first and a write this page was still holding is dropped: the reader
142
- * has the address they were sent, not the one the previous click armed (see `appAddressDrop`). */
143
- appAddressDrop();
144
- const state = appLinkUse();
145
- if (state === 'refused') appForeign = true;
146
- appTransient = state === 'ours';
135
+ /* An address that came in from outside is read here the way it is read at opening, and a link is not the reader's
136
+ * choice until he changes something — the memory stays his own. The address itself is never rewritten: the page has
137
+ * no business in the tab's title bar, and what a link holds is read rather than made. */
138
+ appTransient = appLinkUse() === 'ours';
147
139
  appPaint();
148
- appForeign = false;
149
140
  appTransient = false;
150
141
  });
package/src/page/build.js CHANGED
@@ -24,8 +24,10 @@ export function esc(s) {
24
24
  *
25
25
  * The chapters follow the page's subjects, and the order of pasting (the list below) is the order of declarations in
26
26
  * the assembled program: the calculation first, then the choice's state, the nodes, the panel, the table and the
27
- * assembling. The chapters are **slices of one text**: pasting glues them in a row, so the assembled page would stay
28
- * the same if the chapters became one file again. */
27
+ * assembling. A chapter that is gone leaves the page with one subject fewer rather than with a hole: the stripe over
28
+ * a long drawing left with the freeze it was drawn for (`src/page/app.js` says what a switch costs now). The
29
+ * chapters are **slices of one text**: pasting glues them in a row, so the assembled page would stay the same if
30
+ * the chapters became one file again. */
29
31
  export function stripModules(src) {
30
32
  return src.split('\n')
31
33
  .filter((line) => !/^import\s.*;\s*$/.test(line))
@@ -257,7 +259,7 @@ export function pageHtml(data, cfg) {
257
259
  + '<p class="sub">' + esc(subText(data, loc.page)) + '</p>\n</header>\n'
258
260
  + '<div id="panel" class="panel"></div>\n'
259
261
  + '<p id="notice" class="notice" hidden></p>\n'
260
- + '<div id="shell" class="shell"><table id="grid"></table></div>\n'
262
+ + '<div id="shell" class="shell"><div id="grid" class="grid"></div></div>\n'
261
263
  + '<p id="state" class="state" hidden></p>\n'
262
264
  + '<p id="note" class="note"></p>\n'
263
265
  + '<script type="application/octet-stream" id="data" data-pack="base64+gzip">'