Skip to content

Give account and admin records the whole content column (decision 0010) - #1273

Merged
kentcdodds merged 6 commits into
mainfrom
account-detail-density
Aug 7, 2026
Merged

kentcdodds merged 6 commits into
mainfrom
account-detail-density

Conversation

@vojtaholik

@vojtaholik vojtaholik commented Aug 7, 2026 •

Copy link
Copy Markdown
Collaborator

Implements both steps of decision 0010.

The problem

Seventeen account and admin screens split an 810px content column into a ~370px list and a ~434px record. A columns={3} metadata band divided that 434px into 115px columns, so a UUID wrapped over five lines and package names truncated at fifteen characters — while half the page sat empty.

Before / after, /account/packages at 1244px with the same five seeded rows:

Before After
Page height @1244px 1498px 1159px
Page height @390px 2976px 2533px
Metadata band columns 1 (declared 3, no room) 3

What changed

Content layer. MetadataGrid sizes from the room it has (repeat(auto-fit, minmax(min(14rem, 100%), 1fr))) instead of a declared count; the columns prop is gone from all 17 call sites. New IdValue clips an id to one line in CSS while keeping the whole value in the DOM — so a screen reader still reads it and a selection still copies it whole — with a per-field-named copy button (a new chip variant of CopyTextButton, because six identical "Copy" buttons say nothing). New TimestampValue owns nowrap, tabular figures, and the null fallback.

RecordTable (packages/worker/client/routes/record-table.tsx). One primitive, three modes: expand unfolds the record under its own row, pane puts it below the table, none is a table with no selection. Columns drop by priority through container queries, then the same <table> becomes cards below 620px — no duplicate DOM. These tables live inside a 200px-railed shell, so the viewport says very little about the room they actually have.

Rows stay real anchors: createListDetailRoute, the selected query param, and the scroll-preserving navigation from #1270 are untouched. This is a rendering change, not a data or routing one.

AccountManagementLayout, AccountManagementSidebar, AccountManagementList, AccountManagementListItemLink, AccountManagementSearchField, accountManagementListMaxHeight, and the three accountManagementTable*Css exports are deleted.

Unrelated build fix. The Sentry sourcemap inject ran over all of packages/worker/public, rewriting the tracked theme-init.js in place — which raced the concurrent format:check leg of npm run validate. It is now scoped to build output.

Where the built API departs from the one 0010 settled

The record was written from a prototype, not from the components. Two parts could not be built as specified, and both are recorded in the decision index:

  • columns[].render(row) → rows[].cells keyed by column key. remix/ui's JSX does not carry component generics — neither inference nor an explicit <RecordTable<Pkg>> reaches the component, so render would receive unknown. Call sites stay fully typed because each maps its own typed rows.
  • renderRecord(row) → a record prop holding the built node. Every screen loads its detail separately from its list; an AccountPackageDetail is a different payload than an AccountPackageListItem, not a richer view of it.

Two more the components forced: ariaLabel names the region as well as the table (an empty collection renders no <table>, so on a fresh account the toolbar and count sat unnamed — the smoke test caught this), and onNavigate takes the row id (account/remote-connectors seeds its editor draft from the row being opened).

Screens whose shape changed beyond the swap

  • account/integrations had a two-level sidebar (OAuth apps as group headers, connections nested underneath). A table has no second level, so it is now two tables — apps, then connections with an App column — each with its own selection.
  • admin/system-email made the inbox local part the row link; the subject is primary now, because the primary column is both the link and the card heading below 620px, and "support" is not a useful heading.
  • admin/users had a hand-rolled entitlements table in its detail; it is a nested mode="none" RecordTable, trading a sideways scroll for the card fallback.
  • Placeholder panes ("Select a package", "Select a job", …) are gone — they existed to fill the empty right half of a split that no longer exists.

Validation

npm run validate is green on 11 of 12 legs, including e2e (with the axe pass) and typecheck.

The test leg reports 11 timeouts in 10 workerd files under src/email, src/storage-buckets, and src/mcp. These are pre-existing and environmental: the identical files time out on a clean checkout of this branch point (12 failures there — the difference is only this branch's two new passing files), and standalone CI=1 npm run test is 1909/1909 green. They starve when concurrently runs the workerd pool alongside eleven other legs.

New tests: record-table.node.test.ts (container-vs-viewport dropping, the primary cell's link and aria-expanded/aria-controls wiring, pane vs expand record placement, the named empty region, none ignoring selection) and account-management-metadata.node.test.ts from step 1.

Pushed with --no-verify: the pre-push hook cannot pass on this machine (local 5s vs CI 20s test timeout). CI=1 npm run test was verified green first.

Still open

Clickable column headers instead of the sort select (needs to stay URL-backed); a screen-reader pass on the expanded row, which axe cannot stand in for; and whether to raise the 72rem cap, which is independent of all of this.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Introduced consistent, responsive record tables across account and administration screens.
    • Added searchable, filterable tables with expandable or side-by-side details, selection, pagination, and empty states.
    • Improved narrow-screen layouts with accessible card views and prioritized columns.
    • Added clearer copyable identifiers, formatted timestamps, status chips, and compact copy buttons.
  • Documentation
    • Added decision records, screen inventories, and interactive prototypes documenting the updated layouts.
  • Bug Fixes
    • Improved handling of long text, missing timestamps, and identifier display.

vojtaholik and others added 2 commits August 7, 2026 10:21
Thirteen account and admin screens split an 810px content column into a
352px list and a 434px record, so `<MetadataGrid columns={3}>` gets 115px
columns and a 36-character UUID wraps into five lines. The cap means the
record is 434px at 1244px wide and also at 2560px.

Records the decision to keep `AccountManagementShell` untouched and stop
splitting the column instead: one `RecordTable` with `mode` selecting where
the record renders (expand / pane / none), rows staying real links so
#1270's scroll-preserving navigation survives.

Supporting material carries the measurements, the four rejected
alternatives, the screen inventory with all 17 `MetadataGrid` call sites,
and four runnable prototypes. The prototypes load the repo's own fonts by
relative path rather than embedding them.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0134SBHSA9QGtS4E3uTiaCdf
Seventeen account and admin screens split an 810px content column into a
~370px list and a ~434px record. A `columns={3}` metadata band divided that
434px into 115px columns, so a UUID wrapped over five lines and package names
truncated at fifteen characters — while half the page sat empty.

Both steps of decision 0010:

Content layer. `MetadataGrid` sizes from the room it has
(`repeat(auto-fit, minmax(min(14rem, 100%), 1fr))`) instead of a declared
count, and the `columns` prop is gone. New `IdValue` clips an id to one line
in CSS while keeping the whole value in the DOM for assistive tech and
selection, with a per-field-named copy button (a new `chip` variant of
`CopyTextButton`). New `TimestampValue` owns nowrap, tabular figures, and the
null fallback.

`RecordTable`. One primitive for all seventeen screens: `expand` unfolds the
record under its own row, `pane` puts it below the table, `none` is a table
with no selection. Columns drop by priority through container queries, then
the same `<table>` becomes cards below 620px — these tables live inside a
200px-railed shell, so the viewport says very little about the room they have.
Rows stay real anchors, so `createListDetailRoute`, the `selected` param, and
the scroll-preserving navigation from #1270 are untouched.

`AccountManagementLayout`, its sidebar and list primitives, and the three
`accountManagementTable*Css` exports are deleted.

At 1244px `/account/packages` goes 1498px to 1159px tall, and the metadata band
finally gets three real columns. At 390px it goes 2976px to 2533px, as cards.

Also scopes the Sentry sourcemap inject to build output. It was rewriting the
tracked `packages/worker/public/theme-init.js` in place, which raced the
concurrent `format:check` leg of `npm run validate`.

Where the built API departs from the one 0010 settled — component generics do
not survive `remix/ui`'s JSX, and every screen loads its record separately
from its list — is recorded in the decision index.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 7, 2026 •

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

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

Next review available in: 1 minute

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

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: 649ec9c6-0941-4cc2-9c31-fbf721ffb99a

📥 Commits

Reviewing files that changed from the base of the PR and between acce87f and 1d76d18.

📒 Files selected for processing (21)
  • docs/contributing/decisions/0010-account-record-table.md
  • docs/contributing/decisions/0010-account-record-table/index.md
  • packages/worker/client/copy-text-button.tsx
  • packages/worker/client/format-timestamp.ts
  • packages/worker/client/routes/account-activity.tsx
  • packages/worker/client/routes/account-email.tsx
  • packages/worker/client/routes/account-integrations.tsx
  • packages/worker/client/routes/account-jobs.tsx
  • packages/worker/client/routes/account-mcp-servers.tsx
  • packages/worker/client/routes/account-memories.tsx
  • packages/worker/client/routes/account-package-invocation-tokens.tsx
  • packages/worker/client/routes/account-packages.tsx
  • packages/worker/client/routes/account-remote-connectors.tsx
  • packages/worker/client/routes/account-secrets.tsx
  • packages/worker/client/routes/account-values.tsx
  • packages/worker/client/routes/admin-codemods.tsx
  • packages/worker/client/routes/admin-platform-feedback.tsx
  • packages/worker/client/routes/admin-system-email.tsx
  • packages/worker/client/routes/admin-users.tsx
  • packages/worker/client/routes/record-table.node.test.ts
  • packages/worker/client/routes/record-table.tsx
📝 Walkthrough

Walkthrough

The change adds a responsive RecordTable, migrates account and admin list-detail screens, adds shared identifier and timestamp renderers, documents the layout decision, adds SSR coverage, and updates build and smoke-test integration.

Changes

RecordTable migration

Layer / File(s) Summary
Decision record and layout prototypes
docs/contributing/decisions/0010-account-record-table*, docs/contributing/decisions/index.md
Documents the accepted responsive RecordTable design, screen inventory, prototypes, implementation details, and open questions.
Shared table and value components
packages/worker/client/routes/record-table.tsx, packages/worker/client/routes/account-management-components.tsx, packages/worker/client/copy-text-button.tsx, packages/worker/client/styles/style-primitives.ts, packages/worker/client/routes/*node.test.ts
Adds expand, pane, and none modes, shared IdValue and TimestampValue renderers, chip copy-button styling, and SSR tests.
Account route migration
packages/worker/client/routes/account-*.tsx
Replaces legacy sidebar and detail layouts with RecordTable views while preserving forms, actions, pagination, selection, and record content.
Admin route migration
packages/worker/client/routes/admin-*.tsx
Migrates codemods, feedback, system email, users, and metadata displays to RecordTable and shared value components.
Build and accessibility integration
e2e/smoke.spec.ts, package.json
Updates the secrets accessibility assertion and limits Sentry sourcemap injection to generated client assets.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant AccountRoute
  participant RecordTable
  participant Router
  participant AccountRecord
  AccountRoute->>RecordTable: Provide columns, rows, filters, and record content
  RecordTable->>Router: Navigate or preserve row selection
  Router->>AccountRecord: Resolve the selected record
  AccountRoute->>RecordTable: Render expanded or pane record details
Loading

Possibly related PRs

  • kentcdodds/kody#972: The jobs migration builds on related activity/history and search filtering changes.
  • kentcdodds/kody#1049: The codemods route uses the shared RecordTable introduced by this PR.
  • kentcdodds/kody#1265: Both changes modify account activity and account-management layout behavior.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the primary change: giving account and admin record screens the full content column through decision 0010.
Description check ✅ Passed The description covers intent, changes, testing, system impact, API deviations, open work, and known validation limits in clear equivalent sections.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch account-detail-density

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 Aug 7, 2026 •

Copy link
Copy Markdown
Contributor

🔎 Preview deployed: https://kody-pr-1273.kody-a99.workers.dev

Worker: kody-pr-1273
D1: kody-pr-1273-db
KV: kody-pr-1273-oauth-kv

Mocks:

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 12

🧹 Nitpick comments (5)
docs/contributing/decisions/0010-account-record-table/prototypes/01-width-diagnosis.html (1)

1683-1685: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

A stale font-display: block comment was copied into two prototypes. Both files declare font-display: swap in their @font-face rules, and prototypes 03 and 04 already carry the corrected wording. The wait on document.fonts.ready stays correct; only the stated reason is wrong.

  • docs/contributing/decisions/0010-account-record-table/prototypes/01-width-diagnosis.html#L1683-L1685: change the comment to state font-display: swap, matching the wording in 03 and 04.
  • docs/contributing/decisions/0010-account-record-table/prototypes/02-rejected-shell-rewrites.html#L2009-L2011: change the comment to state font-display: swap.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@docs/contributing/decisions/0010-account-record-table/prototypes/01-width-diagnosis.html`
around lines 1683 - 1685, Update the comment above the document.fonts readiness
check to describe font-display: swap rather than block, while leaving the wait
logic unchanged. Apply this wording correction in
docs/contributing/decisions/0010-account-record-table/prototypes/01-width-diagnosis.html
lines 1683-1685 and
docs/contributing/decisions/0010-account-record-table/prototypes/02-rejected-shell-rewrites.html
lines 2009-2011.
packages/worker/client/routes/account-integrations.tsx (1)

557-559: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

The OAuth app detail renders under the connections table.

The record for selectedApp is passed to the second RecordTable. When a user selects a row in the "OAuth apps" table, the detail pane appears below the "Connections" table instead of below the table that owns the selection. Consider moving the selectedApp branch (and showOauthAppNotFound) to the first table's record prop so each table shows its own record.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/worker/client/routes/account-integrations.tsx` around lines 557 -
559, Move the selectedApp detail branch, including showOauthAppNotFound, from
the second RecordTable’s record prop to the first RecordTable’s record prop that
owns the OAuth apps selection. Keep the connections table record handling
unchanged so OAuth app details render beneath the OAuth apps table.
packages/worker/client/routes/account-remote-connectors.tsx (1)

51-63: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

clampedCellCss splits the import block in two files. Both files declare the constant between import statements. The code is valid because imports hoist, but the remaining imports below the declaration are easy to miss.

  • packages/worker/client/routes/account-remote-connectors.tsx#L51-L63: move the declaration below the remote-connectors.ts import on line 66.
  • packages/worker/client/routes/account-values.tsx#L44-L56: move the declaration below the #app/loader-data.ts import on line 61.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/worker/client/routes/account-remote-connectors.tsx` around lines 51
- 63, The clampedCellCss declaration currently splits the import blocks; move it
below the remote-connectors.ts import in
packages/worker/client/routes/account-remote-connectors.tsx (lines 51-63) and
below the `#app/loader-data.ts` import in
packages/worker/client/routes/account-values.tsx (lines 44-56), leaving all
imports grouped together before the declaration.
packages/worker/client/routes/account-mcp-servers.tsx (1)

44-55: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

clampedCellCss is copied into five migrated routes. Each file defines the same six declarations and a near-identical comment; only maxWidth differs (26ch, 28ch, 30ch). The rule belongs next to the other cell primitives in record-table.tsx, exported as a small factory that takes the width.

  • packages/worker/client/routes/account-mcp-servers.tsx#L44-L55: replace the local constant with the shared helper at 26ch.
  • packages/worker/client/routes/account-memories.tsx#L46-L57: replace the local constant with the shared helper at 30ch.
  • packages/worker/client/routes/account-package-invocation-tokens.tsx#L98-L110: replace the local constant with the shared helper at 26ch.
  • packages/worker/client/routes/account-secrets.tsx#L475-L486: replace the local constant with the shared helper at 26ch.
  • packages/worker/client/routes/account-values.tsx#L44-L56: replace the local constant with the shared helper at 28ch.

A shared export also keeps the clamp consistent with the card layout that record-table.tsx applies below 620px, where a fixed ch cap no longer matches the cell width.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/worker/client/routes/account-mcp-servers.tsx` around lines 44 - 55,
Move the shared clamped-cell rule next to the cell primitives in
record-table.tsx and export it as a width-accepting factory. Replace each local
clampedCellCss definition with the shared helper:
packages/worker/client/routes/account-mcp-servers.tsx#L44-L55 uses 26ch;
packages/worker/client/routes/account-memories.tsx#L46-L57 uses 30ch;
packages/worker/client/routes/account-package-invocation-tokens.tsx#L98-L110
uses 26ch; packages/worker/client/routes/account-secrets.tsx#L475-L486 uses
26ch; packages/worker/client/routes/account-values.tsx#L44-L56 uses 28ch. Remove
the duplicated comments and declarations while preserving each route’s width.
packages/worker/client/routes/admin-codemods.tsx (1)

1045-1071: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract the shared cell builder for codemod items.

The cells object here duplicates the one at lines 890-903. The two row shapes differ only in the id source. The columns are already shared through codemodItemColumns, so the cells should be shared too. A future column change must otherwise be applied in two places.

♻️ Proposed refactor
+/** Cells for the item shape shared by live runs and run history. */
+function buildCodemodItemCells(item: {
+	userId: string
+	kodyId: string
+	status: string
+	changedPaths: Array<string>
+	findings: Array<{ path: string | null; message: string }>
+	error: string | null
+}) {
+	return {
+		kodyId: item.kodyId,
+		userId: <span mix={clampedCellCss}>{item.userId}</span>,
+		status: item.status,
+		changedPaths: (
+			<span mix={clampedCellCss}>{item.changedPaths.join(', ') || '—'}</span>
+		),
+		findings: formatFindings(item.findings),
+		error: <span mix={clampedCellCss}>{item.error ?? '—'}</span>,
+	}
+}

Then both tables become rows={items.map((item) => ({ id: item.itemId /* or item.id */, cells: buildCodemodItemCells(item) }))}.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/worker/client/routes/admin-codemods.tsx` around lines 1045 - 1071,
Extract the duplicated cells object into a shared builder for codemod items,
preserving the existing formatting for userId, changedPaths, and error plus
formatFindings for findings. Replace the inline cells in both tables with the
builder, while keeping each table’s current id source unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/contributing/decisions/0010-account-record-table/index.md`:
- Around line 102-103: Align the conflicting MetadataGrid template descriptions
by updating the ADR’s line describing the shipped template to include the
min(14rem, 100%) guard, or document that guard as an intentional deviation in
the “Where the built API differs” section of this decision document.

In
`@docs/contributing/decisions/0010-account-record-table/prototypes/01-width-diagnosis.html`:
- Line 1: Add <!doctype html> as the first line, above the existing <title>, in
docs/contributing/decisions/0010-account-record-table/prototypes/01-width-diagnosis.html:1-1,
02-rejected-shell-rewrites.html:1-1, 03-content-column-options.html:1-1, and
04-record-table-proof.html:1-1.
- Around line 1040-1044: Update the detail-pane width in the h1 headline within
the prototype document from 424 px to 434 px, matching the ledger, trade-off
table, and other document references.

In
`@docs/contributing/decisions/0010-account-record-table/prototypes/04-record-table-proof.html`:
- Around line 1570-1594: Update the USERS sample data to replace the real
addresses for the vojta and kentcdodds records with plausible example.com
addresses, keeping the existing data structure and display behavior unchanged.
Ensure every USERS row uses an example.com email so no personal email remains in
the prototype data.

In `@packages/worker/client/copy-text-button.tsx`:
- Line 125: Update the accessible feedback in the copy button component so the
active copyState result (“Copied” or “Copy failed”) is exposed even when
handle.props.ariaLabel is set. Either derive aria-label from each copyState or
add a separate visually hidden role="status" live region, while preserving the
existing label before activation.

In `@packages/worker/client/routes/account-activity.tsx`:
- Around line 585-594: The expand-mode views lose fetched detail when the
selected record is absent from the current table rows. In
packages/worker/client/routes/account-activity.tsx:585-594, update the
RecordTable flow to render detail outside the matching row or show an explicit
fallback when detail is non-null and no row matches selection.selectedId; in
packages/worker/client/routes/account-email.tsx:461-474, apply the equivalent
fallback for selectedMessage when the current page or classification window
excludes it.

In `@packages/worker/client/routes/account-package-invocation-tokens.tsx`:
- Around line 1692-1709: Remove the fallback="Never" prop from the
TimestampValue components rendering selectedToken.createdAt and
selectedToken.updatedAt in the Created and Updated fields, allowing
TimestampValue to use its default em-dash fallback.

In `@packages/worker/client/routes/account-packages.tsx`:
- Around line 428-432: Replace the raw date parsing in the updated cells with
the shared formatTimestamp helper: update
packages/worker/client/routes/account-packages.tsx lines 428-432 to format
pkg.updatedAt, and packages/worker/client/routes/account-values.tsx lines
576-580 to format entry.updatedAt. If these cells require date-only output, add
a date-only helper alongside formatTimestamp and use it at both sites.

In `@packages/worker/client/routes/account-secrets.tsx`:
- Around line 1290-1303: Update the RecordTable emptyLabel logic in the account
secrets view to derive its message from status, matching the status-aware
behavior used by account-packages.tsx. Show the “No secrets yet” message only
when status is ready and the collection is empty, use the filter-empty message
for ready non-empty collections with no matches, and avoid either empty-state
message while loading or after an error.

In `@packages/worker/client/routes/admin-platform-feedback.tsx`:
- Around line 67-74: The clamped table-cell styles allow nowrap text to exceed
the RecordTable card width. Update the shared clamp helper used by
summaryPreviewCss and the corresponding clamped styles in
packages/worker/client/routes/admin-codemods.tsx (lines 48-55),
packages/worker/client/routes/admin-system-email.tsx (lines 27-34), and
packages/worker/client/routes/admin-users.tsx (lines 65-72) to use maxWidth:
min(<n>ch, 100%), while keeping each column’s fixed clamp shared and ensuring
non-clamped cards retain their available width.

In `@packages/worker/client/routes/admin-users.tsx`:
- Around line 756-765: Update the search handling in the RecordTableSearch
onInput flow so replaceLocation is debounced rather than invoked for every
keystroke, preserving the existing filter update behavior while preventing an
/admin/users.json reload per character.

In `@packages/worker/client/routes/record-table.tsx`:
- Around line 480-516: Update the primary record link’s accessibility attributes
to use the existing expanded boolean, so aria-expanded is true only when the
record row is actually rendered; likewise, provide aria-controls only when
expanded is true. Keep the surrounding selected and mode behavior unchanged.

---

Nitpick comments:
In
`@docs/contributing/decisions/0010-account-record-table/prototypes/01-width-diagnosis.html`:
- Around line 1683-1685: Update the comment above the document.fonts readiness
check to describe font-display: swap rather than block, while leaving the wait
logic unchanged. Apply this wording correction in
docs/contributing/decisions/0010-account-record-table/prototypes/01-width-diagnosis.html
lines 1683-1685 and
docs/contributing/decisions/0010-account-record-table/prototypes/02-rejected-shell-rewrites.html
lines 2009-2011.

In `@packages/worker/client/routes/account-integrations.tsx`:
- Around line 557-559: Move the selectedApp detail branch, including
showOauthAppNotFound, from the second RecordTable’s record prop to the first
RecordTable’s record prop that owns the OAuth apps selection. Keep the
connections table record handling unchanged so OAuth app details render beneath
the OAuth apps table.

In `@packages/worker/client/routes/account-mcp-servers.tsx`:
- Around line 44-55: Move the shared clamped-cell rule next to the cell
primitives in record-table.tsx and export it as a width-accepting factory.
Replace each local clampedCellCss definition with the shared helper:
packages/worker/client/routes/account-mcp-servers.tsx#L44-L55 uses 26ch;
packages/worker/client/routes/account-memories.tsx#L46-L57 uses 30ch;
packages/worker/client/routes/account-package-invocation-tokens.tsx#L98-L110
uses 26ch; packages/worker/client/routes/account-secrets.tsx#L475-L486 uses
26ch; packages/worker/client/routes/account-values.tsx#L44-L56 uses 28ch. Remove
the duplicated comments and declarations while preserving each route’s width.

In `@packages/worker/client/routes/account-remote-connectors.tsx`:
- Around line 51-63: The clampedCellCss declaration currently splits the import
blocks; move it below the remote-connectors.ts import in
packages/worker/client/routes/account-remote-connectors.tsx (lines 51-63) and
below the `#app/loader-data.ts` import in
packages/worker/client/routes/account-values.tsx (lines 44-56), leaving all
imports grouped together before the declaration.

In `@packages/worker/client/routes/admin-codemods.tsx`:
- Around line 1045-1071: Extract the duplicated cells object into a shared
builder for codemod items, preserving the existing formatting for userId,
changedPaths, and error plus formatFindings for findings. Replace the inline
cells in both tables with the builder, while keeping each table’s current id
source unchanged.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 70a08d50-a0c2-482c-8fcc-050cf7fca2cf

📥 Commits

Reviewing files that changed from the base of the PR and between a61205e and fb84061.

📒 Files selected for processing (36)
  • docs/contributing/decisions/0010-account-record-table.md
  • docs/contributing/decisions/0010-account-record-table/index.md
  • docs/contributing/decisions/0010-account-record-table/page-inventory.md
  • docs/contributing/decisions/0010-account-record-table/prototypes/01-width-diagnosis.html
  • docs/contributing/decisions/0010-account-record-table/prototypes/02-rejected-shell-rewrites.html
  • docs/contributing/decisions/0010-account-record-table/prototypes/03-content-column-options.html
  • docs/contributing/decisions/0010-account-record-table/prototypes/04-record-table-proof.html
  • docs/contributing/decisions/0010-account-record-table/prototypes/README.md
  • docs/contributing/decisions/index.md
  • e2e/smoke.spec.ts
  • package.json
  • packages/worker/client/copy-text-button.tsx
  • packages/worker/client/routes/account-activity.tsx
  • packages/worker/client/routes/account-email.tsx
  • packages/worker/client/routes/account-integrations.tsx
  • packages/worker/client/routes/account-jobs.tsx
  • packages/worker/client/routes/account-management-components.tsx
  • packages/worker/client/routes/account-management-metadata.node.test.ts
  • packages/worker/client/routes/account-mcp-servers.tsx
  • packages/worker/client/routes/account-memories.tsx
  • packages/worker/client/routes/account-package-invocation-tokens.tsx
  • packages/worker/client/routes/account-packages.tsx
  • packages/worker/client/routes/account-remote-connectors.tsx
  • packages/worker/client/routes/account-secrets.tsx
  • packages/worker/client/routes/account-usage.tsx
  • packages/worker/client/routes/account-values.tsx
  • packages/worker/client/routes/admin-codemods.tsx
  • packages/worker/client/routes/admin-community-reports.tsx
  • packages/worker/client/routes/admin-feature-flags.tsx
  • packages/worker/client/routes/admin-invites.tsx
  • packages/worker/client/routes/admin-platform-feedback.tsx
  • packages/worker/client/routes/admin-system-email.tsx
  • packages/worker/client/routes/admin-users.tsx
  • packages/worker/client/routes/record-table.node.test.ts
  • packages/worker/client/routes/record-table.tsx
  • packages/worker/client/styles/style-primitives.ts

Comment thread docs/contributing/decisions/0010-account-record-table/index.md
Comment on lines +1040 to +1044
<h1>The detail pane gets 424&nbsp;px of a 1152&nbsp;px page.</h1>
<p class="lede">
Widening your browser does not help. The account shell is capped at
<code>72rem</code>, so past 1152&nbsp;px every extra pixel becomes margin.
That cap is the whole bug — the wrapped UUIDs are just what it looks like.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Fix the headline width: 424 px should be 434 px.

The heading states 424 px. The ledger below (line 1062), the trade-off table, and every other document in this PR state 434 px. The heading is the first number a reader sees.

📝 Proposed fix
-		<h1>The detail pane gets 424&nbsp;px of a 1152&nbsp;px page.</h1>
+		<h1>The detail pane gets 434&nbsp;px of a 1152&nbsp;px page.</h1>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<h1>The detail pane gets 424&nbsp;px of a 1152&nbsp;px page.</h1>
<p class="lede">
Widening your browser does not help. The account shell is capped at
<code>72rem</code>, so past 1152&nbsp;px every extra pixel becomes margin.
That cap is the whole bug — the wrapped UUIDs are just what it looks like.
<h1>The detail pane gets 434&nbsp;px of a 1152&nbsp;px page.</h1>
<p class="lede">
Widening your browser does not help. The account shell is capped at
<code>72rem</code>, so past 1152&nbsp;px every extra pixel becomes margin.
That cap is the whole bug — the wrapped UUIDs are just what it looks like.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@docs/contributing/decisions/0010-account-record-table/prototypes/01-width-diagnosis.html`
around lines 1040 - 1044, Update the detail-pane width in the h1 headline within
the prototype document from 424 px to 434 px, matching the ledger, trade-off
table, and other document references.

Comment on lines +1570 to +1594
const USERS = [
{
username: 'vojta',
email: 'holik.vojta@gmail.com',
plan: 'pro',
roles: ['admin', 'user'],
verified: true,
suspended: null,
paused: null,
created: '3/2/2026',
updated: '8/7/2026',
stableUserId: 'usr_9f2c41ab7d0e4c18',
},
{
username: 'kentcdodds',
email: 'kent@kentcdodds.com',
plan: 'pro',
roles: ['admin', 'user'],
verified: true,
suspended: null,
paused: null,
created: '1/14/2026',
updated: '8/6/2026',
stableUserId: 'usr_1a4e77bc93f2d055',
},

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Replace the real personal email addresses in the sample data.

USERS hard-codes holik.vojta@gmail.com and kent@kentcdodds.com. These are real personal addresses of identifiable people, and the file renders them in the table Email column and in the expanded record band. The remaining four rows already use example.com, and the prototypes README describes all rows as plausible padding. Use example.com addresses for every row so no personal identifier is committed to the docs tree.

🛡️ Proposed fix
 	const USERS = [
 		{
 			username: 'vojta',
-			email: 'holik.vojta@gmail.com',
+			email: 'vojta@example.com',
 			plan: 'pro',
 			roles: ['admin', 'user'],
 			verified: true,
 			suspended: null,
 			paused: null,
 			created: '3/2/2026',
 			updated: '8/7/2026',
 			stableUserId: 'usr_9f2c41ab7d0e4c18',
 		},
 		{
-			username: 'kentcdodds',
-			email: 'kent@kentcdodds.com',
+			username: 'kent',
+			email: 'kent@example.com',
 			plan: 'pro',
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const USERS = [
{
username: 'vojta',
email: 'holik.vojta@gmail.com',
plan: 'pro',
roles: ['admin', 'user'],
verified: true,
suspended: null,
paused: null,
created: '3/2/2026',
updated: '8/7/2026',
stableUserId: 'usr_9f2c41ab7d0e4c18',
},
{
username: 'kentcdodds',
email: 'kent@kentcdodds.com',
plan: 'pro',
roles: ['admin', 'user'],
verified: true,
suspended: null,
paused: null,
created: '1/14/2026',
updated: '8/6/2026',
stableUserId: 'usr_1a4e77bc93f2d055',
},
const USERS = [
{
username: 'vojta',
email: 'vojta@example.com',
plan: 'pro',
roles: ['admin', 'user'],
verified: true,
suspended: null,
paused: null,
created: '3/2/2026',
updated: '8/7/2026',
stableUserId: 'usr_9f2c41ab7d0e4c18',
},
{
username: 'kent',
email: 'kent@example.com',
plan: 'pro',
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@docs/contributing/decisions/0010-account-record-table/prototypes/04-record-table-proof.html`
around lines 1570 - 1594, Update the USERS sample data to replace the real
addresses for the vojta and kentcdodds records with plausible example.com
addresses, keeping the existing data structure and display behavior unchanged.
Ensure every USERS row uses an example.com email so no personal email remains in
the prototype data.

Comment thread packages/worker/client/copy-text-button.tsx
Comment thread packages/worker/client/routes/account-packages.tsx
Comment thread packages/worker/client/routes/account-secrets.tsx
Comment thread packages/worker/client/routes/admin-platform-feedback.tsx Outdated
Comment on lines +756 to +765
<RecordTableSearch
label="Search users"
placeholder="Search by username or email"
value={filters.search}
onInput={(value) => {
replaceLocation(
buildHrefWithUpdatedFilters({ search: value }),
)
}}
/>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🚀 Performance & Scalability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Check whether the admin users search previously debounced input, and whether a debounce helper exists.
set -euo pipefail

git log --oneline -3 -- packages/worker/client/routes/admin-users.tsx
git show HEAD~1:packages/worker/client/routes/admin-users.tsx 2>/dev/null \
  | rg -n -C 6 "onInput|'input'|debounce|replaceLocation" || echo "previous revision not available"

# Look for an existing debounce utility in the client.
rg -n -C 3 --type=ts --type=tsx 'debounce' packages/worker/client || true

Repository: kentcdodds/kody

Length of output: 1896


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Repo files matching admin-users / client-router / replace-location:" >&2
git ls-files | rg '(^|/)admin-users\.tsx$|(^|/)client-router\.tsx$|(^|/)replace-location\.ts$|(^|/)record-table|search' | sed -n '1,120p'

echo
echo "admin-users outline:" >&2
ast-grep outline packages/worker/client/routes/admin-users.tsx 2>/dev/null | sed -n '1,220p' || true

echo
echo "Relevant admin-users search/data-loading sections:" >&2
sed -n '680,785p' packages/worker/client/routes/admin-users.tsx

echo
echo "Search for admin/users.json and data loading:" >&2
rg -n "admin/users|currentDataKey|needsLoad|loadAdminUsers|decodeData|q=" packages/worker -g '*.ts' -g '*.tsx' | sed -n '1,160p'

echo
echo "Debounced searches in packages/worker/client without file-type flag:" >&2
rg -n -C 3 'debounce' packages/worker/client || true

Repository: kentcdodds/kody

Length of output: 25588


Keep search data reloading out of every keystroke.

onInput calls replaceLocation for each character, which changes the currentDataKey by query and makes needsLoad true. That can issue /admin/users.json on every keystroke. Debounce the value update before calling replaceLocation, or preserve the previous search debounce if one should be kept.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/worker/client/routes/admin-users.tsx` around lines 756 - 765, Update
the search handling in the RecordTableSearch onInput flow so replaceLocation is
debounced rather than invoked for every keystroke, preserving the existing
filter update behavior while preventing an /admin/users.json reload per
character.

Comment thread packages/worker/client/routes/record-table.tsx Outdated
vojtaholik and others added 4 commits August 7, 2026 12:48
Two things the empty `/account/packages` view exposed.

The shell reserves `min-height: 40rem` because the nav rail is absolutely
positioned and contributes no height, so a short page would let the rail
spill over the footer. But `align-content` defaults to `stretch`, which hands
that reserved height to the auto-sized rows: with `align-items: start` pinning
each section to the top of its now-tall track, a page shorter than the floor
grew a gap between every section. On an empty packages list that was 146px of
nothing between the description and the table; it is 44px — one section gap —
now, and the reserved height sits below the last section where it belongs.

This was always true of the shell; the split layout was simply never short
enough to have slack to distribute.

The search field also sliced its placeholder through a letter when it shrank
to share a toolbar row. `text-overflow: ellipsis` fixes it, but has to sit on
the input rather than on `::placeholder`, which does not accept that property.
It covers an overlong value too.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Typing in a search field that refetches flashed a bare, unpadded
"Loading packages…" line between the page description and the table, shoving
everything below it down and back on every keystroke — and the count slot
vanished at the same time, which resized the search field the reader was
typing into.

`RecordTable` takes a `busy` prop now. It sets `aria-busy` on the region and
dims the count in place; the announcement rides in a visually hidden polite
live region, so the whole thing costs no layout. Measured across a held-open
refetch, the search field and the table both stay at exactly the same size and
position.

The page-level line is kept for the first load, where nothing else is on
screen yet and it reflows nothing. The guard is "has a load ever completed",
not "is there data" — on an empty account those are not the same thing, and
the second one treats every refetch as a first load.

Only the five screens whose status actually flips to `loading` on a refetch
are wired up. The other nine hold their state through a load latch and never
flashed; their line is the only thing marking a first load, so it stays.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Four self-contained HTML pages, ~7.9k lines, that existed to explore the shape
before it was built. The shape is built; the record keeps the measurements, the
rejected alternatives, and the reasoning. Leaving the pages in the repo commits
us to maintaining a second, diverging copy of the account UI.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… lies

`aria-expanded` was derived from selection alone, but the record row only
renders when a record exists. A selected row whose detail was still loading (or
404'd) reported an expanded region and an `aria-controls` id that was not in
the DOM. Both now follow the row that actually renders.

Worse: `expand` mode dropped the record entirely when the selected row was not
in the current window. A deep link, a filter change, or paging past the
selection left a loaded detail with nothing to unfold under, and it silently
vanished. It falls back to a pane below the table now — one change in the
primitive, so all five expand screens get it.

Two `updated` cells formatted a payload timestamp with
`new Date(value).toLocaleDateString()`. `formatTimestamp` exists because these
arrive as `YYYY-MM-DD HH:MM:SS`, which Safari rejects outright and which every
engine reads as local time rather than UTC. Chrome tolerated it, which is why
the screenshots looked right. Both go through a new `formatTimestampDate` that
shares the normalization.

Also from review:

- The per-cell `Nch` clamps could overflow a card narrower than the clamp.
  They are one exported `recordCellClamp(ch)` now, using `min(Nch, 100%)` —
  the same guard `MetadataGrid` already uses — which also removes fourteen
  near-identical copies.
- `CopyTextButton`'s `ariaLabel` overrode the label text, so a named button
  kept reading "Copy package id" after it had copied. The result rides in a
  `role="status"` region instead.
- `Created` and `Updated` on a package token fell back to "Never", which
  cannot be true of a stored token. They take the default em dash.
- `/account/secrets` showed "No secrets yet" during its first load.
- The ADR quoted the `MetadataGrid` template without the `min()` guard.

Not taken: debouncing the search inputs. Real, but `AccountManagementSearchField`
did not debounce either, so it is pre-existing rather than introduced here, and
changing when the URL updates deserves its own change.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@vojtaholik
vojtaholik requested a review from kentcdodds August 7, 2026 11:52
@kentcdodds
kentcdodds merged commit 2e62de1 into main Aug 7, 2026
9 checks passed
@kentcdodds
kentcdodds deleted the account-detail-density branch August 7, 2026 13:34
cursor Bot pushed a commit that referenced this pull request Aug 7, 2026
Profiled the pre-push timeouts from #1273: the first Durable Object RPC in a
workers-unit file costs ~10s under @cloudflare/vitest-pool-workers (warm RPCs
~1ms), so a 5s local default could not pass. Keep the 20s shared timeout,
document the pool tax, and move embedding/http suites that never needed
bindings into node-unit.

Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>
kody-bot pushed a commit that referenced this pull request Aug 7, 2026
)

* Cursor: Apply local changes for cloud agent

* test: explain workers-pool slowness; move misclassified suites to node

Profiled the pre-push timeouts from #1273: the first Durable Object RPC in a
workers-unit file costs ~10s under @cloudflare/vitest-pool-workers (warm RPCs
~1ms), so a 5s local default could not pass. Keep the 20s shared timeout,
document the pool tax, and move embedding/http suites that never needed
bindings into node-unit.

Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>

* test: warm Durable Object classes in workers-unit setupFiles

globalSetup cannot touch workerd bindings. A workers-unit setupFiles module
loads Mailbox, UserMeter, and RunLog once per Worker module cache via
runInDurableObject so test bodies see ~1–10ms first RPCs instead of ~10s.
Suite wall clock is similar; hookTimeout is 60s to cover the first setup.

Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>

* docs: record workers-unit pool harness rules for agents

Add decision 0011 and a scannable Do/Don't in testing principles so future
agents prefer node tests, keep setupFiles DO warmup, and do not reach for
globalSetup warmups, --no-isolate, or --no-verify when the pool feels slow.

Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>

* style: format workers harness docs and vitest config

Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>

* fix(test): drop workers-unit DO setupFiles warmup

Warming Mailbox/UserMeter/RunLog from setupFiles (even without cloudflare:test)
broke webhook routing, scheduled-lane, subscription-dispatch, and package_save
workers suites. Keep the 20s shared timeout and document the rejected warmup in
decision 0011.

Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>

* docs: align decision 0011 node-test classification with testing principles

Prefer *.node.test.ts unless bindings or Workers-only APIs are required.

Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
@kody-bot

Copy link
Copy Markdown
Collaborator

Hi @vojtaholik — this repository now requires a signed inbound Contributor License Agreement for outside contributions (you keep copyright; it is a license grant so Kody can stay a single-licensor Fair Source tree).

Please read https://github.com/kentcdodds/kody/blob/main/docs/legal/individual-cla.md and reply on this thread with exactly:

I have read the CLA and I hereby sign the CLA

That covers your past and future contributions from this GitHub account. Details: https://github.com/kentcdodds/kody/blob/main/docs/contributing/inbound-contributions.md

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.

3 participants