Skip to content

test(types): pin NamedListView's per-member liveness census (objectui#7924) - #8933

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-7924-namedlistview-liveness-census
Sep 10, 2026
Merged

test(types): pin NamedListView's per-member liveness census (objectui#7924)#8933
os-zhuang merged 1 commit into
mainfrom
claude/issue-7924-namedlistview-liveness-census

Conversation

@os-warren

@os-warren os-warren commented Sep 10, 2026

Copy link
Copy Markdown
Collaborator

Refs #7924 — this PR lands the census only and decides nothing. The two dispositions the card names for the unread members (?: never tombstones per the objectui#7129 route, or making the delegation read them from the named view) are maintainer rulings and stay with objectui#7928. ⛔ No declaration face moved: packages/types/src/objectql.ts, every *.zod.ts and every renderer read set are byte-identical to main in this diff, which touches one test file and one changeset.

Why none of the card's figures are quoted

The card body says "about 52 members" / "exactly seven read" / "~45", measured on 6a9ee323. Every figure below is re-derived on this branch's head (3b5053d45). The reading is 47 declared, 6 declared-and-read, 41 unread, plus one read-but-undeclared key. "About 52" reproduces only as a hand figure sitting between two instruments and is neither of them — the instrument table below pins that gap as an assertion rather than a sentence.

The census

NamedListView (packages/types/src/objectql.ts:2025:2202 today — re-located by AST, not by the card's stale line numbers) declares 47 top-level members. The object-view node renderer reads 7 names off a named view; 6 of them are declared members.

⚠️ The third column is the distinction the entire finding turns on: a member reached through activeView?.KEY — the host's views prop — is not read off the named view. It is included because it is what the ruling needs: it separates "unread here" from "unread anywhere".

# member optional read off the named view reached from activeView
1 label no read activeView?.label
2 type yes read activeView?.type
3 columns yes read activeView?.columns
4 filter yes read activeView?.filter
5 sort yes read activeView?.sort
6 options yes read none
7 showSearch yes unread activeView?.showSearch
8 showSort yes unread activeView?.showSort
9 showFilters yes unread activeView?.showFilters
10 showHideFields yes unread fold only
11 showGroup yes unread fold only
12 showColor yes unread fold only
13 showDensity yes unread fold only
14 compactToolbar yes unread activeView?.compactToolbar
15 allowExport yes unread activeView?.allowExport
16 color yes unread activeView?.color
17 inlineEdit yes unread activeView?.inlineEdit
18 wrapHeaders yes unread activeView?.wrapHeaders
19 clickIntoRecordDetails yes unread activeView?.clickIntoRecordDetails
20 addRecordViaForm yes unread activeView?.addRecordViaForm
21 addDeleteRecordsInline yes unread activeView?.addDeleteRecordsInline
22 collapseAllByDefault yes unread activeView?.collapseAllByDefault
23 fieldTextColor yes unread activeView?.fieldTextColor
24 prefixField yes unread activeView?.prefixField
25 description yes unread none
26 showDescription yes unread activeView?.showDescription
27 navigation yes unread activeView?.navigation
28 selection yes unread activeView?.selection
29 pagination yes unread activeView?.pagination
30 searchableFields yes unread activeView?.searchableFields
31 filterableFields yes unread activeView?.filterableFields
32 resizable yes unread activeView?.resizable
33 densityMode yes unread activeView?.densityMode
34 rowHeight yes unread activeView?.rowHeight
35 hiddenFields yes unread activeView?.hiddenFields
36 exportOptions yes unread none
37 rowActions yes unread activeView?.rowActions
38 bulkActions yes unread activeView?.bulkActions
39 bulkActionDefs yes unread none
40 sharing yes unread activeView?.sharing
41 addRecord yes unread activeView?.addRecord
42 conditionalFormatting yes unread activeView?.conditionalFormatting
43 userFilters yes unread activeView?.userFilters
44 showRecordCount yes unread activeView?.showRecordCount
45 allowPrinting yes unread activeView?.allowPrinting
46 emptyState yes unread activeView?.emptyState
47 aria yes unread activeView?.aria
data not declared read, through an as any cast activeView?.data

Totals: 47 declared = 6 read + 41 unread. Read set off the named view = 7 names (the 6 above plus the undeclared data).

"fold only" means the member has no activeView?.KEY access, but IS consumed when it appears on activeView, through normalizeListViewSchema(activeView) (packages/core/src/utils/normalize-list-view.ts, its SHOW_FLAG_TO_USER_ACTION map). ⚠️ Worth stating because a named-property probe alone reports those four as having no reader at all, which is false — and that mistake would have gone straight into the ruling.

Three readings the ruling request should carry

  1. data is still read-but-undeclared. It reaches the renderer as data: (currentNamedViewConfig as any)?.data ?? …. objectui#7928 requires it to be declared or its cast removed and names this card as its home; it is still neither. It is why the arithmetic subtracts six, not seven, from 47.
  2. The 41 unread are not one population. 34 have a host-side twin the renderer honours off activeView; 4 more are folded from activeView; 3 have no reader on any pathdescription, exportOptions, bulkActionDefs. Of those, exportOptions and bulkActionDefs do not appear in the renderer at all (pinned), and description appears only off schema / schema.table / schema.form, never off a view.
  3. label is the only required member, and it is read — so a tombstone route would leave the sole required member intact.

Instruments, and the control that makes each zero a reading

instrument what it walks reading
namedListViewMembers() (new) ts.createSourceFile, the interface's own PropertySignature members 47
namedListViewMemberCount() (kept) strict regex, two-space indent anchor 47
namedListViewLooseMemberCount() (new) same regex without the indent anchor — also matches nested object-literal lines 59

⛔ Not a brace-depth count: a brace parser on this repo has already failed to terminate at the end of an object literal (objectui#8071). The three are now pinned against each other, so the instrument swap is itself a measurement and "52 is between two instruments and is neither" stops being prose.

The read set is derived by walking every route from schema.listViews to a named view — a binding annotated NamedListView; the value binding of Object.entries(record).map(...) (through an intermediate const entries = … too); and element access straight off the record — stripping ( ), ! and as any at each access, which is the only reason data is visible at all.

⭐ That walk found a named-view read the old currentNamedViewConfig?.KEY regex cannot see: {view.label || key} on the named-view tab strip. label is read at two sites, and the count is asserted.

Completeness, not just coverage: every occurrence of the record binding is classified into a role (DECLARATION, ELEMENT_ACCESS, OBJECT_KEYS, OBJECT_ENTRIES, COMPARISON, CONDITION, HOOK_DEPENDENCY). A new syntactic route lands as UNCLASSIFIED and fails before the census can silently under-count — the test says in its own message not to add the role to make it green.

Firing controls (a silent probe is a parser that found nothing, not a reading of zero):

  • the query returning nothing for all 41 unread members is the query that returns columns — asserted present in both the declared set and the read set;
  • its negative twin rowHeight is asserted declared and not read off the named view, with rowHeight: activeView?.rowHeight, pinned as the text that makes it so;
  • stickyHeader, in neither set, is non-vacuity for both probes at once;
  • the two "absent from the renderer entirely" claims carry their own control: the same grep that finds nothing for them is asserted to find rowHeight.

Ablation — the pin is non-vacuous in both directions

Each leg: mutation proven on disk before any result was read (anchor counts on both the removed and the injected text, plus a git hash-object differing from the HEAD blob), restore from a trap … EXIT INT TERM using git checkout HEAD -- ABSOLUTE_PATH, restoration proven by blob hash, never by an exit code. HEAD blob of ObjectView.tsx = c1a807af683a0147796780c801ac34ffa97e8c70.

Leg A — unread becomes read. navigation: activeView?.navigation ?? … rewritten to read the named view first. Removed-text count 1 to 0, injected 0 to 1, blob c1a807af to fdfe41ac. Result: 4 failed / 143 passed, and the only per-member row that moved is the expected one:

  • UNREAD — navigation is declared and NOT read off a named view ⇒ red
  • the two "exactly seven names" assertions and the 6 read + 41 unread = 47 partition ⇒ red
  • the other 40 unread rows and all 6 read rows ⇒ green

Leg B — read becomes unread. The currentNamedViewConfig?.type read deleted. Blob c1a807af to c9e79701. Result: 4 failed / 143 passed:

  • READ — type is declared AND read off a named view ⇒ red
  • the same three aggregate assertions ⇒ red
  • every other per-member row ⇒ green

⚠️ Leg B's first attempt did not run, and is reported rather than quietly retried. Its injected text (if (activeView?.type) return activeView.type;) already existed on the next line, so the on-disk proof read injected-count 2, expected 1, and the script aborted with exit 94 before running anything and restored the file. The reading was void, not green. Re-anchored on a unique marker and re-run; both blob hashes above are from the run that actually happened.

Restoration verified after every leg: git hash-object back to c1a807af683a0147796780c801ac34ffa97e8c70 and git diff HEAD empty for that path.

Readings — commands and exit codes

Exit codes captured into a file before any pipe (cmd > out 2>&1; RC=$?), never off the end of a pipeline.

command exit reading
pnpm exec vitest run packages/types/src/__tests__/object-view-unmirrored-keys-7779.test.ts 0 Test Files 1 passed (1) · Tests 147 passed (147)
pnpm --filter @object-ui/types type-check 0 all three of tsc --noEmit, tsconfig.examples.json, tsconfig.test.json echoed and green — the test file is typechecked
pnpm exec eslint packages/types/src/__tests__/object-view-unmirrored-keys-7779.test.ts 0 clean
node scripts/check-changeset-presence.mjs 0 1 source file of 1 released package changed, 1 changeset declared, empty frontmatter = the explicit exemption
node scripts/check-changeset-no-major.mjs 0 no changeset declares major
pnpm check:control-bytes 0 7111 tracked text files scanned
pnpm check:phantom-deps 0 typescript is already a declared devDependency of @object-ui/types
node scripts/check-governed-queue-guard.mjs --test (both changed paths) 0 NOT GOVERNED — 2 paths, no governed surface matched

All test runs went through the container's shared heavy-verify lock; each printed VERDICT command-exit 0.

Declared narrowing. Repo-wide pnpm lint and pnpm test are CI's runs, not this seat's. The lint reading above is narrowed to the one changed file; eslint.config.js configures no type-aware linting (no project / projectService), so this diff cannot move the verdict on any file it does not touch. Test scope is the single pin file because the diff changes exactly that file plus a changeset, and no published byte moves.

Scope

⛔ The three live branches' files were not touched: packages/types/src/complex.ts / plugin-kanban / plugin-gantt (PR #8865), packages/types/src/layout.ts / page-breadcrumbs-refusal-8871.test.ts (PR #8930), registry-inputs-spec-parity.test.ts / plugin-calendar (PR #8872).

The census extends the existing pin rather than adding a file. That file already owned both halves of this measurement (the 47 count and the seven-name read set) and is where the regex instrument being improved on lives; a second file would create a second instrument for the same number, which is the staleness shape objectui#7947 was filed about. The header's prose figures are now backed by the assertions underneath them.

⛔ This PR stays draft — the PM lands it. The card is not closed by it: the disposition is still unruled.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Jmxdo7bmeqCQHLSfmLVX9w


Generated by Claude Code


Generated by Claude Code

…#7924)

Re-derive both halves of the `listViews` measurement on today's head and pin
them per member, replacing the regex instruments with the TypeScript parser.

- declared set: walked with `ts.createSourceFile` over the interface's own
  `PropertySignature` members, not a regex and not a brace-depth count. The two
  regexes are kept and asserted against the parser, so the instrument swap is
  itself a reading: strict regex 47 = parser 47, loose regex 59.
- read set: derived through every route from `schema.listViews` to a named
  view, with every occurrence of that record classified. A new route fails as
  UNCLASSIFIED instead of silently shrinking the reading. This found a
  named-view read the old `currentNamedViewConfig?.KEY` regex cannot see —
  `{view.label || key}` on the tab strip.
- each of the 47 members is pinned by name on its side of the partition
  (6 read, 41 unread), so a member moving between the sets fails in either
  direction; `data` stays read-but-undeclared, reached via an `as any` cast.

A member reached through `activeView?.KEY` — the host's `views` prop — is not
read off the named view; `rowHeight: activeView?.rowHeight` is the pinned
counter-control.

Measurement only: no declaration face moved and no renderer read set moved.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jmxdo7bmeqCQHLSfmLVX9w
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 50 chunks) 3487.8 KB 3512.7 KB
Main entry chunk (gzip) 144.1 KB 350 KB
Entry file index-BHejIwo8.js
Status PASS

The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it.


📦 Bundle Size Report

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 16.69KB 6.21KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (ActiveOrganizationStorage.js) 25.05KB 9.16KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.18KB 10.59KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.65KB 2.22KB
auth (SocialSignInButtons.js) 9.61KB 3.89KB
auth (UserMenu.js) 3.41KB 1.23KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.21KB 10.80KB
auth (createAuthenticatedFetch.js) 8.46KB 3.43KB
auth (index.js) 3.19KB 1.44KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 11.08KB 4.58KB
collaboration (CommentThread.js) 26.08KB 7.56KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 500.20KB 114.67KB
core (index.js) 7.48KB 2.96KB
create-plugin (index.js) 28.04KB 9.46KB
data-objectstack (index.js) 200.01KB 55.77KB
fields (index.js) 246.97KB 62.30KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 1.22KB 0.64KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 6.57KB 2.76KB
i18n (index.js) 3.65KB 1.47KB
i18n (pickLocalized.js) 7.62KB 3.26KB
i18n (provider.js) 26.89KB 9.04KB
i18n (useDisplayLocale.js) 2.85KB 1.45KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 38.84KB 10.94KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 2.53KB 0.85KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 4.39KB 1.66KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 13.52KB 4.88KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.24KB 2.16KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.39KB 3.10KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 14.81KB 3.63KB
plugin-calendar (index.js) 49.03KB 13.93KB
plugin-charts (index.js) 71.39KB 19.92KB
plugin-chatbot (index.js) 194.54KB 46.34KB
plugin-dashboard (index.js) 132.41KB 34.84KB
plugin-designer (index.js) 215.68KB 44.27KB
plugin-detail (index.js) 252.45KB 65.33KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 136.79KB 34.19KB
plugin-gantt (index.js) 166.96KB 40.93KB
plugin-grid (index.js) 210.86KB 57.28KB
plugin-kanban (index.js) 57.58KB 16.47KB
plugin-list (index.js) 112.54KB 27.65KB
plugin-map (index.js) 20.49KB 6.83KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.42KB 11.92KB
plugin-timeline (index.js) 30.10KB 8.74KB
plugin-tree (index.js) 9.55KB 3.32KB
plugin-view (index.js) 84.42KB 20.80KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.66KB 3.50KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 81.07KB 26.86KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.63KB 2.18KB
react (schema-input.js) 2.32KB 1.24KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (codegen.js) 6.58KB 2.74KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 5.66KB 2.50KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (kanban-quick-add.js) 2.71KB 1.35KB
sdui-parser (parse.js) 20.57KB 5.88KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 14.82KB 4.99KB
types (ai.js) 0.20KB 0.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 1.00KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.93KB 1.49KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.74KB 2.25KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 4.73KB 2.28KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 14.27KB 5.47KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

Copy link
Copy Markdown
Collaborator Author

Green on 3b5053d45 — flipped ready and enqueued. ⚠️ The enqueue is NOT VERIFIED; the merge is what will settle it.

domain:spec @ objectui PM seat, session session_01Jmxdo7bmeqCQHLSfmLVX9w.

Eligibility, measured. get_check_runs on head 3b5053d45f85dd5d2f5767afe38800b676048627: 34 runs — 31 success, 3 skipped, 0 failure, 0 in_progress. The three skips are by design (Test (coverage), Test (coverage shard …), dependabot). ⛔ 入队资格 is every check green, ⛔ not a required subset.

⚠️ What I can and cannot prove. The flip out of draft was read back. The enqueue was not verified: enable_pr_auto_merge returns "(method: , enabled at )" with both fields empty, and this session's MCP server exposes no issue-timeline / PR-events tool, so no reading available to me distinguishes "enqueued" from "no-op". Reported as NOT VERIFIED rather than dressed up. A check-in will verify by outcome, and by content rather than an API field.

Why this PR is Clause-②: no — stated, since the card it serves is a narrowing question

No declaration face moved. The diff is exactly two files: this pin file and its changeset. packages/types/src/objectql.ts is read, never edited; no *.zod.ts is touched; no renderer's read set moves. The disposition for the 41 unread members — ?: never tombstones (a narrowing) or making the delegation read them (capability growth) — is ⛔ not taken here and stays with objectui#7928, where the four-group ruling request is now posted (5612300243).

What is worth keeping from this round

The instrument was replaced, and the replacement is itself an assertion. The strict indent-anchored regex (47) and the TypeScript parser (47) are pinned as agreeing, and the loose regex (59) is pinned as disagreeing for a stated reason — it also counts nested object-literal lines inside members' inline types. ⇒ the retired "about 52" is pinned as a hand figure sitting between two instruments and equal to neither, rather than as a sentence someone has to remember.

Completeness is enforced, not assumed. Every occurrence of the named-views record is classified into a syntactic role; a new route to a named view lands as UNCLASSIFIED and fails, instead of silently shrinking the read set. That is what caught the read the old currentNamedViewConfig?.KEY regex could not see — {view.label || key} on the tab strip — so label is now asserted at two sites.

The ablation runs in both directions, which is the property a partition pin needs: make an unread member read (navigation) → exactly the UNREAD — navigation row red, the other 40 unread rows and all 6 read rows green; make a read member unread (type) → exactly READ — type red. Mutations proven on disk by blob hash before any result was read, restored from a trap … EXIT INT TERM and proven back by blob hash with an empty git diff HEAD.

⚠️ And leg B's first attempt did not run, and was reported as void rather than retried into silence: its injected text already existed on the following line, the on-disk proof read injected = 2 against an expected 1, and the harness aborted exit 94 before running anything and restored. Re-anchored on a unique marker and re-run. ⛔ A reading taken after a mutation that did not land the way it was proven is not a green — it is nothing.


Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants