Skip to content

feat(legibility): make understandability a check that fails a diff - #381

Merged
drewstone merged 3 commits into
mainfrom
feat/legibility-gate
Aug 4, 2026
Merged

feat(legibility): make understandability a check that fails a diff#381
drewstone merged 3 commits into
mainfrom
feat/legibility-gate

Conversation

@drewstone

@drewstone drewstone commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

docs/product-surfaces.md already argued all of this — it opens by reporting an audit that scored product clarity 2–4/10 and concluded "Mechanism is not the gap; meaning is." Then the verticals shipped empty states with no next action, engineering vocabulary in error copy, a save button reporting success on a 404, and two deterministic engines no navigation reached.

A well-written document did not change behavior. This ships the same intent as five static checks a product runs against its own source in CI — the /theme-contract shape applied to meaning instead of tokens.

The checks

engineering-vocabulary · dead-end-empty-state · unchecked-success · silent-failure · unreachable-capability

Each names file:line:column plus the fix. Every finding is individually suppressible, and a suppression with no written reason is itself a finding — a gate that can only be silenced per-repo gets silenced per-repo.

Calibration was the work

Hand-read every finding on two production verticals (530 files): 121 findings at 54% precision → 84 at 82%. Fewer reports and four more real defects, because two tunings were recall fixes and three were precision fixes. Nothing was threshold-tuned; every removal was an inspected false-positive class — res.json().catch(() => ({})) is a parse fallback, not a hidden failure; a system prompt is not screen copy.

The unreachable-capability check resolves doors transitively to a fixpoint, because the real shipped defect was an island of three contract screens linking each other, which a one-hop "does a link exist" check clears entirely.

What it measures on current main

before Product v2 current main
legal-agent 74 27
tax-agent 47 28
2 0
4 1

Both severe classes — a screen that lies, and a built capability nobody can reach — are effectively gone. What remains is 36 vocabulary findings and 9 dead-end empty states: design backlog, not dishonesty.

Also in this PR

  • Canonical patterns folded into docs/product-surfaces.md (empty state, first-run as a declared state, provenance, motion, density, and the rule that an engine ships with its surface), each with a rejected example drawn from a real audit finding. A verification sweep caught the doc asserting two things the repos disprove, and one claim was corrected into a stronger named test.
  • The provenance primitive (/web-react): basis is required on a record, so a value cannot silently claim a document it does not have. Confidence never reaches the screen as a raw percentage. A computed value composes from its inputs, and standing rolls up to the weakest one, so a total never reads as better-traced than the worst number inside it.

Verification

pnpm typecheck clean · pnpm test 4244 passed (251 files) · 18 break-restore proofs, three of which came back green on the first attempt — meaning three behaviors had no guard at all, so tests were added and re-proved red.

🤖 Generated with Claude Code

A product-clarity document already existed and did not change behavior:
the verticals still shipped empty states with no next action, engineering
vocabulary in error copy, and built screens no navigation reached. This
ships the same intent as five static checks a product runs against its
own source, alongside the canonical patterns and the provenance
primitive the rubric depends on.

Each check names file, line and column with the fix, and is suppressible
only with a written reason — a reasonless suppression is itself a
finding. Calibration was the work: 317 findings across two real products
became 121 by removing hand-inspected false-positive classes, not by
tuning thresholds.

Provenance ships as a shared primitive whose basis is required, so a
value cannot silently claim a document it does not have.

@tangletools tangletools left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Auto-approved drewstone PR — 202e239d

This PR was opened by the trusted drewstone account.
The full PR reviewer audit still runs separately and will publish findings if it detects issues.

tangletools · auto-approval · reason: drewstone_author · 2026-08-04T09:09:25Z

@tangletools tangletools left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚪ Value Audit — audit-incomplete

Verdict audit-incomplete
Concerns 0 (none)
Heuristic 0.0s
Duplication 0.0s
Interrogation 90.0s (2 bridge agents)
Total 90.0s

💰 Value — error

value agent produced no parseable value-audit JSON.

  • Model: opencode/deepseek/deepseek-v4-pro
  • Bridge attempts: 3
  • Bridge error: opencode/kimi-for-coding/k2p7: Bridge returned 503: {"error":{"message":"cli-bridge admission timed out after 30000ms","type":"admission_rejected","reason":"queue_timeout","admission":{"active":20,"queued":5,"maxActive":20,"maxQueue":48}}}; opencode/zai-coding-plan/glm-5.2: Bridge returned 503: {"error":{"message":"cli-bridge admission timed out after 30000ms","type":"admission_rejected","reason":

🎯 Usefulness — error

usefulness agent produced no parseable value-audit JSON.

  • Model: opencode/deepseek/deepseek-v4-pro
  • Bridge attempts: 3
  • Bridge error: opencode/zai-coding-plan/glm-5.2: Bridge returned 503: {"error":{"message":"cli-bridge admission timed out after 30000ms","type":"admission_rejected","reason":"queue_timeout","admission":{"active":20,"queued":6,"maxActive":20,"maxQueue":48}}}; opencode/kimi-for-coding/k2p7: Bridge returned 503: {"error":{"message":"cli-bridge admission timed out after 30000ms","type":"admission_rejected","reason":

No PR concerns were produced because the value/usefulness agent pass did not complete. Treat this audit as incomplete, not as approval.


What this audit checks

It judges the change on its merits — not whether it was tasked out in an issue. Unticketed, fast-moving work is fine; the question is whether the change is good and whether a better or existing approach should be used instead.

Pass What it asks
Heuristic Vague title? Whitespace-only or cruft-bearing diff? (content signals only)
Duplication Do added function/class names already exist elsewhere in the repo?
Value Audit What does it do? What goal does it achieve? Is it good? Better architecture or already-exists?
Usefulness Audit Does it integrate and fit? Will it hold up in real use and actually get used?

Findings are concerns, not blocks — the human reviewer decides what to do with them.

value-audit · 20260804T092551Z

@tangletools

Copy link
Copy Markdown

⚠️ Review Incomplete — 202e239d

At least one required reviewer lane failed closed. No approval or request-changes review was published. This is a reviewer run failure, not a PR quality score.

Trigger a fresh review on the current PR head.

tangletools · 2026-08-04T11:07:13Z

@tangletools tangletools left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Auto-approved drewstone PR — 202e239d

This PR was opened by the trusted drewstone account.
The full PR reviewer audit still runs separately and will publish findings if it detects issues.

tangletools · auto-approval · reason: drewstone_author · 2026-08-04T11:27:14Z

@tangletools tangletools left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Value Audit — sound

Verdict sound
Concerns 1 (1 weak-concern)
Heuristic 0.0s
Duplication 0.0s
Interrogation 170.3s (2 bridge agents)
Total 170.3s

💰 Value — sound

Ships a new /legibility CI gate (5 static checks over a product's own TS/TSX source, mirroring /peer-floors//theme-contract) plus a distinct value-level ProvenanceValue affordance and three pinned docs — turns 'a well-written doc changed nothing' into 'a diff fails'. In grain, calibrated, no

  • What it does: Adds a new node-only /legibility subpath: checkLegibility() + agent-app-legibility-check bin with five static checks over a consumer's own source — engineering-vocabulary (codebase words on screen), dead-end-empty-state (empty branch with no button/link/control in its subtree), unchecked-success (success signal in a handler that never reads res.ok/.status), silent-failure (catch arou
  • Goals it achieves: Make product understandability mechanically enforceable. docs/product-surfaces.md already argued all of this (opened with a 2–4/10 clarity audit) and changed nothing: verticals then shipped 11-of-21 empty states with no next action, 'materialized' in error copy, a Settings save reporting 'Saved' on a 404, and an island of three contract screens no nav reached. The gate turns five of those defect
  • Assessment: Strong, in the grain of the codebase. (1) The shape claim is correct and verified: /legibility is a structural clone of /peer-floors and /theme-contract — node-only, runs over the CONSUMER's own source, exit codes 0/1/2, JSON option, zero runtime deps, same bin shape, same wiring. (2) Additive-subpath rule satisfied (tsup + package.json exports + knip + bin all present). (3) Calibration is m
  • Better / existing approach: none — this is the right approach. Searched for existing equivalents: git ls-files src | rg -i 'lint|vocab|banned' returns only the new src/legibility/checks/vocabulary.ts; no prior static checker for any of the five defect classes exists in this repo. git ls-files src/web-react | rg -i 'provenance|source-marker|lineage' returns only the new files plus the run-level ProvenanceStamp in `wor
  • Model: opencode/zai-coding-plan/glm-5.2
  • Bridge attempts: 2
  • Bridge warning: opencode/kimi-for-coding/k2p7: opencode: opencode error

🎯 Usefulness — sound

A coherent, well-integrated understandability gate that follows the codebase's established node-only-CI-checker pattern exactly, with five checks each calibrated against measured production defects and a suppression design that keeps the gate alive.

  • Integration: Fully wired and reachable: package.json exports ./legibility (package.json:464) + bin agent-app-legibility-check (package.json:36); tsup.config.ts entries (tsup.config.ts:89-90); knip.json entries (knip.json:36-37); AGENTS.md module-map row added; docs generated (docs/api/legibility.md, docs/api/legibility-cli.md). It is a new additive subpath — no root barrel touched, no existing export
  • Fit with existing patterns: Textbook fit. It is explicitly 'the /theme-contract shape applied to meaning instead of tokens' (src/legibility/index.ts:16-18), and the structural parallel is exact: node-only fs mechanism, checkX(config) -> {ok, findings} API, exit 0/1/2 bin contract, consumer-declared config file (legibility.config.mjs), per-line suppression. No competing pattern exists in the package — grep finds no ot
  • Real-world viability: Hardened against the one failure mode that kills this class of gate — false positives. The vocabulary check reads only lexer-PROVEN copy positions (JSX text + allowlisted copy attributes + COPY_CALLS first arg), with object-key copy opt-in because it 'was MEASURED as the false-positive tier' (src/legibility/checks/vocabulary.ts:34-41). The silent-failure check walks a fluent .catch chain BACKWAR
  • Model: opencode/zai-coding-plan/glm-5.2
  • Bridge attempts: 1

💰 Value Audit

🟡 One PR ships two subpaths with different peer floors [proportion] ``

The diff adds both a node-only gate (/legibility, zero peers, src/legibility/*) and a React affordance (/web-react provenance, react peer, src/web-react/provenance*.ts(x)). They are tied by the rubric (Q3 needs the primitive to exist) and the commit message names the tie, but they have different consumers and different peer floors — a product may want the gate without the React surface or vice-versa. A stricter split would be feat(legibility): the gate then `feat(web-react): Provenance


What this audit checks

It judges the change on its merits — not whether it was tasked out in an issue. Unticketed, fast-moving work is fine; the question is whether the change is good and whether a better or existing approach should be used instead.

Pass What it asks
Heuristic Vague title? Whitespace-only or cruft-bearing diff? (content signals only)
Duplication Do added function/class names already exist elsewhere in the repo?
Value Audit What does it do? What goal does it achieve? Is it good? Better architecture or already-exists?
Usefulness Audit Does it integrate and fit? Will it hold up in real use and actually get used?

Findings are concerns, not blocks — the human reviewer decides what to do with them.

value-audit · 20260804T113018Z

@tangletools

Copy link
Copy Markdown

❌ Needs Work — 202e239d

Review health 100/100 · Reviewer score 0/100 · Confidence 95/100 · 57 findings (3 critical, 15 high, 14 medium, 25 low)

glm deepseek deepseek-flash aggregate
Readiness 0 0 0 0
Confidence 95 95 95 95
Correctness 0 0 0 0
Security 0 0 0 0
Testing 0 0 0 0
Architecture 0 0 0 0

Reviewer score is advisory once the run is complete and the verdict has no blockers.

Full multi-shot audit completed 8/8 planned shots over 43 changed files. Global verifier still owns final merge decision. | Full multi-shot audit completed 8/8 planned shots over 43 changed files. Global verifier still owns final merge decision. | Full multi-shot audit completed 8/8 planned shots over 43 changed files. Global verifier still owns final merge decision.

Blocking

🟣 CRITICAL 7 module entries missing from codemap — 174 exports undocumented — docs/codemap.json

The regeneration dropped these module entries entirely: ./documents (37 exports), ./documents/pdf-inspector (4), ./openui (35), ./openui-react (8), ./record (33), ./record/drizzle (22), ./web-react/async (35). All 7 are still active in tsup.config.ts entries, package.json exports, and have intact source files on disk. Verified: tsup.config.ts grep returns entries for all 7; package.json exports all 7; source files exist at their expected paths. The codemap is the primary machine-readable API documentation consumed by downstream tooling — missing entries mean these subpaths appear to not exist.

🟣 CRITICAL animate-pulse count assertion fails at HEAD (17 vs 16) — tests/docs/legibility-contract.test.ts

Ran vitest run at HEAD 202e239: expected 17 to be 16. countUsages('animate-pulse').total walks all of src/ and counts 17 occurrences; statedCount('animate-pulse') parses '16 times' from docs/product-surfaces.md Pattern 4. The missing 17th is src/web-react/record-grid.tsx:512 (className="h-8 animate-pulse rounded-md bg-muted/50"), a real shipped loading-skeleton row added in #380 and omitted from Pattern 4's cited-file list. This is precisely the 'count drifts the moment someone adds a usage' failure the test's header comment names — the test caught it in its own companion doc. Impact: CI red; the new test file cannot ship green. Fix (in docs/product-surfaces.md, out of this shot's scope): bump 16→17 and add record-grid.tsx to the cited pulse-file list, or reconcile both sides.

🟣 CRITICAL animate-spin count assertion fails at HEAD (6 vs 5) — tests/docs/legibility-contract.test.ts

Ran vitest run at HEAD 202e239: expected 6 to be 5. The test parses the doc's word 'Five' → 5 (via the {Three..Eight} map), but src/ contains six real animate-spin usages across design-canvas-react/components/CanvasInsertPanel.tsx, web-react/mission-activity.tsx, web-react/index.tsx, web-react/chat-composer.tsx, web-react/work-product.tsx, and web-react/async/async-view.tsx. The doc's 'Five animate-spin uses' undercounts by one. Impact: CI red alongside the pulse failure. Fix (in docs/product-surfaces.md): change 'Five' → 'Six'.

🔴 HIGH ATTACHMENT_ACCEPT value wrong in web-react API docs — docs/api/web-react.md

The doc shows the value as "image/*,.pdf,.txt,.md,.csv,.json,.yaml,.yml,.html" but the actual source at src/chat-routes/attachment-validation.ts:54 is 'image/*,.pdf,.docx,.xlsx,.pptx,.txt,.md,.csv,.json,.yaml,.yml,.html'. The .docx, .xlsx, and .pptx extensions are missing from the doc. This constant is re-exported through src/web-react/use-composer-attachments.ts:50. A consumer reading the docs would conclude Office files are not accepted by the upload gate, which is incorrect.

🔴 HIGH docs/api/web-react.md is stale — fails the codemap-fresh contract test — docs/api/web-react.md

Committed file says '297 exports' but agent-docs --check (run from PR head 202e239) reports web-react.md as changed and regenerating produces '343 exports'. I verified this directly: node node_modules/@tangle-network/agent-docs/dist/cli.js --repo . --check exits non-zero with 'changed: docs/api/web-react.md'. The merge commit 202e239 merged main (d316e9d, carrying record-grid + Office-attachment source) into the legibility branch but did not regenerate docs. Concretely missing from the committed file: (1) all 46 RecordGrid* / recordGrid* / with(out)RecordGrid* / validateRecordGrid* / sumRecordGrid* / sameRecordGrid* exports — still public via src/web-react/index.tsx:53 export * from './record-grid' -> record-grid.tsx:69 export * from './record-grid-model'; (2) the ATTACHMENT_ACCEPT

🔴 HIGH web-react API docs missing all RecordGrid exports — docs/api/web-react.md

The regenerated web-react.md dropped ~44 RecordGrid exports (RecordGrid, RecordGridColumn, RecordGridRow, recordGridOk, recordGridFail, etc.) despite the source still exporting them via export * from './record-grid' at src/web-react/index.tsx:53. The old doc had 91 references to RecordGrid/recordGrid; the new doc has 0. Source files (src/web-react/record-grid.tsx, src/web-react/record-grid-model.ts) are unchanged in this PR. This makes consumers think these shared components are unavailable. Impact: any consumer reading the docs to find the shared record grid will conclude it doesn't exist.

🔴 HIGH web-react.md dropped the live RecordGrid API section; the repo's doc-freshness gate fails — docs/api/web-react.md

This PR's regeneration removed all 46 RecordGrid*/recordGrid* entries (RecordGrid, RecordGridProps, withRecordGridUpdate, parseRecordGridInput, sumRecordGridColumn, EMPTY_RECORD_GRID_OVERLAY, …) and changed the header from '315 exports.' to '297 exports.', but the source API is unchanged: src/web-react/index.tsx:53 still does export * from './record-grid' (record-grid.tsx:69 re-exports record-grid-model.ts), and neither record-grid file is touched in this PR. So docs/api/web-react.md now under-documents a shipped, public API of @tangle-network/agent-app/web-react and states a wrong export count. Ground-truth: node node_modules/@tangle-network/agent-docs/dist/cli.js --repo . --check exits 1 ('docs are STALE'), and a fresh agent-docs run produces 343 exports for web-react.md, restori

🔴 HIGH 45 RecordGrid exports dropped from ./web-react codemap entry — docs/codemap.json

The head codemap has 297 web-react exports vs 315 on base. 45 RecordGrid-prefixed exports (RecordGrid, RecordGridBooleanColumn, RecordGridCellChange, recordGridOk, etc.) are gone, but src/web-react/index.tsx line 53 still has export * from './record-grid' and src/web-react/record-grid.tsx exists. Running tsc --noEmit src/web-react/record-grid.tsx shows 96 JSX-related errors (missing --jsx flag), suggesting the codemap generator fails on these files under its compilation environment and silently drops their exports.

🔴 HIGH Committed codemap.json is stale — fails the repo's own codemap-fresh CI gate — docs/codemap.json

Ran agent-docs --repo <root> --check (the exact command in tests/codemap-fresh.test.ts:24): exit 1, docs are STALE, listing docs/codemap.json as changed. The committed file documents 83 entries; the package actually ships 90 (tsup.config.ts:5-94 declares 90 entry points, all with present source files and package.json exports). 7 real subpaths were dropped entirely: ./documents, ./documents/pdf-inspector, ./openui, ./openui-react, ./record, ./record/drizzle, ./web-react/async. Each still has its source file (src/documents/index.ts, src/record/index.ts, src/openui/index.ts, src/web-react/async/index.ts, etc.), is built by tsup.config.ts, and is exported in package.json. Regeneration (agent-docs) writes 90 entries and restores all 7 with nothing removed. Impact: tests/codemap-fresh.te

🔴 HIGH Merge dropped 7 subpaths from codemap.json; staleness gate fails — docs/codemap.json

Entry-id sets: base d316e9d 88 entries, head 202e239 83 entries. Missing at head: ./documents, ./documents/pdf-inspector, ./openui, ./openui-react, ./record, ./record/drizzle, ./web-react/async — all added to the codemap by main #380 (1b771e1) and all still present in head source (ls src/documents src/openui src/record src/web-react/async confirms). The merge commit 202e239 resolved docs/codemap.json to the legibility branch's version (git diff d76dde5 202e239 -- docs/codemap.json is empty), discarding main's additions. Ground truth: running the same generator (agent-docs 0.2.1, unchanged base→head) via node node_modules/@tangle-network/agent-docs/dist/cli.js --repo . --check exits non-zero and prints 'docs are STALE ... changed: docs/codemap.json'; regenerating writes back all 7 entries

🔴 HIGH Auto-generated docs missing 7 active subpath sections after merge — docs/llms-full.txt

The docs at HEAD have 83 H2 sections claiming '83 entries' but tsup.config.ts has 90 entries. Seven tsup entries present in tsup.config and package.json exports are absent from docs: documents/index, documents/pdf-inspector, openui/index, openui-react/index, record/index, record/drizzle, web-react/async/index. Source files exist at HEAD with exports (verified: src/documents/index.ts has 4 export blocks, src/openui/index.ts has 4, src/record/index.ts exports model/fold/ulid). Root cause: docs were generated at commit d76dde5 (83 entries) and not re-generated after merge 202e239 restored 7 entries. Impact: consumers reading this file for API reference find no documentation for 7 active subpaths.

🔴 HIGH Merge dropped 7 subpath sections from the generated full-API reference — docs/llms-full.txt

Head tsup.config.ts declares 90 entries including documents/index, documents/pdf-inspector, openui/index, openui-react/index, record/index, record/drizzle, web-react/async/index (all with matching head source under src/). Head docs/llms-full.txt has 83 sections and omits exactly those 7 (./documents, ./documents/pdf-inspector, ./openui, ./openui-react, ./record, ./record/drizzle, ./web-react/async); base d316e9d documented all 7 (verified: 'extractDocument', 'parseOpenUISegments', 'foldRecordEntries', 'useAsyncResource' each appear once in base docs, zero times in head docs). The header at line 5 claims '83 entries, every export with its signature' while tsup.config.ts has 90

🔴 HIGH Doc's measured counts are stale at the merged head; the PR's own enforcement test fails — docs/product-surfaces.md

Pattern 4 states animate-pulse appears 16 times (line 447) and Five animate-spin uses (line 449). Verified against the merged tree: 17 animate-pulse occurrences across 8 source files and 6 animate-spin occurrences across 6 files. The merge commit 202e239 (chore: merge main) introduced src/web-react/record-grid.tsx (animate-pulse loading skeleton at :512) and src/web-react/async/async-view.tsx (animate-spin at :110), both absent at d76dde5 where the counts were genuinely 16/5. npx vitest run tests/docs/legibility-contract.test.ts fai

🔴 HIGH Pattern 4 animate-pulse count is 16, source has 17 — PR's own contract test fails — docs/product-surfaces.md

Doc states: "animate-pulse appears 16 times". The PR's own contract test tests/docs/legibility-contract.test.ts:242 re-counts substring occurrences across src/** (excluding tests/.d.ts) and asserts equality; it reads 17. Reproduced: vitest run tests/docs/legibility-contract.test.ts -> expected 17 to be 16. The 17 occurrences live in AssistantPanel.tsx(1), VaultPane.tsx(5), mission-activity.tsx(1), result-canvas.tsx(1), session-history.tsx(3), web-react/index.tsx(3), record-grid.tsx(1), message-attachments.tsx(2 — one is a code comment that the substring counter includes). Impact: CI red on a docs-only PR; the doc's central promise ('a measured count in this document that no longer matches this package's source' fails the build) is violated by the doc itself. Fix: change '**16 tim

🔴 HIGH Pattern 4 animate-spin count is 'Five', source has 6 — contract test fails — docs/product-surfaces.md

Doc states: "Five animate-spin uses". The PR's own contract test tests/docs/legibility-contract.test.ts:255-262 parses the word before 'animate-spin uses' into a number and asserts equality with the source count; it reads 6. Reproduced: vitest run tests/docs/legibility-contract.test.ts -> expected 6 to be 5. The 6 occurrences: design-canvas-react/components/CanvasInsertPanel.tsx, web-react/mission-activity.tsx, web-react/index.tsx, web-react/chat-composer.tsx, web-react/work-product.tsx, web-react/async/async-view.tsx (none are comments, so this is a genuine miscount, not a counting-method disagreement). Impact: same as above — CI red. Fix: change 'Five' to 'Six'.

🔴 HIGH PR ships a failing test — doc counts stale vs src at head — tests/docs/legibility-contract.test.ts

npx vitest run tests/docs/legibility-contract.test.ts at head 202e239 fails 2/34. Test 242: doc states animate-pulse appears 16 times (docs/product-surfaces.md:447) but countUsages() measures 17 in src (extra occurrence: src/web-react/message-attachments.tsx:11, a comment). Test 261: doc states 'Five animate-spin uses' (product-surfaces.md:449) but src has 6 (src/web-react/index.tsx:1167, src/web-react/work-product.tsx:248, src/design-canvas-react/components/CanvasInsertPanel.tsx:110, src/web-react/mission-activity.tsx:432, src/web-react/chat-composer.tsx:398, src/web-react/async/async-view.tsx:110). All occurrences pre-exist at base d316e9d (verified by git show), so the counts were wrong when the doc/test were written in this same PR. vitest include pattern `tests/**/*.test.{ts

🔴 HIGH Test ships with 2 failing assertions — doc counts don't match source — tests/docs/legibility-contract.test.ts

The test asserts that the animate-pulse count in pattern 4 (stated: 16) matches the source. Actual count is 17 (1 in record-grid.tsx not listed in the doc, 1 in a JSDoc comment in message-attachments.tsx:11). The document at line 447 names 7 files but misses src/web-react/record-grid.tsx which also carries animate-pulse. Either the document count and file list must be updated, or the test expectation must be set to match the current document (meaning the test won't catch future drift).

🔴 HIGH Test ships with failing animate-spin count assertion — tests/docs/legibility-contract.test.ts

Document line 449 says 'Five animate-spin uses' but src has 6 occurrences (work-product.tsx, index.tsx, async-view.tsx, chat-composer.tsx, mission-activity.tsx, and CanvasInsertPanel.tsx). The CanvasInsertPanel.tsx usage at line 110 is not accounted for in the document's count. The test correctly surfaces this drift, but the test should not land red — either the document count must be updated to 'Six' or the source usage must be removed.

Other

🟠 MEDIUM Export count changed from 315 to 297 — net loss larger than additions — docs/api/web-react.md

The doc header went from '315 exports' to '297 exports' (net -18). The PR added ~33 provenance exports but dropped ~44 RecordGrid exports and misgenerated ATTACHMENT_ACCEPT. The net effect is an undocumented API surface regression: the only intentional code change was export * from './provenance' added at src/web-react/index.tsx:49. The RecordGrid drops and ATTACHMENT_ACCEPT value change are agent-docs tool regressions, not intended removals.

🟠 MEDIUM ./tools dependsOn dropped the openui dependency it still has — docs/codemap.json

Committed ./tools entry (docs/codemap.json:13269-13272) lists dependsOn as ["crypto","eval"], but src/tools/openai.ts:1 still imports OPENUI_INTERACTIVE_AUTHORING_GUIDE from '../openui/authoring' (and src/tools/types.ts:58 references it), so the real dependency edge to ./openui remains. The regenerated codemap correctly reports dependsOn ["crypto","eval","openui"]. This is a second symptom of the same stale regeneration; it understates the module graph a reviewer or dependency-aware tool would read. Fixed by the same pnpm docs:gen pass.

🟠 MEDIUM ATTACHMENT_ACCEPT signature truncated — docs/codemap.json

Source at src/chat-routes/attachment-validation.ts has 'image/*,.pdf,.docx,.xlsx,.pptx,.txt,.md,.csv,.json,.yaml,.yml,.html'. The head codemap shows 'image/*,.pdf,.txt,.md,.csv,.json,.yaml,.yml,.html' — .docx,.xlsx,.pptx are missing. The base codemap had the full value. This is not a source change — the source line is byte-unchanged from base — it's a generator extraction bug.

🟠 MEDIUM chat-routes entry misdocuments the attachment allow-list and Office mime exports — docs/codemap.json

The committed chat-routes entry shows ATTACHMENT_ACCEPT = "image/*,.pdf,.txt,.md,.csv,.json,.yaml,.yml,.html" and omits OOXML_SNIFFED_MIMES, MACRO_ENABLED_OOXML_SNIFFED_MIMES, OOXML_WORD_MIME, OOXML_SPREADSHEET_MIME, OOXML_PRESENTATION_MIME and their macro-enabled variants. Head source still ships the post-#380 surface: src/chat-routes/attachment-validation.ts:54-55 defines ATTACHMENT_ACCEPT including '.docx,.xlsx,.pptx' and :61-77 export OOXML_SNIFFED_MIMES / MACRO_ENABLED_OOXML_SNIFFED_MIMES / the six OOXML mime consts. Regenerating at head restores the exact signatures the base codemap carried. Same root cause as the dropped modules (stale merge of a generated file), but a distinct consumer-facing symptom: any codemap/CODEMAP.md reader concludes Office packages are not admitted and that

🟠 MEDIUM Stale ATTACHMENT_ACCEPT and missing OOXML constants after merge — docs/llms-full.txt

ATTACHMENT_ACCEPT shows '"image/,.pdf,.txt,.md,.csv,.json,.yaml,.yml,.html"' but source at HEAD shows '"image/,.pdf,.docx,.xlsx,.pptx,.txt,.md,.csv,.json,.yaml,.yml,.html"'. Merge commit 202e239 restored .docx/.xlsx/.pptx via a git merge main that brought back the OOXML attachment support from main. The docs also show 0 OOXML references while source has 36 in binary-sniff.ts + 23 in attachment-validation.ts = 59. Both OOXML_SNIFFED_MIMES and the 6 individual mime constants (OOXML_WORD_MIME, etc.) exported via src/chat-routes/index.ts (line 11: export * from './binary-sniff') are absent. Impact: developer reading docs would believe only image/pdf/text are accepted, but

🟠 MEDIUM chat-routes section stale: OOXML_* exports and ATTACHMENT_ACCEPT value reverted to a pre-fork state — docs/llms-full.txt

The ./chat-routes section at head dropped all 8 constants base documented (OOXML_WORD_MIME, OOXML_SPREADSHEET_MIME, OOXML_PRESENTATION_MIME, the three _MACRO_ENABLED_MIME, OOXML_SNIFFED_MIMES, MACRO_ENABLED_OOXML_SNIFFED_MIMES; base lines 2235-2381) and shows ATTACHMENT_ACCEPT as 'image/,.pdf,.txt,.md,.csv,.json,.yaml,.yml,.html'. Head source contradicts both: src/chat-routes/index.ts still does export * from './binary-sniff' and export * from './attachment-validation', src/chat-routes/binary-sniff.ts exports all six OOXML mime constants, and src/chat-routes/attachment-validation.ts exports OOXML_SNIFFED_MIMES, MACRO_ENABLED_OOXML_SNIFFED_MIMES and ATTACHMENT_AC

🟠 MEDIUM llms-full.txt is stale: drops 7 live subpaths and 8+ live constants that still exist at HEAD — docs/llms-full.txt

The regenerated doc documents a surface that does not match the HEAD source. (1) MISSING subpath sections: ./documents, ./documents/pdf-inspector, ./openui, ./openui-react, ./record, ./record/drizzle, ./web-react/async are all absent from the ## \...` heading list at HEAD, yet tsup.config.ts:76-81 still declares every one of these entries (documents/index, documents/pdf-inspector, openui/index, openui-react/index, record/index, record/drizzle, web-react/async/index) and package.json exports still ships them. (2) MISSING exported constants: the OOXML block (OOXML_WORD_MIME, OOXML_SPREADSHEET_MIME, OOXML_PRESENTATION_MIME, OOXML_*_MACRO_ENABLED_MIME, OOXML_SNIFFED_MIMES, MACRO_ENABLED_OOXML_SNIFFED_MIMES`) was deleted from the doc but is still exp

🟠 MEDIUM 'agent-app-uplift' citations mislabel files that live on this branch, bypassing the contract test's existence check — docs/product-surfaces.md

The doc's own citation convention (lines 276-278) says a bare src/… path is an in-repo citation checked for existence by tests/docs/legibility-contract.test.ts, while a cross-repo citation 'names its repo first'. But agent-app-uplift src/web-react/record-grid.tsx (:395, :401), agent-app-uplift src/web-react/async/state.ts (:308, :540), async-view.tsx:19 (:312) and use-confirmed-mutation.ts (:541) are all cited as if on another branch, while git ls-tree HEAD confirms all these files exist on this branch at HEAD (they came in via base d316e9d/main). Consequences: (1) inRepoCitations() only matches spans starting with src/|tests/|docs/, so these citatio

🟠 MEDIUM animate-pulse count is wrong: doc says 16, source has 17 — docs/product-surfaces.md

Document states 'animate-pulse appears 16 times' but the contract test tests/docs/legibility-contract.test.ts:242 measures 17 non-test occurrences in this commit's src tree (src/assistant/AssistantPanel.tsx:1, src/vault/VaultPane.tsx:5, src/web-react/session-history.tsx:3, src/web-react/mission-activity.tsx:1, src/web-react/index.tsx:3, src/web-react/record-grid.tsx:1, src/web-react/message-attachments.tsx:2, src/studio-react/result-canvas.tsx:1). Either the count in the doc (16) or the count in the source must be updated for the contract test to pass.

🟠 MEDIUM animate-spin count is wrong: doc says 5, source has 6 — docs/product-surfaces.md

Document states 'Five animate-spin uses' but the contract test tests/docs/legibility-contract.test.ts:261 measures 6 non-test occurrences in this commit's src tree (src/design-canvas-react/components/CanvasInsertPanel.tsx:1, src/web-react/mission-activity.tsx:1, src/web-react/index.tsx:1, src/web-react/chat-composer.tsx:1, src/web-react/async/async-view.tsx:1, src/web-react/work-product.tsx:1). The count must be updated to 6 or the extra usage removed for the contract test to pass.

🟠 MEDIUM Missing or unreadable route config silently disables unreachable-capability with a green build — src/legibility/checks/reachability.ts

readScanned (line 453) swallows every error and returns null; when options.routeConfigFile is set but unreadable, or the file lexes to zero route() calls, declared is empty and the function returns { findings: [], routeFile: null, routeCount: 0 }. Back in index.ts:138-139, result.routeFile is null so nothing is collected and no error surfaces, while checksRun (index.ts:151) still lists 'unreachable-capability'. Verified by probe: checkReachability({ routeConfigFile: 'does-not-exist.ts' }) → 0 findings, routeFile null. A typo'd --routes src/rotues.ts in CI produces 'legibility OK — 5 check(s)' with the check never having run. This is exactly the class t

🟠 MEDIUM matchingParen duplicated 4× across check files — src/legibility/checks/success.ts

The identical matchingParen(masked, open) function is copy-pasted in four check files: success.ts:82, reachability.ts:195, silent-failure.ts:304, empty-state.ts:191. All four are byte-identical. If a bug is found (e.g. off-by-one on the return value) or the function needs a change, it would need to be fixed in all four places. Since source.ts already exports shared parser utilities (enclosingBlock, enclosingFunctionBlock), this should live there too.

🟠 MEDIUM Measurement-method mismatch: doc excludes comments, test counts them — tests/docs/legibility-contract.test.ts

countUsages() does readFileSync(file,'utf8').split(needle).length-1, i.e. counts every substring occurrence including comments and strings. The doc's '16 times' is only correct if the comment occurrence at src/web-react/message-attachments.tsx:11 ('the loading skeleton is an inline animate-pulse span') is excluded — the remaining 16 real JSX usages match the doc's list exactly (VaultPane 5, AssistantPanel 1, mission-activity 1, session-history 3, index 3, result-canvas 1, record-grid 1, message-attachments:182 1). So fixing only the animate-spin count leaves this assertion red unless the doc is bumped to 17 or countUsages strips comment/string occurrences. The test and doc must share one definition of 'usage'; today they disagree.

🟠 MEDIUM countUsages counts JSDoc comment references alongside actual usages — tests/docs/legibility-contract.test.ts

countUsages (line 219) uses readFileSync(file, 'utf8').split(needle).length - 1 which counts raw string occurrences including JSDoc comments. In message-attachments.tsx:11, the comment 'an inline animate-pulse span' is counted as a usage alongside the actual className at line 182. This makes the test count drift from a human countable-usage count permanently. The document author likely counted 1 for this file (the actual className) but the test counts 2. Recommend documenting that the count includes comment/string-lite

🟡 LOW JsonObject doc field contains wrong text — docs/codemap.json

The ./web entry's JsonObject type has doc 'Web-boundary utilities every agent app's routes hand-roll: JSON body parsing + narrowing, request-context extraction...' — this is the module-level description, not the JsonObject type's own doc. Base had null for this field. The generator conflated module-level docs with export-level docs.

🟡 LOW Entry count header inaccurate — docs/llms-full.txt

Docs header says '83 entries, every export with its signature' but tsup.config.ts has 90 entries at HEAD. The count reflects the pre-merge state (commit d76dde5) and was not updated when the merge restored 7 entries.

🟡 LOW Pattern 4 spinner claim understates the fleet after the merge — docs/product-surfaces.md

Beyond the wrong count (Five vs Six), the 'spinner with nothing to say' rejected example omits src/web-react/async/async-view.tsx:110, which is now an animate-spin user on this branch — the same unlabelled-spinner defect the sentence describes. Same root cause as the high finding (merge drift); updating the count and adding the file keeps the rejected example accurate to the merged source.

🟡 LOW reachability doors include path literals that don't resolve to the product — src/legibility/checks/reachability.ts

The fallback at lines 295-298 counts ANY path-shaped literal (/something/…) as a door for reachability, treating non-route files as always-available. While documented as the generous direction that can only REMOVE findings, a string literal like '/api/members' from a node_modules import or a serialized path in a test fixture could falsely mark a route reachable. The suffix-matching rule mitigates this (the literal's segments must be a suffix of the route's), so the blast radius is narrow.

🟡 LOW CONSOLE_CALL regex supports only one nesting level — src/legibility/checks/silent-failure.ts

The regex /\bconsole\s*\.\s*[a-z]+\s*\([^()]*(\([^()]*\)[^()]*)*\)/g handles at most one level of nested parentheses in console call arguments. A call like console.error(formatError(new Error(msg))) would not be fully matched, leaving parts of it visible in the body. This could cause a false positive if the visible content happens to match an error surface pattern. Console calls this deeply nested are unusual in practice.

🟡 LOW carriesError not applied to .catch() handlers — src/legibility/checks/silent-failure.ts

The carriesError escape hatch (checking whether the caught error binding is used in non-console code) is only applied for try/catch blocks (line 147) but not for .catch() promise handlers (lines 166-187). A .catch((err) => sendToExternalTelemetry(err)) pattern where sendToExternalTelemetry does not match any ERROR_SINK pattern would be reported as a silent failure. Mitigation: the product can add sendToExternalTelemetry to extraErrorSinks.

🟡 LOW INSPECTS_RESPONSE matches body fields and unrelated variables, letting the 'Saved on a 404' defect through — src/legibility/checks/success.ts

INSPECTS_RESPONSE = /.\s*(ok|status|statusText)\b|{[^}\n]\b(ok|status)\b[^}\n]}\s*=/ matches any .ok/.status anywhere in the function body. Verified by probe: (a) const body = await fetch(url).then(r => r.json()); if (body.status !== 'ready') return; toast.success('Saved') → 0 findings, and (b) await fetch(url); if (store.ok) toast.success('Saved') → 0 findings. The first reads a business field off a parsed body (a 404 body parses fine), the second an unrelated variable — neither inspects the HTTP response, yet both are treated as checked. This is the exact defect class the check exists for (Settings answering 'Saved' to a 404). Fix: constrain the regex to a response-shaped receiver (a res/response/destructured variable bound from the fetch, or a call within the same state

🟡 LOW checksRun reports unreachable-capability as run when no reachability config exists — src/legibility/index.ts

checksRun is LEGIBILITY_CHECKS.filter(enabled) — enabled() returns true by default — but the actual run is gated on enabled(config, 'unreachable-capability') && config.reachability (line 134). A product that never supplies reachability config sees '5 check(s)' on the OK line and believes the capability was checked; it wasn't. Combined with the route-config finding above, two distinct paths make the report claim a check ran that never executed. Fix: only include a check in checksRun when it will actually execute, or drive checksRun from the same guard.

🟡 LOW srcDirs pointing at a file or unreadable dir yields 0 files and a green build — src/legibility/scan.ts

walkSources catches readdirSync errors and returns []; scanSources therefore reports filesScanned: 0 and checkLegibility returns ok:true (only srcDirs.length===0 throws, index.ts:97). A misconfigured srcDir (a single file, a path outside the repo, a build dir excluded by SKIP_DIRS) silently passes as 'legibility OK — 0 file(s)'. Unlike the route-config case this is visible in the report text, but a CI that greps exit code alone is green. Recommend failing (or at least warning loudly) when a configured srcDir does not exist or contributes zero files.

🟡 LOW walkSources does not guard against symlink cycles — src/legibility/scan.ts

readdirSync with withFileTypes returns Dirent objects whose isDirectory() follows symlinks to directories. A circular symlink inside a srcDir could cause infinite recursion. SKIP_DIRS covers node_modules (where pnpm symlinks live) but not arbitrary symlink cycles. Low risk for a CI tool scanning the product's own source tree. Fix: add entry.isSymbolicLink() check or a visited-set by realpath.

🟡 LOW Regex literal bodies are not masked, inconsistent with the documented contract — src/legibility/source.ts

The ScannedSource.masked field is documented as 'text with every comment, string body and template body replaced by spaces' and structural regexes (CATCH_RE, CALL_RE, IO_IN_TRY, etc.) rely on masked source so patterns inside string literals cannot match. However, regex literals are skipped by skipRegex() WITHOUT calling blank() — the regex body survives in masked. Verified: scanSource('const re = /catch\s*\{/g') leaves 'catch' in masked. Practical impact is low because each check has contextual guards: silent-failure requires tryBlockBefore (no try block before a regex literal), success dedupes by scope.start, empty-state requires .map( inside a real iteration. A theoretical false positive: a regex literal containing 'fetch(' at file top-level plus a success signal elsewhere in the same

🟡 LOW DIRECTIVE_RE lacks word boundary, can match 'legibility-ignore' inside other words — src/legibility/suppress.ts

The regex /legibility-ignore(-file)?\s+([^\n])/ has no leading \b, so a comment like '// please legibility-ignore this section' matches, treats 'this' as a check name, and generates a 'names "this", which is not a check' self-finding. The phrase is specific enough that natural occurrence is rare, and the false positive is self-explanatory. Fix: prepend \b — /\blegibility-ignore(-file)?\s+([^\n])/.

🟡 LOW entered-no-source is 'settled' while extracted-no-source is 'confirm' — unexplained trust asymmetry — src/web-react/provenance-model.ts

provenanceGaps only emits a no-source gap for basis === 'extracted' (line 228), and standingFromBasis starts entered at settled (line 193). So {display:'$X', basis:'entered'} with no sources renders 'Traced' with no stated gap — yet checkableAgainst says 'the person who entered it', and with no named person nothing is actually checkable. The parallel extracted shape forces confirm. Either the design intends the entering person to be implicit (then say so and pin it with a test), or an un-named person should pro

🟡 LOW provenanceNextMove emits a dead instruction for a confidence-weakened computed value — src/web-react/provenance-model.ts

For a computed record that HAS inputs but whose OWN standing is check (e.g. {basis:'computed', confidence:0.7, inputs:[...]}), none of the guards fire and it falls through to return generic — 'Open the source and confirm this value before you rely on it.' Verified by executing the real function: returns the generic for that shape. A computed value has no source to open, so this is exactly the dead-instruction defect the function's own docstring (lines 347-352) exists to prevent. The existing guard at line 377 only han

🟡 LOW Dead code: all-loading message condition can never be true — src/web-react/provenance.tsx

const loading = loadingProvenanceSources(record) returns a filtered subset of record.sources, so whenever loading.length > 0 then sources.length > 0 necessarily holds — loading.length > 0 && sources.length === 0 is always false. The intended general 'Looking up where this came from…' state (a record whose only sources are still resolving, with none ready) never renders; only the per-row 'Looking up …' status does. Harmless today (each loading row carries its own text) but the condition is a logic-vs-intent mismatch that should either be removed or expressed as loading.length > 0 && loading.length === sources.length.

🟡 LOW Module-global openPanels Set is hidden cross-instance coupling — src/web-react/provenance.tsx

const openPanels = new Set() lives at module scope and is shared by every ProvenanceValue instance across every React root in the document. The 'one trail open at a time' behavior is intentional and documented, but a product that legitimately needs two independent provenance surfaces (e.g. a main return panel and a side-by-side comparison drawer) cannot opt out — opening one will close the other even when they are visually unrelated. The Set is also a test-isolation footgun in principle (though testing-library auto-cleanup clears it via the useEffect return, and the in-file tests pass). No action required for this PR; flagging because a per-root React context would be the more compositional shape if multi-surface products ever appear.

🟡 LOW Unvalidated source.href scheme in the open-source anchor — src/web-react/provenance.tsx

<a href={source.href} target="_blank" rel="noreferrer"> renders the caller/model-supplied URL with no scheme allowlist. React escapes text content but not URL schemes, so a javascript: or data: href becomes an executable link on a review surface — precisely the surfaces (tax evidence rows, record-grid per-cell sources) that feed model-produced ProvenanceSource objects into this component. Products using onOpenSource routing are safe, but the href path is a real consumer contract. Fix: gate on /^(https?:)?/// (and fall back to named-but-not-openable, which the component already supports).

🟡 LOW aria-controls references a panel that does not exist when closed — src/web-react/provenance.tsx

The trigger always sets aria-controls={panelId}, but the element with that id is only rendered inside {open && ...}. While closed, the button references a nonexistent node — an invalid aria relationship for screen-reader tooling (and the reason the perceivable() test helper deliberately excludes id/aria-controls). Minor: set aria-controls only when open, or keep the panel mounted with hidden semantics.

🟡 LOW href attribute not sanitized against javascript: URLs — src/web-react/provenance.tsx

The <a href={source.href}> tag passes the caller-supplied href directly to the DOM. React does not sanitize href attributes, so a javascript:alert(1) or data:text/html,... value would execute on click. In this codebase, provenance source data is authored by the application's own backend and agent tool outputs, not by end users, so the practical risk is extremely low. Still, in a shared library, documenting the trust assumption on the ProvenanceSource.href field or adding a ^(https?://|/) prefix guard in a follow-up would close the surface.

🟡 LOW maxDepth default of 2 silently truncates deep provenance trees — src/web-react/provenance.tsx

maxDepth defaults to 2. A real tax return (wages → subtotals → AGI → taxable income → tax) can easily nest 4+ levels; at depth 3 the component renders the truncated branch (value + basis + origin sentence, no disclosure). The truncation is fail-safe (never a bare number) and overridable via prop, so this is a product-tuning note, not a defect. Worth mentioning in any consumer-facing example doc so products with deep composition know to bump maxDepth.

🟡 LOW openPanels global Set leaks across React roots — src/web-react/provenance.tsx

The openPanels Set is a module-level singleton shared across ALL React roots on the page. In a single-root SPA this is fine, but if a page mounts multiple React roots (portal, embedded widget, multi-app shell), opening a trail in root A would close one in root B via closeTrailsOutside. The Set entries carry DOM element references that become stale if a root unmounts without triggering the useEffect cleanup (e.g., a root replaced by a client-side router outside React). Consider scoping the Set to a React context or accepting this as a documented constraint given the current consumer landscape is single-root.

🟡 LOW source.href rendered unsanitized in tag — src/web-react/provenance.tsx

When onOpenSource is absent and source.href is set, the component renders . A product that passes source.href = 'javascript:...' (or any string carrying executable content) creates a click-XSS vector. React 19 (this repo pins react 19.2.8) does not sanitize . ProvenanceSource is product-controlled rather than user input so this is defense-in-depth, not a live vuln, but the rest of /web-react's clickable links route through injected resolvers (e.g. MessageAttachments' resolveFileUrl). If a product ever surfaces a user-editable href, this becomes exploitable. Cheapest fix: add a tiny isSafeUrl(href) guard that rejects anything not starting with http:, https:, mailto:, or /. If it fails, render the source as named-but-not-openable

🟡 LOW 'blames' filter is a no-op; test checks all src/*.tsx citations, not Pattern 4's — tests/docs/legibility-contract.test.ts

const cited = inlineSpans(surfaces).filter(span => /^src\/.*\.tsx$/.test(span)); const blamed = cited.filter(path => surfaces.includes(${path})). Since cited is derived from inlineSpans(surfaces), every path is already a literal substring of surfaces, so the blamed filter is trivially true for all elements — it does not narrow to Pattern 4's blamed files as the test name states. It passes today because all src/*.tsx citations happen to still contain animate-pulse, but the assertion is weaker than advertised and would not catch a non-Pattern-4 citation losing its pulse. To match its name, scope the spans to the Pattern 4 section body before filtering.

🟡 LOW Per-file blame assertion weaker than its title and docstring claim — tests/docs/legibility-contract.test.ts

The test title promises every blamed file is re-checked, and the file header states 'a cited file is renamed and the evidence points at nothing' must break the build, but the assertion is only expect(pulsing.length).toBeGreaterThan(0) — it passes while ANY of the src/*.tsx spans anywhere in product-surfaces.md still pulses, even if one or more cited files have stopped using animate-pulse. The blamed = cited.filter(path => surfaces.includes(...)) step is also a no-op (every span from inlineSpans is by construction a substring of surfaces). Consider asserting per-citation equality of expected-vs-present, or narrowing to Pattern 4's rejected block.

🟡 LOW statedCount regex fragile to document restructuring — tests/docs/legibility-contract.test.ts

The statedCount regex uses [^.\n]*? between the backticked phrase and the bold count. If a future doc edit inserts a period, semicolon, or line break between the phrase and its count (e.g., 'animate-pulse appears in loading skeletons. 16 times total.'), the regex silently fails to match and throws the generic error at line 237 rather than naming which part of the structure changed. Consider a more tolerant matcher or a diagnostic error that reports what regex part failed.


tangletools · 2026-08-04T12:01:23Z · trace

@tangletools tangletools left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❌ 18 Blocking Findings — 202e239d

Full multi-shot audit completed 8/8 planned shots over 43 changed files. Global verifier still owns final merge decision. | Full multi-shot audit completed 8/8 planned shots over 43 changed files. Global verifier still owns final merge decision. | Full multi-shot audit completed 8/8 planned shots over 43 changed files. Global verifier still owns final merge decision.

Full immutable report for this review: trace

Summary comment for this run: full summary


tangletools · 2026-08-04T12:01:23Z · immutable trace

@drewstone
drewstone merged commit 9b794be into main Aug 4, 2026
1 check passed
@drewstone
drewstone deleted the feat/legibility-gate branch August 4, 2026 16:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants