Skip to content

Commit 1fb36ca

Browse files
os-billclaude
andauthored
test(lint): derive the list-view position-coverage criterion from the rule (#18876)
Fixes #18836 Clause-②: no The test table's docblock claimed two directions and the assertion under it carried one. This replaces the floor with a criterion derived from the rule's own tables, and makes the docblock true. ## The premise, re-measured Every reading below was taken on this branch's base, `origin/main` `36583e989b` — the dispatch quoted `a43b9d0654`, this worktree branched later, so the numbers were re-taken rather than inherited. They land in the same place: - docblock: `packages/lint/src/validate-list-view-field-refs.test.ts:205-210`, still word for word `a position added to the rule without a row here leaves the count assertion below short` - the assertion it points at: `:315`, `expect(cases.length).toBeGreaterThanOrEqual(51)` - `cases.length` at that commit: **52** Both directions were measured by mutating the rule's `POSITIONS` table on disk and running the rule's own test file. Each mutation proved itself on disk (anchor count 1 before, 0 after; injected line count 1; blob hash moved) and was restored under a trap, with `git diff HEAD` proving the restore byte for byte. | direction | mutation | before this PR | after this PR | |---|---|---|---| | one — a position **dropped** from the rule fails here | remove `tree.labelField` | 1 failed / 129 passed | **2 failed** / 129 passed (of 131) — the per-case assertion AND the new sibling assertion | | two — a position **added** to the rule with no row here | add `tree.ablationProbeField` | **130 passed, 0 failed** | **1 failed**, `covers every position the rule walks`, diff names `- "tree.ablationProbeField"` | Direction two reported nothing at all: `cases.length` counts the rows of the test table, which a position added to the rule never moves, so the floor stayed true. That is the direction #18565 came in through. After this PR the failing assertion is the completeness one, and its diff names the offender (`- "tree.ablationProbeField"`). Direction one is unchanged and still carried by the per-case assertions, exactly as the docblock's first half says — the failing test both before and after is `views[0].list.tree.labelField reports at warning`. **A third reading the card did not claim.** The floor did not reliably do even its own advertised job. Deleting one row from the table (`tree.labelField`) on the base tree left **129 passed, 0 failed** — 52 rows against a floor of 51 is exactly one row of slack. The same deletion after this PR is red, and the failing assertion is the completeness one. ## Step 1 — does the new export reach a published entry? `@objectstack/lint` is not private, `files` is `dist` / `README.md` / `CHANGELOG.md`, and `exports` has `.` and `./runtime` — so `src/` does not ship but `dist/` does. Re-measured on this base and it holds. Two facts about the barrels: `src/index.ts` re-exports this module by NAME (there is no `export *` in either barrel, count 0), and `src/runtime.ts` does not reference the module at all. Readings after `pnpm --filter @objectstack/lint build`, grepping `dist/`. Bright control throughout: `validateListViewFieldRefs`, a symbol known to ship. **Shape measured first — `export const X = Object.freeze([...])`:** | `dist/` file | new symbol | bright control | |---|---|---| | `index.js` | 1 | 3 | | `index.cjs` | 1 | 4 | | `runtime.js` | 1 | 2 | | `runtime.cjs` | 1 | 2 | | `index.d.ts` | 0 | 2 | | `index.d.cts` | 0 | 2 | | `runtime.d.ts` / `runtime.d.cts` | 0 | 0 | | `runtime-DAPAkqcs.d.ts` / `.d.cts` | 0 | not present | A raw count in a bundle body is not a published entry, so the export layer was measured separately. In `dist/index.js`'s `export {}` clause: new symbol 0, against 1 each for `validateListViewFieldRefs`, `LIST_VIEW_FIELD_DOTTED` and `LIST_VIEW_FIELD_UNKNOWN`. In `dist/index.cjs`'s `__export` map: new symbol 0, controls 1 each. Dark control for that layer: `COLUMN_ENTRY_POSITIONS`, a module const the rule has never exported, reads **2** in `index.js` and 2 in `index.cjs` — a name in the bundle body is genuinely no evidence of a published entry. Under this shape four `dist` JS files moved bytes (esbuild keeps a frozen initializer as dead weight); all four `.d.ts` / `.d.cts` files were byte-identical. **Shape shipped — `export function listViewWalkedPositions()`:** the symbol is tree-shaken. 0 occurrences in all eight published files, while the bright control still reads 3 / 4 / 2 with the same instrument on the same files. > ⚠️ **Corrected by the dispatching seat after the contract review, ⛔ not by the author.** This paragraph first read 「all nine `dist/` artifacts byte-identical … 9 of 9 OK」. The measured face is **14 artifacts, of which 10 are byte-identical** — the 4 JS bundles and all 6 type files — and **4 source maps moved** (`index.js.map`, `index.cjs.map`, `runtime.js.map`, `runtime.cjs.map`; `files[]` ships `dist` whole, so they are published). Their differing keys are `mappings` and `names` only, `sources` are equal, and the new symbol is absent from `names` in all four ⇒ a line offset, ⛔ not a surface. The original count hashed 9 files and omitted the 4 maps and one `runtime-*.d.cts`. Measured twice independently: by the at-tier contract review (record `5724912914`) and again by the author in its rework report. The reading the paragraph rests on is unchanged; only the count was wrong. **Reading: the new export reaches no published entry and moves zero bytes of the published artifact.** Per the dispatch's step-2 table, that is route A, and route A is what this PR implements. ### On the declaration line above `Clause-②: yes (widening)` was the dispatch's mechanical floor, declared before this measurement existed and routed for the seat's contract review. The at-tier review (record `5724912914`) **overturned it to `no`**, with no direction arm — the diff widens no accept set and expands no public surface. Per `references/lanes/spec.md:20` a conservative `yes` is never an error, so the overturn is by design and is ⛔ not a fault of the author. > ⚠️ **Rewritten by the dispatching seat, ⛔ not by the author** — the charter gives the author exactly one body write and routes later corrections through the seat. The changeset that graded this `minor` has been **deleted** by the author in the rework, and the PR carries `skip-changeset` instead: measured, the `fixed` group of about 70 packages would otherwise all move to 17.5.0 for a change that delivers **0 changed code or type bytes** to any consumer. ## What changed **`validate-list-view-field-refs.ts`** gains one module-level export, `listViewWalkedPositions()`, which derives the `block.key` id of every field-naming position from the rule's own `POSITIONS` and `COLUMN_ENTRY_POSITIONS` tables. 52 positions today. No `POSITIONS` row is touched — not a position, not a tier, not a severity — and the barrel is untouched. **`validate-list-view-field-refs.test.ts`**: the floor becomes set equality between the positions the rule walks and the positions this file's two case tables assert. The five-row measured table moves to module scope because three positions (`grouping.fields`, `kanban.groupByField`, `gantt.startDateField`) are asserted there and nowhere else; a criterion reading only the sibling table would need a hand-written exemption for them, which is the kind of constant this card is about. The per-case assertion bodies are unchanged, byte for byte. ## Why set equality and not the count equality route A suggested Route A said: derive the count from `POSITIONS` and compare it to `cases.length`. That equality cannot be written without magic constants, because rows are not positions: - 52 rows in the second table and 52 positions walked — the same number over different sets, by coincidence. - 3 positions are asserted only in the sibling five-row table. - 2 rows in the second table (and 1 in the first) assert the rule's hard-coded filter walks, which no table declares. - 2 positions carry two rows each (`columns` and `gantt.tooltipFields`: once as a bare name, once as a record with a `field` key), so the second table holds 1 duplicate row. The arithmetic a count equality would have had to hard-code is `52 = 52 - 3 + 2 + 1`. Set equality states the property directly instead, names the offending position in the failure diff, and needs no constant at all. Per the dispatch's own zoning this is the suggested-route zone, so it is reported rather than silently taken: the ruling (measurement decides A vs B) is executed as written. **What the new assertion does NOT carry**, stated in the code so it cannot become the next overclaim: it is one-directional. `positionAsserted` answers only with positions the rule currently walks, so a row for a position the rule has dropped contributes to neither side and the assertion stays green — that direction belongs to the per-case assertions, and both halves are measured above. A first draft of the comment claimed both directions; the direction-one probe showed only the per-case assertion firing, and the comment was corrected before the first push rather than shipped. ## docblock, sentence by sentence | original | now | true? | held by what | |---|---|---|---| | `Every remaining position, with the severity tier it earns.` | unchanged | yes | the per-case rows, each asserting one path and one severity | | `The table is the readable half of the rule's own POSITIONS table` | unchanged | yes, and now mechanically so | the completeness assertion reads the rule's tables directly | | `a position dropped from the rule fails here` | unchanged | yes | the per-case assertions — measured red before and after | | `a position added to the rule without a row here leaves the count assertion below short` | `a position added to the rule without a row in either table fails the completeness assertion below — which derives the set of positions from the rule's own tables instead of counting this one's rows` | yes | the new set-equality assertion — measured green before, red after | ## The inline comment above the assertion The dispatch fenced it as accurate and not to be rewritten as if it were the defect. It is not rewritten as a finding — it is replaced because the assertion it annotates no longer exists, and the dispatch also forbids leaving a floor and an equality side by side. Its semantics were right; the one-row slack measured above is reported as a reading, not fixed as a second thing. ## Verification Run through the shared verify lock. Package: `@objectstack/lint`. | check | before | after | |---|---|---| | `pnpm --filter @objectstack/lint test` | 104 files / 3909 tests passed | 104 files / **3910** tests passed (the delta is the one new `it`) | | `pnpm --filter @objectstack/lint typecheck` | — | exit 0 (`tsc --noEmit` plus `check:test-typecheck`: 2 files / 6 errors / 2 pinned signatures held) | | `pnpm lint` (repo-wide, `eslint . --no-inline-config`) | — | exit 0 | | control-character self-scan over the three touched files | — | no match | Gates: `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` derived **51** families from this tree at `9897eaae29` (58 at the previous head; the 7 that left are the changeset families, since the changeset was deleted); all 51 were run and each exit code recorded before any pipe, then reconciled with `--ran`. **48 green, 3 NOT MEASURED** — `check:dual-build-cjs-loads`, `check:lean-entry-closure` and `check:type-check-debt` each exited 3, PREREQUISITE NOT MET: all three read built output across the whole repo, which this worktree does not have. They are neither a pass nor a failure here, and CI builds fresh and runs them. The `dist` byte-identity measured above is why this diff cannot move their verdict. Dependency-closure build: `pnpm --filter '@objectstack/lint^...' build`, exit 0. ## Acceptance notes - Nothing outside this card's scope was changed. The `POSITIONS` table, the package barrel and the per-case assertion bodies are untouched; the only positions-table edits in this branch were the two ablation mutations, each restored and proven restored. - Noted, not filed: the replaced floor had exactly one row of slack (52 rows against 51), so a single silent row deletion passed it. It is fixed here as a consequence of replacing the assertion, so there is no separate card; the reading is above. Carrier: this PR. - Noted, not filed: `dist/runtime.js` bundles `validateListViewFieldRefs` although `dist/runtime.d.ts` exports it nowhere. That is the documented consequence of `splitting: false` in `packages/lint/tsup.config.ts`, whose own header carries the measurement, not a defect. Carrier: none. > ⚠️ **Five cells refreshed by the dispatching seat at the reworked head, ⛔ not by the author.** They described head `270d9f4b35`; this PR is now at `9897eaae29`. Refreshed: the direction-one "after" cell (the rework made that direction fail TWICE — the per-case assertion and the new sibling assertion — where it previously failed once), the direction-two "after" cell (now quoted by the failing assertion's own name and diff), the one-row-slack sentence (its post-PR count moved with the new `it`), the test count (3909 → 3910) and the gates paragraph (58 → 51 families, because deleting the changeset removed 7 changeset families). ⭐ The direction-one and direction-two readings were **re-verified by the at-tier review at this head** (record `5725290629`); the gates and test counts are the **author's** readings at this head, taken from its rework report and ⛔ not re-run by the seat. ⛔ No reading here was weakened: every refresh describes the same or stronger protection. --- _Generated by [Claude Code](https://claude.ai/code)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 84ba4a8 commit 1fb36ca

2 files changed

Lines changed: 213 additions & 25 deletions

File tree

‎packages/lint/src/validate-list-view-field-refs.test.ts‎

Lines changed: 154 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
validateListViewFieldRefs,
1717
LIST_VIEW_FIELD_UNKNOWN,
1818
LIST_VIEW_FIELD_DOTTED,
19+
listViewWalkedPositions,
1920
type ListViewFieldRefFinding,
2021
} from './validate-list-view-field-refs.js';
2122
import { SORT_FIELD_UNKNOWN } from './validate-sortable-fields.js';
@@ -172,25 +173,32 @@ describe('#14107 — the clean surface reports nothing', () => {
172173
* The card's own measured table. Each of these five passed `os validate`
173174
* (`valid: true, warnings: []`) and `os build` (exit 0, `✓ Build complete`) on
174175
* `@objectstack/cli` 17.2.0 — the reason this card exists.
176+
*
177+
* [#18836] At module scope because it is the other half of the coverage the
178+
* completeness assertion below judges: three positions (`grouping.fields`,
179+
* `kanban.groupByField`, `gantt.startDateField`) are asserted HERE and nowhere
180+
* else, so a criterion that read only the sibling table would have to carry a
181+
* hand-written exemption for them — the kind of constant that rots into the
182+
* very lie this card is about.
175183
*/
176-
describe('#14107 — the card\'s five measured positions', () => {
177-
const cases: Array<[string, AnyRec, string]> = [
178-
['columns[].field', { columns: [{ field: 'B2_no_such_field' }] }, 'views[0].list.columns[0].field'],
179-
[
180-
'filter[].field',
181-
{ filter: [{ field: 'A8_no_such_field', operator: 'equals', value: 'x' }] },
182-
'views[0].list.filter[0].field',
183-
],
184-
[
185-
'grouping.fields[].field',
186-
{ grouping: { fields: [{ field: 'A7_no_such_field' }] } },
187-
'views[0].list.grouping.fields[0].field',
188-
],
189-
['kanban.groupByField', { kanban: { groupByField: 'A9_no_such_field' } }, 'views[0].list.kanban.groupByField'],
190-
['gantt.startDateField', { gantt: { startDateField: 'B1_no_such_field' } }, 'views[0].list.gantt.startDateField'],
191-
];
184+
const MEASURED_CASES: Array<[string, AnyRec, string]> = [
185+
['columns[].field', { columns: [{ field: 'B2_no_such_field' }] }, 'views[0].list.columns[0].field'],
186+
[
187+
'filter[].field',
188+
{ filter: [{ field: 'A8_no_such_field', operator: 'equals', value: 'x' }] },
189+
'views[0].list.filter[0].field',
190+
],
191+
[
192+
'grouping.fields[].field',
193+
{ grouping: { fields: [{ field: 'A7_no_such_field' }] } },
194+
'views[0].list.grouping.fields[0].field',
195+
],
196+
['kanban.groupByField', { kanban: { groupByField: 'A9_no_such_field' } }, 'views[0].list.kanban.groupByField'],
197+
['gantt.startDateField', { gantt: { startDateField: 'B1_no_such_field' } }, 'views[0].list.gantt.startDateField'],
198+
];
192199

193-
for (const [label, patch, path] of cases) {
200+
describe('#14107 — the card\'s five measured positions', () => {
201+
for (const [label, patch, path] of MEASURED_CASES) {
194202
it(`${label} is an error, at a path an author can look up`, () => {
195203
const findings = validateListViewFieldRefs(stackWith(mutate(patch)));
196204
expect(idsOf(findings)).toEqual([path]);
@@ -202,11 +210,63 @@ describe('#14107 — the card\'s five measured positions', () => {
202210
}
203211
});
204212

213+
/**
214+
* [#18836] A case's asserted finding path, with the fixture's own `views[0].list`
215+
* prefix and every array index dropped, so it can be compared with a position id.
216+
*/
217+
function casePath(path: string): string {
218+
return path.replace(/^views\[\d+\]\.list\./, '').replace(/\[\d+\]/g, '');
219+
}
220+
221+
/**
222+
* [#18836] The `POSITIONS` position a case's asserted finding path names, or
223+
* `undefined` when the path belongs to one of the rule's hard-coded filter
224+
* walks — which no table declares, so {@link listViewWalkedPositions} has
225+
* nothing to derive them from. Those are declared and asserted separately, in
226+
* {@link HARD_CODED_FILTER_WALKS}.
227+
*
228+
* Two arms, because an `entries` position reports at two different paths: at
229+
* the entry itself when the author wrote a bare field name
230+
* (`gantt.tooltipFields[0]`) and at its `field` key when they wrote a record
231+
* (`gantt.tooltipFields[0].field`). The whole path is tried FIRST, so a
232+
* position whose own key is `field` (`rowColor.field`, and both
233+
* `columns[].summary.field` / `columns[].prefix.field`) matches itself instead
234+
* of being truncated to its block.
235+
*/
236+
function positionAsserted(path: string, walked: ReadonlySet<string>): string | undefined {
237+
const at = casePath(path);
238+
if (walked.has(at)) return at;
239+
const entry = at.replace(/\.field$/, '');
240+
return walked.has(entry) ? entry : undefined;
241+
}
242+
243+
/**
244+
* [#18836] The rule's three hard-coded filter walks, spelled as
245+
* {@link casePath} normalises the paths they report at.
246+
*
247+
* They are open code, not table rows — `checkListView` calls `checkFilter` on
248+
* `listView.filter`, on `tabs[]` and on `userFilters.tabs[]` — so the position
249+
* set derived from the rule cannot reach them, and the completeness assertion
250+
* below would let their rows be deleted in silence. That is precisely the hole
251+
* the floor this card replaced DID cover, by counting rows.
252+
*
253+
* So they are declared here and asserted EXACTLY: delete one of their rows and
254+
* the list comes up short; give a fourth hard-coded walk a row without adding
255+
* it here and the list comes up long. Together with the derived assertion, every
256+
* row in both tables is then accounted for by one criterion or the other.
257+
*/
258+
const HARD_CODED_FILTER_WALKS = [
259+
'filter.field',
260+
'tabs.filter.field',
261+
'userFilters.tabs.filter.field',
262+
];
263+
205264
/**
206265
* Every remaining position, with the severity tier it earns. The table is the
207266
* readable half of the rule's own POSITIONS table: a position dropped from the
208-
* rule fails here, and a position added to the rule without a row here leaves
209-
* the count assertion below short.
267+
* rule fails here, and a position added to the rule without a row in either
268+
* table fails the completeness assertion below — which derives the set of
269+
* positions from the rule's own tables instead of counting this one's rows.
210270
*/
211271
describe('#14107 — every other walked position', () => {
212272
const BAD = 'nope_field';
@@ -305,14 +365,83 @@ describe('#14107 — every other walked position', () => {
305365
});
306366
}
307367

308-
// A floor, so a position quietly dropped from the rule's table cannot pass
309-
// by simply never being asserted.
368+
// [#18836] The completeness criterion, DERIVED from the rule's own tables.
369+
//
370+
// It replaces a `toBeGreaterThanOrEqual` floor over `cases.length`. That
371+
// floor counted the rows of THIS table, which a position added to the rule
372+
// never moves — so the second direction the docblock above claimed, "a
373+
// position added to the rule without a row here", reported nothing at all.
374+
// That is the direction #18565 came in through: `kanban.titleField` was
375+
// declared by the schema from #16894 and walked by no position row, and this
376+
// file stayed green.
377+
//
378+
// What it asserts, exactly: every position the rule walks is asserted by a
379+
// row in one of this file's two tables. A position added to the rule with no
380+
// row in either is MISSING from the covered set, and the diff names it.
381+
//
382+
// ⛔ THIS assertion does not carry the opposite direction on its own.
383+
// `positionAsserted` answers only with positions the rule CURRENTLY walks, so
384+
// a row left behind for a position the rule has DROPPED contributes to
385+
// neither side here and this one stays green. Two other things catch it, both
386+
// measured: that row's own per-case assertion above stops seeing a finding,
387+
// which is the first half of the docblock; and the sibling assertion below,
388+
// where the stale row matches nothing and comes up as an extra.
310389
//
311-
// [#18835] Raised by the four rows this card adds — `calendar.allDayField`
312-
// and the three objectui-lifted `gantt` bindings. The number moves by what
313-
// was added and nothing else; the floor's own semantics are untouched.
390+
// The floor's OWN job — a row silently deleted from a table — is taken over
391+
// by this assertion and its sibling TOGETHER. The residue is stated here per
392+
// kind of row rather than left to be discovered, because a criterion that
393+
// claims a direction it does not cover is the exact defect this card is
394+
// about. Each one measured on this branch:
395+
//
396+
// - a row for a position no other row covers: deleting it drops the position
397+
// from the covered set ⇒ RED here.
398+
// - a row for one of the rule's three hard-coded filter walks: outside this
399+
// assertion by construction, because those walks are open code and nothing
400+
// derives them ⇒ RED in the sibling assertion below, which is why that one
401+
// exists.
402+
// - one of the two positions asserted TWICE on purpose (`columns` and
403+
// `gantt.tooltipFields`: once as a bare name, once as a record with a
404+
// `field` key): the surviving row still covers the position, so deleting
405+
// either one is ⛔ SILENT — measured, deleting both leaves this file green.
406+
// That seam is disclosed, not closed. Closing it means deriving each
407+
// `entries` position's two legal forms and demanding a row for both, which
408+
// is three rows these tables do not have today.
409+
const assertedPaths = (): string[] => [
410+
...MEASURED_CASES.map(([, , path]) => path),
411+
...cases.map(([, path]) => path),
412+
];
413+
314414
it('covers every position the rule walks', () => {
315-
expect(cases.length).toBeGreaterThanOrEqual(51);
415+
const walked = listViewWalkedPositions();
416+
const walkedSet = new Set(walked);
417+
const covered = assertedPaths()
418+
.map((path) => positionAsserted(path, walkedSet))
419+
.filter((position): position is string => position !== undefined);
420+
expect([...new Set(covered)].sort()).toEqual([...walked].sort());
421+
});
422+
423+
// [#18836] The other half of the same account, and the half the derived
424+
// assertion above structurally cannot reach: every path the two tables assert
425+
// names either a position the rule walks or one of its declared hard-coded
426+
// filter walks — nothing else. A set, compared in both directions, which
427+
// holds three things the completeness assertion does not:
428+
//
429+
// - SHORT ⇒ RED. Delete a filter-walk row and the set loses a member. All
430+
// three rows measured, one by one. That is exactly the coverage the
431+
// row-counting floor had and the derived assertion cannot reach.
432+
// - LONG ⇒ RED, measured two ways. Remove one of the three declarations
433+
// below and the rows outnumber them. Leave a row behind for a position the
434+
// rule has DROPPED and it matches neither side, so it arrives here as an
435+
// extra — red here as well as in that row's own per-case assertion, which
436+
// is how this assertion ends up carrying the direction its sibling above
437+
// cannot. A fourth hard-coded walk given a row without being declared
438+
// below lands in the same place by the same comparison.
439+
it('accounts for every asserted path, as a walked position or a declared filter walk', () => {
440+
const walkedSet = new Set(listViewWalkedPositions());
441+
const unaccounted = assertedPaths()
442+
.filter((path) => positionAsserted(path, walkedSet) === undefined)
443+
.map(casePath);
444+
expect([...new Set(unaccounted)].sort()).toEqual([...HARD_CODED_FILTER_WALKS].sort());
316445
});
317446
});
318447

‎packages/lint/src/validate-list-view-field-refs.ts‎

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -482,6 +482,65 @@ const COLUMN_ENTRY_POSITIONS: Array<{ block: string; key: string; severity: Sev
482482
{ block: 'prefix', key: 'field', severity: 'warning' },
483483
];
484484

485+
/**
486+
* [#18836] Every field-naming position the two declarative tables above make
487+
* this rule walk, as `block.key` — a bare key for the list view's own top
488+
* level, and `columns.<block>.<key>` for a position nested inside a `columns[]`
489+
* entry.
490+
*
491+
* DERIVED, never hand-written: `validate-list-view-field-refs.test.ts` asserts
492+
* that its own case tables cover exactly this set, so a position added to
493+
* {@link POSITIONS} with no row over there fails that assertion on the commit
494+
* that adds it. The floor it replaced counted the test table's own rows, which
495+
* a position added HERE never moves — the direction #18565 came in through.
496+
*
497+
* Deliberately NOT re-exported from the package barrel, and deliberately a
498+
* FUNCTION rather than a frozen const. Both halves were measured on this
499+
* branch, `pnpm --filter @objectstack/lint build`, grepping `dist/` with
500+
* `validateListViewFieldRefs` as the bright control:
501+
*
502+
* - Barrel: `src/index.ts` re-exports this module by NAME (no `export *`), so
503+
* an export added here reaches no published entry — 0 occurrences in the
504+
* `export {}` clause of `dist/index.js`, 0 in the `__export` map of
505+
* `dist/index.cjs`, and 0 in all six `.d.ts`/`.d.cts` files, against 1, 1
506+
* and 2 for the control (whose own 0 in the four `runtime*.d.*` files is
507+
* what those entries declare, not a dead instrument).
508+
* - Const vs function: as `const X = Object.freeze([...])` esbuild kept the
509+
* initializer as dead weight — 1 occurrence in each of `dist/index.js`,
510+
* `dist/runtime.js` and both `.cjs` siblings. As the function below it is
511+
* tree-shaken: 0 occurrences in all 14 `dist/` artifacts, and 10 of the 14
512+
* are byte-identical to a build of this package from the pre-export source
513+
* — the four JS bundles and all six type files.
514+
* - ⛔ The other 4 DO move, and `files[]` ships `dist` whole, so the honest
515+
* statement is not "nothing in dist changed": `index.js.map`,
516+
* `index.cjs.map`, `runtime.js.map` and `runtime.cjs.map` differ in
517+
* `mappings` and `names` only, with `sources` equal and this symbol absent
518+
* from `names`. A line offset, not a surface — but a measured one.
519+
*
520+
* It names positions only — no severity, no shape — so reading it can never
521+
* stand in for reading the tables.
522+
*
523+
* The three filter walks further down (`listView.filter`, `tabs[].filter`,
524+
* `userFilters.tabs[].filter`) are deliberately absent: they are open code, not
525+
* table rows, so there is nothing HERE to derive them from. ⛔ Absent from this
526+
* list is not absent from the test's account of itself — the test declares those
527+
* three as an explicit list and asserts it exactly, because a row of theirs
528+
* deleted in silence is the one thing the row-counting floor this replaced did
529+
* cover.
530+
*/
531+
export function listViewWalkedPositions(): string[] {
532+
return [
533+
...Object.entries(POSITIONS).flatMap(([block, spec]) =>
534+
[
535+
...Object.keys(spec.scalars ?? {}),
536+
...Object.keys(spec.lists ?? {}),
537+
...Object.keys(spec.entries ?? {}),
538+
].map((key) => (block === '' ? key : `${block}.${key}`)),
539+
),
540+
...COLUMN_ENTRY_POSITIONS.map(({ block, key }) => `columns.${block}.${key}`),
541+
];
542+
}
543+
485544
/** Filter positions on a list view, as `[key path from the view, where suffix]`. */
486545
const SILENT_EMPTY =
487546
'Nothing resolves the name at render time: the view renders successfully with a blank, ' +

0 commit comments

Comments
 (0)