Skip to content

chore: tech polish — UrlInput state, notice a11y, table virtualization, TODO.md audit - #81

Merged
dbolser merged 4 commits into
mainfrom
chore/tech-polish
Jul 24, 2026
Merged

dbolser merged 4 commits into
mainfrom
chore/tech-polish

Conversation

@dbolser

@dbolser dbolser commented Jul 24, 2026

Copy link
Copy Markdown
Owner

Bundle of the small known tech items from PLAN.md section 4.

What / why

1. UrlInput no longer loses the typed URL when the Data section collapses

PanelSection unmounts its children while collapsed, so the URL draft held in UrlInput local state was wiped by a collapse/expand. UrlInput is now a controlled component: the draft lives in App (urlDraft) and is passed through ControlPanel. Bonus: the draft also survives the empty-state → loaded transition, since both UrlInput instances share the same state.
Guarantee: typecheck (the new required props make an unwired usage a compile error) + existing ControlPanel suites updated and green.

2. a11y live regions on the notice banners

The column-limit and empty-columns notices now carry role="status" + aria-live="polite" so screen readers announce them (declined as out-of-scope in #67 triage, bundled here). The URL-error banner already had role="alert" (assertive live region — appropriate for an error), left as-is. Also added the missing aria-label on the column-limit dismiss button and role="alert" on the URL validation error.
Guarantee: covered by typecheck + existing suites (markup-only change).

3. Data table: windowed rendering, 1,000-row cap removed (noted in #60)

Dependency-free virtualization: computeRowWindow(scrollTop, viewportHeight, totalRows, rowHeight, overscan) → {start, end, padTop, padBottom} in src/utils/tableLayout.ts picks the visible row window; DataTable renders only those rows between top/bottom spacer <tr>s, so the full dataset (30k+ rows) scrolls with a ~constant DOM row count (≈ viewport/37px + 2×5 overscan ≈ a few dozen <tr>s). Rows are fixed-height (TABLE_ROW_HEIGHT = 37px, whitespace-nowrap cells), the header is sticky, sorting jumps back to the top, and the viewport height tracks the resizable panel via ResizeObserver. capTableRows/TABLE_ROW_CAP and the page-based pagination are gone; the table look is otherwise unchanged.
Guarantee: (a) — the windowing math is a pure clamped function with 12 unit tests in src/test/tableLayout.test.ts (spacers+rows == full scroll height invariant, end-of-data clamping, stale scrollTop after dataset shrink, negative overscroll, empty data, partial rows, defaults, constant DOM row count). Perf conventions hold: per scroll frame the work is one slice of ~30 rows — no O(N) DOM.

4. TODO.md final audit → deleted

Every remaining item verified against the code:

Item Verdict
Non-destructive column filtering Done — displayColumns derived via filterColumns useMemo (B5)
vitest ^3.2.4 "not an official release" Stale — 3.2.4 is an official release
selectedStateHash first-5-IDs collision risk Done — full sorted-set additive hash (selectionUtils.ts)
dataStateHash robustness Done — versioned dataStateHash feeding buildRenderKey
setTimeout leak / caching-in-setTimeout Done — RAF scheduler; effect cleanup cancels both rafId and timeoutId
Consolidate cache refs / remove unused refs Stale — old triple-ref cache replaced by canvasElementsRef + ImageDataLRU snapshots
Hardcoded gridSize Done — single default parameter in selectionUtils.ts
Magic numbers (2.5 point radius, padding/2) Fixed here (point radius now uses shared POINT_RADIUS); padding/2 declined — pervasive, self-explanatory, render-path churn
'@' alias → ./src + alias in tests Declined — components//App.tsx live at the repo root; nothing imports via @/
Unused DndProvider/HTML5Backend test imports Fixed here
Filtering test re-implements app logic Fixed here — tests now call the real filterColumns, incl. a new comma-OR case
Non-null assertion on canvasContainerRef Done — guarded access
Memoize visible column count Stale — computed in App, passed as visibleDisplayCount
Random notes (duplicate spatial-grid callbacks, any[] typing) Done

Nothing genuinely-undone remained to move to PLAN.md (only the pre-existing RAF-tuning line stays in section 4). TODO.md deleted; references updated in PLAN.md and ROADMAP.md; CLAUDE.md's stale "first 5 IDs" hash description corrected (it doesn't mention TODO.md).

Gates

  • npm run typecheck ✅
  • npm run test:run ✅ (31 files, 369 tests)
  • npm run build ✅

🤖 Generated with Claude Code

https://claude.ai/code/session_0152hQSk9mV9zoZJBSRAJQWn

dbolser and others added 3 commits July 24, 2026 14:57
…ns on notices

- UrlInput is now a controlled component: the draft URL lives in App and is
  passed through ControlPanel, so collapsing the Data panel section (which
  unmounts its children) no longer wipes the typed text. The empty-state view
  shares the same draft, so it also survives the empty->loaded transition.
- Notice banners (column-limit, empty-columns) now have role="status" +
  aria-live="polite" so screen readers announce them; the URL error banner
  already had role="alert". Added missing aria-labels on dismiss buttons and
  role="alert" on the URL validation error.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0152hQSk9mV9zoZJBSRAJQWn
The full-dataset table view was capped at the first 1,000 rows (issue #56 /
PR #60). Replace the cap and per-page pagination with simple, dependency-free
windowed rendering: only the rows in the scroll viewport (plus a small
overscan) are mounted, with top/bottom spacer rows standing in for the rest,
so the FULL dataset (30k+ rows) is scrollable with a ~constant DOM row count.

- computeRowWindow(scrollTop, viewportHeight, totalRows, rowHeight, overscan)
  -> {start, end, padTop, padBottom} lives in src/utils/tableLayout.ts as a
  pure, clamped function; capTableRows/TABLE_ROW_CAP are removed.
- DataTable rows have a fixed height (TABLE_ROW_HEIGHT, whitespace-nowrap
  cells), the header is sticky, sorting scrolls back to the top, and the
  viewport height tracks the resizable panel via ResizeObserver.
- App no longer caps tableRows or passes capNote.
- Windowing math unit-tested (spacer/row-height invariant, end-of-data and
  shrunk-dataset clamping, overscroll, empty data, defaults).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0152hQSk9mV9zoZJBSRAJQWn
Audit verdicts (checked against the code):
- High 1 (non-destructive column filter): done — displayColumns derived via
  filterColumns useMemo (B5).
- High 2 (vitest ^3.2.4 'not official'): stale — 3.2.4 is an official release.
- High 3/4 (selection/data hash robustness): done — full-sorted-set additive
  hash + versioned dataStateHash feeding buildRenderKey.
- High 5 / Medium 3 (setTimeout cleanup / caching): done — RAF paint scheduler
  with effect cleanup cancelling both rafId and timeoutId.
- Medium 1 (consolidate cache refs) / Low 1 (unused refs): stale — the old
  triple-ref cache is gone (canvasElementsRef + ImageDataLRU snapshots now).
- Medium 2 (gridSize): done — single default parameter in selectionUtils.
- Low 2 (magic numbers): fixed here for the point radius — Canvas 2D path now
  uses the shared POINT_RADIUS from webglPoints; padding/2 left as-is
  (pervasive, self-explanatory, render-path churn not worth it).
- Low 3/4 ('@' alias -> ./src): declined — components/ and App.tsx live at the
  repo root, so a src-rooted alias doesn't fit this layout; nothing uses '@/'.
- Low 5 (unused DndProvider/HTML5Backend imports): fixed here.
- Low 6 (filtering test re-implements app logic): fixed here — tests now call
  the real filterColumns, incl. comma-OR semantics.
- Low 7 (non-null assertion on canvasContainerRef): done — guarded access.
- Low 8 (memoize visible count): stale — count is computed in App and passed
  down as visibleDisplayCount.
- Random notes (duplicate spatial-grid callbacks, any[] typing): done.

Also updates the TODO.md references in PLAN.md/ROADMAP.md and corrects
CLAUDE.md's stale 'first 5 IDs' hash description.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0152hQSk9mV9zoZJBSRAJQWn
@gemini-code-assist

Copy link
Copy Markdown
Contributor

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

@coderabbitai

coderabbitai Bot commented Jul 24, 2026 •

Copy link
Copy Markdown

Warning

Review limit reached

@dbolser, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 48 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: d19d2e2e-4dba-40cb-bede-24aff3c3b9ea

📥 Commits

Reviewing files that changed from the base of the PR and between b6723a8 and 7eb361c.

📒 Files selected for processing (16)
  • App.tsx
  • CLAUDE.md
  • PLAN.md
  • ROADMAP.md
  • TODO.md
  • components/ControlPanel.tsx
  • components/DataTable.tsx
  • components/ScatterPlotMatrix.tsx
  • components/UrlInput.tsx
  • src/test/categoryLegend.test.tsx
  • src/test/columnReordering.test.ts
  • src/test/componentLogic.test.tsx
  • src/test/facetPanel.test.tsx
  • src/test/tableLayout.test.ts
  • src/utils/tableLayout.ts
  • src/utils/webglPoints.ts
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch chore/tech-polish

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Jul 24, 2026 •

Copy link
Copy Markdown
Contributor

Preview deployment

Preview Commit
https://dbolser.github.io/Demonstrable-Plotalizer/previews/pr-81/ 7eb361c

Published to previews/pr-81/ on gh-pages; updated on every push, removed when the PR closes. Boot check: node checks/preview-boot.mjs https://dbolser.github.io/Demonstrable-Plotalizer/previews/pr-81/

…tech-polish

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0152hQSk9mV9zoZJBSRAJQWn
@dbolser
dbolser merged commit c235c93 into main Jul 24, 2026
4 checks passed
@dbolser
dbolser deleted the chore/tech-polish branch July 24, 2026 14:08
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.

1 participant