@vernikr/size-report 2.7.0 → 2.8.1

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.7.0 (2026-09-17).** The tool lives as a package of its own: the registry name is
13
+ **Release 2.8.1 (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,42 @@ 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.1 is the drawing of the page put right after its table became a window, and its figures are measured rather than
23
+ retold. **The panel and the table were sharing one class:** the table's rows were styled by a bare `.row` while the
24
+ panel's own rows are `.panel .row`, so the table's rule took the metrics row and the three method lines out of the flow
25
+ and painted them over one another — every rule of the shared part now hangs off `#grid`, and a check reads the selector
26
+ list of that sheet rather than a sample of the page. **The numbers were placed from the grid's left edge while the commit
27
+ column is pinned over it**, which hid the first column — the overall total — completely; they begin at the column's right
28
+ edge now, a 220px figure written in the script and in the styling and held together like the three beside it. **The order
29
+ of the columns comes from the numbers rather than from the commit's list of paths**: a commit can touch a file without
30
+ moving it — this package's own attachment to itself, 2.7.0 → 2.8.0, is one character for another — and a column of empty
31
+ cells standing in front of the table is what a reader calls a broken order, so a file's newest move decides its place;
32
+ the engine's mark `last` stays in `--data` while the page's block no longer carries it. **One border and one height:**
33
+ every line of the table is 1px of one colour (the header's two lines were 2px and another shade) and the header is two rows
34
+ of the table's own height, while a file's name is centred over its group. The artifact grows 351 B for it — 85 495 B
35
+ against 85 144 B of one history. The checks go 88 → **89** in the fast profile and 193 → **194** in the full one; the
36
+ figures and the reasoning stand in `worklog/0209-page-drawing-order.md`.
37
+
38
+ 2.8.0 is about what the report costs whoever opens it, and its figures are measured rather than retold.
39
+ **The table is a window of itself:** every row and every column in sight is built, plus four beyond each edge so that
40
+ the edge of the window is never seen empty, while the rest of the table exists as the extent of the scrolled box and
41
+ nothing else. This repository's own report was a `<table>` of 238 500 cells — 253 770 nodes in the document, 377 396 in
42
+ the browser, 1.46 GB of a fresh Chrome's memory with the page open and nothing else — and the window of it is
43
+ **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
44
+ **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
45
+ 571 ms a frame, which is the freeze a reader felt). **Every column is 70px wide and fixed** — the numbers are short and
46
+ 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
47
+ that does not fit its group is cut with an ellipsis rather than wrapped (the whole name stands in the tooltip). **The
48
+ stripe over a drawing is gone with the freeze it was drawn for**: a switch costs a few milliseconds, so there is nothing
49
+ for an indicator to indicate, and `src/page/work.js` left the tree with it. A virtualizer library was measured and not
50
+ taken — `@tanstack/virtual-core` is ~6.7 kB gzip and headless, `virtua`'s grid is experimental and `Clusterize.js`
51
+ knows rows and not columns — because each would be vendored into the artifact, which the report then measures as its own
52
+ bytes. **The checks of the old table are deactivated while it is rebuilt:** `test/page-view.test.js`, `page-cols`,
53
+ `page-tree` and `page-choice` skip every check with its reason at the top of the file, and this document names them
54
+ where they are the promise-holders of what they described; `test/page-grid.test.js` is what guards the window meanwhile.
55
+ The figures and the reasoning stand in `worklog/0208-table-window.md`. The checks grow with the work: 81 → **88** in the
56
+ fast profile and 186 → **193** in the full one.
57
+
22
58
  2.7.0 is about the page and what a reader does with it, and its figures are measured rather than retold.
23
59
  **The report's address stays clean:** a switch writes the record into the browser's memory and nothing into the
24
60
  address bar — the page no longer puts a `#size-report=…` tail on the report it was opened with — while a link
@@ -217,11 +253,10 @@ commits and installs hooks; the reason for each expensive file is named line by
217
253
 
218
254
  | Run | Command | Checks |
219
255
  |---|---|---|
220
- | Fast — every edit | `pnpm test` | **81 of 186** |
221
- | Full — release and CI | `pnpm test:all` | **186** |
256
+ | Fast — every edit | `pnpm test` | **89 of 194** |
257
+ | Full — release and CI | `pnpm test:all` | **194** |
222
258
 
223
- No check is lost or weakened: the full run starts all 186 with the same files, the fast one takes part
224
- of them. The default is the full run — a file becomes fast only explicitly and with a reason — so new
259
+ No check is lost or weakened: the full run starts every file, the fast one takes part of them. The default is the full run — a file becomes fast only explicitly and with a reason — so new
225
260
  expensive work cannot quietly move into the fast one. Two declarations guard that:
226
261
  `test/suites.test.js` (every file classified, and a reason for each) and the documentation guard
227
262
  `test/docs-numbers.test.js` (the numbers in the table above).
@@ -336,45 +371,51 @@ row hides it, so a click on the sign changes exactly the three things the reader
336
371
  sign and the note in the memory. What guards this is that after folding the table is the same markup
337
372
  rather than a rebuilt one (`test/page-tree.test.js`).
338
373
 
339
- **A switch draws its columns in one task, with a stripe over the page while it is going on.** The class
340
- changes are cheap about 2 µs a node, so a category of this repository's report (73 columns, 55 042 nodes) is
341
- 120 ms of them — while the browser lays the whole table out again for any change of a column's visibility, and
342
- that is where the seconds are (`probes/step-12-columns.mjs`). So `src/page/work.js` asks for the drawing once:
343
- work short enough to be over before the browser could paint goes on the click itself, and a longer one goes to
344
- the next task with the stripe over the top edge of the window. **The stripe carries no share, and that is the
345
- measurement rather than a shortcut**: inside one task the browser cannot repaint, so a length would be a length
346
- nobody could keep; what moves is an indeterminate highlight. A queue worked off between timeouts was written
347
- first and refused by the same probe 37 slices paid the table's relayout 37 times (2.5–4 s each, 169 layouts
348
- against 1) and the numbers stay out of all of it, because the totals are counted on the click itself, where
349
- they cost arithmetic rather than nodes. Nothing is asked for a column that is already right either
350
- (`appColumnStale`): a report opened with everything switched on has nothing to draw, and a record from the
351
- memory or a link queues exactly the columns that differ from it. What guards the two halves is
352
- `test/page-choice.test.js` a long drawing that leaves the click's line at once, runs in the next task and
353
- takes the stripe away, and a short one that is over before the click returns.
354
-
355
- **A click shows and hides rather than builds.** The table is assembled once, with every column of every
356
- file, and a switch afterwards changes only what is visible: a metric is one class on the table plus the
357
- `colSpan` of the group headings, a file's column is a class per node of it, and a folder or a category is
358
- the same for each file of its subtree. The table's own nodes stay the objects the first drawing made:
359
- `test/page-view.test.js` counts what a click appends (a metric: nothing at all; a file, a folder or a
360
- category: at most the cells of the totals, rows × metrics) and checks that the rows and the cells are
361
- still the very same objects. What makes this possible is that the order of the columns depends on the
362
- files rather than on the choice the last commit's first, then the settings' order so a hidden column
363
- keeps its place and the visible ones do not move. The totals are the only numbers a choice changes, and
364
- they are carried rather than recounted: a sum is linear, so a file switched off subtracts exactly its own
365
- values, which costs its own rows instead of rows × files. That arithmetic is the step's one new piece,
366
- and it is held against the engine's own `rowModel` cell by cell for a mixed choice, so that counting a
367
- row stays in one place (`test/page-view.test.js`).
368
-
369
- **The columns the last commit touched come first.** The report is rebuilt after every commit, and a
370
- reader's first question is what that edit brought. The mark comes from the history rather than from the
371
- numbers an edit that changed no size is an edit too and it is taken from the last commit that
372
- touched at least one column, counting back from the top: a commit that went past the columns, above all
373
- the report itself, which the hook commits, is skipped, or the mark would depend on the report's own
374
- commit, the same run would give different bytes and the hook would commit the report a second time.
375
- Inside each part the order stays as it comes from the settings (the sort is stable): the order of the
376
- columns is what the reader is used to, and his choice of files does not rearrange it
377
- (`test/page-view.test.js`, the contract's `last` field).
374
+ **The table is a window of itself, and the window is what a scroll moves.** A row stands at its own `top` inside the
375
+ scrolled content and a column at its own `left`, so scrolling costs the browser nothing but painting no layout of the
376
+ table to redo and no script to run — while the page works only when the window has really moved, and then only on what
377
+ 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
378
+ number belongs to and the file it stands under are always in sight. The price of a table is what this step removed:
379
+ 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
380
+ 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
381
+ a *single* cell of this repository's report, 539 ms for 6 000 (`probes/step-12-columns.mjs`). A window of the same
382
+ 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
383
+ pass that took 36 s of task time over 60 steps takes **1.1 s over 251** (`worklog/0208-table-window.md`). **The columns
384
+ 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
385
+ measurement of every cell of the column the very cost this step removed. A file's name stands centred over its group
386
+ and is cut with an ellipsis rather than wrapped — the whole name stays in the tooltip. The header is two lines of one
387
+ row each, the numbers begin at the
388
+ right edge of the pinned commit column rather than under it, and every line of the table under a row, under a header
389
+ cell, along a group's left edge — is one pixel of one colour: a heavier line would promise a hierarchy to work out
390
+ rather than read. The geometry is four figures a column, a row, the header, the pinned column written in the styling
391
+ and in the script that counts the window's ordinals in them, and `test/page-grid.test.js` reads both and holds them
392
+ together.
393
+
394
+ **A click builds the window again, and that is cheap now.** A file switched off is simply not among the columns that
395
+ are built, and a metric switched off not among the metrics: there is nothing to hide and nothing to carry, and the
396
+ 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
397
+ same click cost as a table. The totals are the sum over the files that are on, counted by the shared `rowModel` for the
398
+ rows the window holds (`src/derived.js`), rather than kept in a running cache that a second road would have to agree
399
+ with. **There is no stripe over the page any longer, and that is a measurement rather than an omission:** it stood over
400
+ the browser's relayout of the whole table, and work that is over before a frame could paint it has no moment to be
401
+ shown in (`src/page/work.js` left the tree with the freeze it was drawn for). **Nothing of it is a library**, and that
402
+ was measured too: a virtualizer of both axes is not a solved problem for a page like this one —
403
+ `@tanstack/virtual-core` is ~6.7 kB gzip and headless (the rows and the columns are two virtualizers, and every node
404
+ is still yours to write), `virtua` calls its grid `experimental_VGrid` and has no sticky pieces, `Clusterize.js`
405
+ virtualizes rows out of a string of all of them and knows nothing of columns and each would have to be vendored into
406
+ the artifact, whose bytes this very tool measures. What is left to write after any of them is what the chapter is: the
407
+ window, the cells, the header and the pinned column.
408
+
409
+ **The columns whose numbers last moved come first.** The report is rebuilt after every commit, and a
410
+ reader's first question is what that edit brought. The mark is taken from the numbers rather than from
411
+ the commit's list of paths, and that is the whole of the difference: a commit can touch a column without
412
+ moving it — a version bumped inside a line of the same length, this package's own attachment to itself is
413
+ one — and a column of empty cells standing in front of the table is what a reader calls a broken order.
414
+ The rest follows in the settings' order (the sort is stable), so the order of the columns is what the
415
+ reader is used to, and his choice of files does not rearrange it (`src/page/table.js`; the rule is counted
416
+ from the contract's own rows in `test/page-grid.test.js`). The engine still answers with `last` — the
417
+ columns the newest commit touched — in `--data`; the page no longer reads it, and the block it carries no
418
+ longer holds it.
378
419
 
379
420
  **On a wide window the panel stands to the left of the table and takes no room from the numbers** (from
380
421
  900px, `src/page/app.css`). That is not decoration: a desktop has much side room and little vertical
@@ -557,15 +598,14 @@ acceptance for each.
557
598
  | `src/size-table.js` | The package's entry point: a re-export of the public API (55 names) and no calculation of its own |
558
599
  | `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 |
559
600
  | `src/css.js` | Reading the styling from disk: which sets of styles exist and what role each has |
560
- | `src/table.css` | The report's table: the geometry of a cell, the sticky header and commit column, the colour of deltas |
601
+ | `src/table.css` | The report's table: the geometry of the window — one width per column, one height per row, the two lines of the header, the pinned commit column — the sticky header and commit column, one 1px line of one colour for every border, a file's caption centred over its group and cut with an ellipsis, the colour of deltas |
561
602
  | `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 |
562
603
  | `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 |
563
604
  | `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 |
564
605
  | `src/page/dom.js` | The page's nodes: the small helpers of markup (`appEl`, `appBox`) — one set for the panel and the table alike |
565
606
  | `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 |
566
- | `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, and the count answerable for how many nodes a column holds |
567
- | `src/page/work.js` | The page's long drawing: the columns of a switch in one task, on the click when they are few and in the next task with an indeterminate stripe over the top edge of the window when they are many one task because the browser's relayout of the table is the price, and it is paid once (this is where the measurement that refused the slices stands) |
568
- | `src/page/app.js` | Assembling and starting the page: the first drawing, then a switch that counts the numbers on the click, hands the columns to the queue and writes the fields it reached without making a node; an anchor change; pasted into the assembled page |
607
+ | `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 order of the columns — 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 |
608
+ | `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 |
569
609
  | `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` |
570
610
  | `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 |
571
611
  | `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 |
@@ -597,7 +637,7 @@ acceptance for each.
597
637
  | `test/api.test.js` | The package's public API: the list of names is frozen, and splitting the engine may not change it |
598
638
  | `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`) |
599
639
  | `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 |
600
- | `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 |
640
+ | `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 |
601
641
  | `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 |
602
642
  | `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 |
603
643
  | `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 |
@@ -613,10 +653,11 @@ acceptance for each.
613
653
  | `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) |
614
654
  | `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 |
615
655
  | `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 |
616
- | `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 |
617
- | `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 |
618
- | `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 |
619
- | `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) |
656
+ | `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, every row of the window against the engine's own calculation, and the drawing's own rules — the order of the columns by the numbers, one border, the header's rows of one height, and the panel out of the shared styling's reach |
657
+ | `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 |
658
+ | `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 |
659
+ | `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 |
660
+ | `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 |
620
661
  | `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 |
621
662
  | `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 |
622
663
  | `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 |
@@ -696,7 +737,7 @@ The same release can be taken by a reference to the repository — installation
696
737
  registry, but stays tied to a revision:
697
738
 
698
739
  ```bash
699
- pnpm add -D github:vernikr/size-report#v2.7.0
740
+ pnpm add -D github:vernikr/size-report#v2.8.1
700
741
  ```
701
742
 
702
743
  With no network (or nothing to fetch from codeload) — the tarball: `pnpm pack` in the package clone, then
@@ -708,7 +749,7 @@ the branch moves on the installation fails with `Could not resolve <sha> to a co
708
749
  observation rather than reasoning: the short pin `6530237` installed while `main` stood on it and stopped
709
750
  working at the very next commit, while the same sha in full installed. A branch name (`#main`) and a tag
710
751
  are both accepted, but a branch is a moving target and a tag is constant: this release stands on the tag
711
- `v2.7.0`, which is also the one in the example (forty characters work as well, but they have to be copied
752
+ `v2.8.1`, which is also the one in the example (forty characters work as well, but they have to be copied
712
753
  out of the history by eye).
713
754
 
714
755
  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.7.0",
3
+ "version": "2.8.1",
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.6.0",
68
+ "@vernikr/size-report": "2.8.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/locales.js CHANGED
@@ -53,9 +53,6 @@ export const LOCALES = {
53
53
  * таблица не сказала бы читателю ничего. */
54
54
  unpack: 'Данные отчёта сжаты, а браузер не умеет их распаковать: в нём нет DecompressionStream. '
55
55
  + 'Обновите браузер — таблицу показывать не из чего.',
56
- /* Полоска вверху страницы: имя для тех, кто читает страницу не глазами, — она показывает,
57
- * что идёт отрисовка колонок, а не что отчёт завис. */
58
- working: 'отрисовка колонок',
59
56
  note: 'Абсолютные размеры стоят один раз — в строке «{now}». Строк: {rows}: коммиты без '
60
57
  + 'изменения объёма и коммиты, тронувшие только выключенные файлы, строк не получают. '
61
58
  + 'Собрано из истории git: {command}.'
@@ -116,9 +113,6 @@ export const LOCALES = {
116
113
  * browser without `DecompressionStream` gets instead of an empty table. */
117
114
  unpack: 'The report\u2019s data is compressed and this browser cannot unpack it: there is no '
118
115
  + 'DecompressionStream. Update the browser — there is nothing to build the table from.',
119
- /* The bar at the top of the page: a name for whoever reads the page without eyes — it shows
120
- * that the columns are being drawn rather than that the report has hung. */
121
- working: 'drawing the columns',
122
116
  note: 'Absolute sizes appear once — in the “{now}” row. Rows: {rows}: commits that change '
123
117
  + 'no number, and commits touching only the disabled files, get no row. '
124
118
  + 'Collected from git history: {command}.'
package/src/page/app.css CHANGED
@@ -14,7 +14,10 @@
14
14
  --pad: clamp(16px, 3vw, 34px);
15
15
  --radius: 10px;
16
16
  --muted: rgba(127, 127, 127, .85);
17
- --line: rgba(127, 127, 127, .32);
17
+ /* The same grey as the table's own lines (`--grid-line` of `src/table.css`): the frame around the numbers and the rows
18
+ * inside it are one drawing, and a second grey for the chrome is what makes a page look assembled from two. The two
19
+ * declarations are held together by the check that reads the borders of both sheets. */
20
+ --line: rgba(127, 127, 127, .25);
18
21
  --tint: rgba(127, 127, 127, .07);
19
22
  }
20
23
 
@@ -116,7 +119,8 @@ h1 { margin: 0 0 3px; font-size: 21px; font-weight: 650; letter-spacing: -.012em
116
119
 
117
120
  /* 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
121
  * 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. */
122
+ * which column it is. What stands inside that frame is a window of the grid rather than the whole of it
123
+ * (`src/page/table.js`). */
120
124
  .shell {
121
125
  overflow: auto;
122
126
  max-height: calc(100vh - 300px);
@@ -127,34 +131,11 @@ h1 { margin: 0 0 3px; font-size: 21px; font-weight: 650; letter-spacing: -.012em
127
131
  /* Numbers are denser than the page's text: there are more of them, they are shorter, and they are read by their digits. */
128
132
  #grid { font-size: 12.5px; }
129
133
 
130
- /* The stripe of a long drawing: over the window's own top edge for as long as a switch is being drawn (`src/page/work.js`).
131
- * It is fixed rather than laid out the page's shape is a grid of five rows, and a stripe appearing inside it would move the
132
- * numbers every time a switch is drawn and it is taken away as soon as the drawing is over, so the reader never sees it at
133
- * rest. It **does not fill**: the drawing is one task of the browser's own layout, inside which nothing can repaint, so a
134
- * share would be a share nobody has (`src/page/work.js` says what was measured). What moves instead is an indeterminate
135
- * stripe — a travelling highlight, which is what this state is in the language of progress — and it moves by a transform,
136
- * so the stripe itself costs the compositor rather than the page. Its colour is the system accent, the one colour the page
137
- * uses for anything but the numbers, and it carries a role and a name, because a reader without eyes has to be told that
138
- * work is going on rather than that the report is broken. */
139
- .bar { position: fixed; top: 0; left: 0; right: 0; z-index: 5; height: 3px; overflow: hidden; }
140
- .bar[hidden] { display: none; }
141
- .bar > i {
142
- display: block;
143
- width: 30%;
144
- height: 100%;
145
- background: AccentColor;
146
- animation: bar-run 1.1s linear infinite;
147
- }
148
- @keyframes bar-run {
149
- from { transform: translateX(-100%); }
150
- to { transform: translateX(333%); }
151
- }
152
- /* Whoever asked the system for less motion gets a still stripe: the state is what matters, not its travelling. */
153
- @media (prefers-reduced-motion: reduce) {
154
- .bar > i { animation: none; width: 100%; }
155
- }
156
-
157
- /* The message about a link that came in stands above the table, so that it cannot be missed, while it does not push the
134
+ /* There is no stripe of progress over the page any longer, and that is a measurement rather than an omission: it stood
135
+ * over the browser's relayout of the whole table, and the table is built as a window now a switch costs a few
136
+ * milliseconds (`src/page/table.js`), so an indicator would be a promise the page does not keep.
137
+ *
138
+ * The message about a link that came in stands above the table, so that it cannot be missed, while it does not push the
158
139
  * table away: one line in the place of the page. Its colour is the system one (the accent), for the page keeps no colours
159
140
  * of its own. */
160
141
  .notice {
@@ -253,7 +234,8 @@ h1 { margin: 0 0 3px; font-size: 21px; font-weight: 650; letter-spacing: -.012em
253
234
  h1 { font-size: 18px; }
254
235
  #grid { font-size: 12px; }
255
236
  .shell { max-height: calc(100vh - 260px); }
256
- /* The commit column needs no rule of its own here any more: its width is the measure `--clip` of the shared part
257
- * (`src/table.css`), and on a narrow screen that measure stands as it is. A shorter one would be `--clip: 150px`
258
- * here — the adaptation the shared part cannot make for itself. */
237
+ /* The commit column keeps its width on a narrow screen, and that is deliberate rather than forgotten: the numbers
238
+ * are placed beside it by the script (`APP_COMMIT`), so a narrower column here would leave a band of empty grid
239
+ * between the two one number would have to be read from the styling by the script, which is a second copy of a
240
+ * geometry the check holds together (`test/page-grid.test.js`). */
259
241
  }
package/src/page/app.js CHANGED
@@ -1,42 +1,23 @@
1
1
  import { appUnpack } from './payload.js';
2
2
  import { appApply, appBoot, appData, appFoldRead, appLinkUse, appNotice, appRead, appUi, appView, appWrite } from './state.js';
3
- import { appColumn, appColumnSize, appColumnStale, appContribute, appMetrics, appState, appTable, appTotals, appTotalsReset } from './table.js';
4
- import { appDraw } from './work.js';
3
+ import { appState, appTable, appWindow } from './table.js';
5
4
  import { appPanel, appPanelAll, appPanelState } from './panel.js';
6
5
 
7
- /* Assembling the report: the table is built once (`appTable` of the table chapter) and everything afterwards only
8
- * shows, hides and recounts. A click on any switch therefore costs a class, a number and the fields it reached — the
9
- * whole table used to be destroyed and built again, which was 81 % of the cost of a click and produced a hundred
10
- * 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.
11
10
  *
12
- * Hence two paths and no third: `appPaint` draws the whole view (the first drawing, a record from the browser's
13
- * memory, a link in the address), while `appSwitch`, `appSwitchGroup` and `appSwitchMetric` are what one click on a
14
- * box does. The numbers are counted on the click itself — that is arithmetic over the data while the nodes of the
15
- * columns are handed to the work chapter (`appDraw`), which draws the short work on the click and the long one in the
16
- * next task with a stripe over the page (`src/page/work.js` says what was measured: the price of a switch is the
17
- * browser's own relayout of the table, and it is paid once here rather than once per slice). Neither path makes a
18
- * 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.
19
16
  */
20
17
 
21
- // The table's cache of node references: made once, at the first drawing.
18
+ // The table's window: made once, at the first drawing.
22
19
  let appCache = null;
23
20
 
24
- /* One file's column drawn from the view — the state is read when the slice runs rather than kept from the click that
25
- * queued it, so a click that arrives while the queue is running is drawn by the next slice rather than after it. */
26
- function appDrawColumn(i) {
27
- appColumn(appCache, i, appView.files[i] === true);
28
- }
29
-
30
- /* The columns of a switch that have to be drawn, as the units the work chapter weighs (`{i, units}` — the file and the
31
- * nodes of its column). Only the columns whose nodes are out of step with the view are asked for (`appColumnStale`): a
32
- * report opened with everything switched on has nothing to draw, and a column that is already right would cost its
33
- * nodes again. The count is the table's (`appColumnSize`), because the table is the only place that knows how many
34
- * nodes a column has. */
35
- function appColumns(indexes) {
36
- const todo = indexes.filter((i) => appColumnStale(appCache, i));
37
- appDraw(appDrawColumn, todo.map((i) => ({ i: i, units: appColumnSize(appCache, i) })));
38
- }
39
-
40
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
41
22
  * written once — with the table rather than with every drawing of it. */
42
23
  function appNote() {
@@ -45,68 +26,59 @@ function appNote() {
45
26
  .replace('{command}', appData.report.fixCommand);
46
27
  }
47
28
 
48
- /* What the empty states are told: how many metrics and how many files are left. The table stands there in either
49
- * 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. */
50
31
  export function appCounts() {
51
32
  appState(appData.metrics.filter((m) => appView.metrics[m.key] === true).length,
52
33
  appView.files.filter((on) => on === true).length);
53
34
  }
54
35
 
55
- /* The whole view drawn: every column, the totals of the whole selection, the metrics, the empty states and the panel's
56
- * 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. */
57
38
  export function appPaint() {
58
- appColumns(appData.files.map((_f, i) => i));
59
- appTotalsReset(appCache);
60
- appMetrics(appCache);
39
+ appWindow(appCache, true);
61
40
  appCounts();
62
41
  appPanelAll();
63
42
  appWrite();
64
43
  }
65
44
 
66
- /* One file switched by the reader: the view, its share of the totals, the fields it shows in and its column — the
67
- * last through the queue, because a column may be long. The message about a link fades here: by this action the
68
- * 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. */
69
47
  export function appSwitch(i, on) {
70
48
  if (appView.files[i] === on) return;
71
49
  appView.files[i] = on;
72
- appContribute(appCache, i, on);
73
- appTotals(appCache);
50
+ appWindow(appCache, true);
74
51
  appCounts();
75
52
  appPanelState([i]);
76
53
  appWrite();
77
54
  appNotice('');
78
- appColumns([i]);
79
55
  }
80
56
 
81
- /* A group switched at once — a folder or a category: the same work per file, then the totals once and the fields of
82
- * the files the choice really reached (switching a folder on when a part of it was already on touches only the rest,
83
- * and a field that did not move is not written). The columns of the whole group go into the queue together, so the bar
84
- * counts them as one piece of work. */
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. */
85
60
  export function appSwitchGroup(indexes, on) {
86
61
  const touched = indexes.filter((i) => appView.files[i] !== on);
87
- touched.forEach((i) => {
88
- appView.files[i] = on;
89
- appContribute(appCache, i, on);
90
- });
91
- appTotals(appCache);
62
+ touched.forEach((i) => { appView.files[i] = on; });
63
+ appWindow(appCache, true);
92
64
  appCounts();
93
65
  appPanelState(touched);
94
66
  appWrite();
95
67
  appNotice('');
96
- appColumns(touched);
97
68
  }
98
69
 
99
- /* One metric switched: a class on the table and the headings' `colSpan`. The totals do not move with a metric they
100
- * 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. */
101
72
  export function appSwitchMetric() {
102
- appMetrics(appCache);
73
+ appWindow(appCache, true);
103
74
  appCounts();
104
75
  appWrite();
105
76
  appNotice('');
106
77
  }
107
78
 
108
79
  /* The first drawing: the choice is already in the view (the link and the memory are applied above), the panel is
109
- * 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. */
110
82
  function appFirst() {
111
83
  appPanel();
112
84
  appCache = appTable(document.getElementById('grid'));