Skip to content

Add account packages route for browsing saved package metadata - #745

Merged
kody-bot merged 1 commit into
mainfrom
cursor/account-packages-route-db5c
Jul 13, 2026
Merged

kody-bot merged 1 commit into
mainfrom
cursor/account-packages-route-db5c

Conversation

@kentcdodds

@kentcdodds kentcdodds commented Jul 13, 2026 •

Copy link
Copy Markdown
Owner

What

Adds /account/packages — a read-only account section for browsing your saved packages and their metadata, following the same list/detail layout as /account/secrets, with server-backed search, filters, sort, and infinite scroll (same pattern as /admin/users).

account_packages_list_detail_search_filter_infinite_scroll_demo.mp4

  • List: sidebar list with text search (q matches name, kody id, description, search text, and tags), an app filter (all / with app / without app), and a sort select (recently updated — default, recently created, name). Server-paginated with an infinite-scroll sentinel plus a "Load more" fallback button and a "Showing X of Y" footer.
  • Detail: path-based selection at /account/packages/:packageId showing name, description, tag chips, kody id, package id, app declaration, source id, created/updated timestamps, and search text.
  • Selection-only navigations keep the loaded scroll window; filter changes re-anchor at page one.
  • New searchSavedPackagesByUserId repo query (user-scoped, shared WHERE clause for page + total, LIKE-escaped search).
  • New Packages entry in the account section nav.

Package detail after infinite scroll
Search filtering to discord packages

How it works

  • routes.ts: accountPackages, accountPackageDetail, accountPackagesApi (/account/packages.json, GET-only — this surface is read-only; packages are created/edited through MCP tools).
  • account-packages-data.ts + handlers/account-packages.ts mirror the secrets/invocation-tokens handler pattern (session auth → load data → renderAppPage / JSON).
  • client/routes/account-packages.tsx blends the secrets layout (path detail, URL-backed filters) with the admin-users infinite list (createInfiniteList + infiniteScrollSentinel).

Testing

  • npm run validate passes (format, lint, typecheck, unit tests, Playwright E2E, MCP E2E).
  • account-packages.node.test.ts: API handler pagination/filter/sort/selection parsing, auth, and method guards.
  • repo-search.workers.test.ts: real-D1 coverage of the search SQL — user scoping, sort orders, case-insensitive multi-column matching, LIKE wildcard escaping, app filter, and paging totals.
  • Manual browser testing against the local dev server with 45 seeded packages: infinite scroll to 45/45, deep-scroll selection preserving the list window, search (discord → 3), app filter (→ 12), name sort, direct full-page detail loads, and the "Package not found." state (see video and screenshots above).
System recap — extends existing primitives (medium risk)

Mode: recap · Base: main @ 18f67ce3 · Head: f5f05073

Classification: extends — adds a new account route to the browser app and a new user-scoped query to the saved-packages data layer. No new primitives, no schema changes.

Primitives touched

Primitive Group Impact
app-ui surfaces extends — new /account/packages(.json) routes, handler, loader data, client route, nav
saved-packages assistant extends — new searchSavedPackagesByUserId filtered/paged query in the registry repo
d1-app-db storage composes — read-only queries against the existing saved_packages table

System map

The packages account page flows from the browser app through the saved-packages registry repo into D1.

Legend: green = composes (wiring only) · amber = extended by this PR · red = new primitive · gray = context (unchanged, included only when an edge crosses it).

flowchart LR
	appUi["app-ui<br/>Browser app (Remix 3)"]:::extended
	savedPackages["saved-packages<br/>Saved packages"]:::extended
	d1AppDb["d1-app-db<br/>D1 app database"]:::touched
	appUi -->|"GET /account/packages.json (q, app, sort, page, selected)"| savedPackages
	savedPackages -->|"searchSavedPackagesByUserId + getSavedPackageById"| d1AppDb
	classDef touched fill:#1a7f37,color:#fff
	classDef extended fill:#9a6700,color:#fff
	classDef added fill:#cf222e,color:#fff
	classDef untouched fill:#57606a,color:#fff
Loading

Invariants

Per-user isolation: every query in searchSavedPackagesByUserId and the detail lookup is scoped by user_id = ? from the authenticated session; the count and page share one WHERE clause.

Open in Web Open in Cursor 

Summary by CodeRabbit

  • New Features

    • Added a Packages section to account navigation.
    • Added an account Packages page with search, app filters, sorting, pagination, and infinite scrolling.
    • Added package detail views showing metadata, tags, timestamps, and search information.
    • Added authenticated package listing and detail routes.
  • Bug Fixes

    • Added handling for unauthorized access, invalid requests, loading states, and data-fetching errors.
  • Tests

    • Added coverage for package searching, filtering, sorting, pagination, authentication, and request validation.

…inite scroll

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

coderabbitai Bot commented Jul 13, 2026 •

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds an authenticated /account/packages page with searchable, sortable, paginated package data, selected-package details, JSON and SSR handlers, route registration, account navigation, and repository tests.

Changes

Account Packages

Layer / File(s) Summary
Package search contracts and repository
packages/worker/src/app/loader-data.ts, packages/worker/src/package-registry/repo.ts, packages/worker/src/package-registry/repo-search.workers.test.ts
Defines package loader types and adds user-scoped package search with query, app filtering, sorting, pagination, wildcard escaping, and repository tests.
Server data loading and routes
packages/worker/src/app/account-packages-data.ts, packages/worker/src/app/handlers/account-packages.ts, packages/worker/src/app/handlers/account-packages.node.test.ts, packages/worker/src/app/routes.ts, packages/worker/src/app/router.ts
Parses request state, loads package lists and selected details, exposes authenticated SSR and JSON handlers, and registers the new routes with handler tests.
Client navigation and package page
packages/worker/client/routes/account-management-components.tsx, packages/worker/client/routes/account-packages.tsx, packages/worker/client/routes/index.tsx
Adds account navigation and client routes, then renders filters, infinite scrolling, selection state, loading/error states, and package details.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant AccountPackagesRoute
  participant accountPackagesRouteLoader
  participant createAccountPackagesApiHandler
  participant loadAccountPackagesData
  Client->>AccountPackagesRoute: navigate or change filters
  AccountPackagesRoute->>accountPackagesRouteLoader: load URL state
  accountPackagesRouteLoader->>createAccountPackagesApiHandler: GET account packages JSON
  createAccountPackagesApiHandler->>loadAccountPackagesData: load authenticated package data
  loadAccountPackagesData-->>createAccountPackagesApiHandler: list and selected package
  createAccountPackagesApiHandler-->>accountPackagesRouteLoader: JSON payload
  accountPackagesRouteLoader-->>AccountPackagesRoute: route loader data
  AccountPackagesRoute-->>Client: render list and package details
Loading

Possibly related PRs

  • kentcdodds/kody#608: Introduced the client route-loader and prefetch plumbing extended by this route.
  • kentcdodds/kody#672: Added saved-package search, sorting, and pagination used by this feature.
  • kentcdodds/kody#705: Introduced the account sub-navigation extended with the Packages entry.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 22.22% 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding an account packages route for browsing saved package metadata.
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
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch cursor/account-packages-route-db5c

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.

@kody-bot
kody-bot marked this pull request as ready for review July 13, 2026 22:24
@github-actions

Copy link
Copy Markdown
Contributor

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

Worker: kody-pr-745
D1: kody-pr-745-db
KV: kody-pr-745-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: 1

🧹 Nitpick comments (5)
packages/worker/src/package-registry/repo.ts (1)

245-255: 🚀 Performance & Scalability | 🔵 Trivial

Full-table scan on every search due to LOWER(...) on unindexed columns.

Every text column (and tags_json) is wrapped in LOWER(...) for case-insensitive matching, which prevents SQLite from using any index and forces a full scan of saved_packages per query. At current expected per-user row counts this is likely fine, but worth keeping in mind if package counts grow substantially.

🤖 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/src/package-registry/repo.ts` around lines 245 - 255, Update
the search condition construction in the query-handling block to avoid wrapping
indexed text columns in LOWER(...) for case-insensitive matching; use the
schema’s supported case-insensitive comparison or indexed representation while
preserving matching across name, kody_id, description, search_text, and
tags_json and the existing escaped LIKE parameters.
packages/worker/client/routes/account-packages.tsx (1)

319-346: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoff

Load-latch logic duplicates the shared route-load-latch.ts pattern.

This render-time block (last-seen/last-failed/needsLoad tracking) closely mirrors the needsLoad/latch logic already factored out in route-load-latch.ts (seen in other routes), just keyed on dataKey instead of href with an added loadingDataKey guard. Not blocking, but worth considering generalizing the shared helper to accept a configurable key so this route doesn't carry its own copy of the same failure-latch semantics.

🤖 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-packages.tsx` around lines 319 - 346,
Generalize and reuse the shared route-load-latch helper from route-load-latch.ts
for the render-time tracking around applyRouteLoaderData, replacing the local
lastSeenDataKey, lastFailedDataKey, and needsLoad logic in the returned
function. Extend the helper’s key handling to support dataKey and preserve this
route’s loadingDataKey guard and stale-navigation refresh behavior.
packages/worker/src/app/account-packages-data.ts (2)

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

Duplicated route base path constant.

accountPackagesBasePath re-hardcodes /account/packages, which already exists as routes.accountPackages in packages/worker/src/app/routes.ts. If the route path ever changes there, this constant silently drifts out of sync.

♻️ Proposed fix
+import { routes } from '`#app/routes.ts`'
...
-const accountPackagesBasePath = '/account/packages'
+const accountPackagesBasePath = routes.accountPackages
🤖 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/src/app/account-packages-data.ts` at line 20, Replace the
hard-coded value in accountPackagesBasePath with the existing
routes.accountPackages symbol imported from the routes module, keeping all
consumers of accountPackagesBasePath unchanged.

24-39: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

Prefer routes.accountPackages here (packages/worker/src/app/account-packages-data.ts:20). accountPackagesBasePath duplicates the shared route string; reuse the exported route constant instead to avoid drift.

🤖 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/src/app/account-packages-data.ts` around lines 24 - 39,
Update readAccountPackagesSelectedPackageId to derive detailPrefix from the
shared routes.accountPackages constant instead of accountPackagesBasePath,
removing the duplicated route-string dependency while preserving the existing
URL parsing and selection behavior.
packages/worker/src/app/handlers/account-packages.node.test.ts (1)

41-62: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

createAccountPackagesHandler (SSR page handler) has no test coverage here.

readAuthSessionResult, redirectToLogin, redirectToLoginWhenUnauthenticated, and renderAppPage are all mocked (Lines 41-52), but only createAccountPackagesApiHandler is imported and tested (Line 61-62). The SSR handler's session/auth redirect logic and renderAppPage invocation are never exercised, despite the mocks being purpose-built for that.

Consider adding tests for createAccountPackagesHandler covering: no session → redirect, no authenticated user → redirect, and happy path → renderAppPage called with { accountPackages } loaderData; or remove the now-unused mocks if SSR coverage is intentionally out of scope.

🤖 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/src/app/handlers/account-packages.node.test.ts` around lines
41 - 62, The SSR handler createAccountPackagesHandler lacks coverage despite its
authentication and rendering dependencies being mocked. Add tests that exercise
no session, no authenticated user, and authenticated success paths, verifying
redirects and that renderAppPage receives loaderData containing accountPackages;
alternatively remove the unused SSR mocks if this handler is intentionally out
of scope.
🤖 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 `@packages/worker/client/routes/account-packages.tsx`:
- Around line 605-637: Reorder the items array in the metadata grid so the
3-column row-major layout is: Kody id, Source id, Package id on the first row,
followed by Created, App, Updated on the second row. Keep each existing field’s
label and value unchanged.

---

Nitpick comments:
In `@packages/worker/client/routes/account-packages.tsx`:
- Around line 319-346: Generalize and reuse the shared route-load-latch helper
from route-load-latch.ts for the render-time tracking around
applyRouteLoaderData, replacing the local lastSeenDataKey, lastFailedDataKey,
and needsLoad logic in the returned function. Extend the helper’s key handling
to support dataKey and preserve this route’s loadingDataKey guard and
stale-navigation refresh behavior.

In `@packages/worker/src/app/account-packages-data.ts`:
- Line 20: Replace the hard-coded value in accountPackagesBasePath with the
existing routes.accountPackages symbol imported from the routes module, keeping
all consumers of accountPackagesBasePath unchanged.
- Around line 24-39: Update readAccountPackagesSelectedPackageId to derive
detailPrefix from the shared routes.accountPackages constant instead of
accountPackagesBasePath, removing the duplicated route-string dependency while
preserving the existing URL parsing and selection behavior.

In `@packages/worker/src/app/handlers/account-packages.node.test.ts`:
- Around line 41-62: The SSR handler createAccountPackagesHandler lacks coverage
despite its authentication and rendering dependencies being mocked. Add tests
that exercise no session, no authenticated user, and authenticated success
paths, verifying redirects and that renderAppPage receives loaderData containing
accountPackages; alternatively remove the unused SSR mocks if this handler is
intentionally out of scope.

In `@packages/worker/src/package-registry/repo.ts`:
- Around line 245-255: Update the search condition construction in the
query-handling block to avoid wrapping indexed text columns in LOWER(...) for
case-insensitive matching; use the schema’s supported case-insensitive
comparison or indexed representation while preserving matching across name,
kody_id, description, search_text, and tags_json and the existing escaped LIKE
parameters.
🪄 Autofix (Beta)

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: 398665da-a43d-490f-bbf9-7adc89d2ab08

📥 Commits

Reviewing files that changed from the base of the PR and between 18f67ce and f5f0507.

📒 Files selected for processing (11)
  • packages/worker/client/routes/account-management-components.tsx
  • packages/worker/client/routes/account-packages.tsx
  • packages/worker/client/routes/index.tsx
  • packages/worker/src/app/account-packages-data.ts
  • packages/worker/src/app/handlers/account-packages.node.test.ts
  • packages/worker/src/app/handlers/account-packages.ts
  • packages/worker/src/app/loader-data.ts
  • packages/worker/src/app/router.ts
  • packages/worker/src/app/routes.ts
  • packages/worker/src/package-registry/repo-search.workers.test.ts
  • packages/worker/src/package-registry/repo.ts

Comment on lines +605 to +637
items={[
{ label: 'Kody id', value: selectedPackage.kodyId },
{
label: 'Package id',
value: (
<code mix={css({ overflowWrap: 'anywhere' })}>
{selectedPackage.id}
</code>
),
},
{
label: 'App',
value: selectedPackage.hasApp
? 'Declares a package app'
: 'No app',
},
{
label: 'Source id',
value: (
<code mix={css({ overflowWrap: 'anywhere' })}>
{selectedPackage.sourceId}
</code>
),
},
{
label: 'Created',
value: formatTimestamp(selectedPackage.createdAt),
},
{
label: 'Updated',
value: formatTimestamp(selectedPackage.updatedAt),
},
]}

@coderabbitai coderabbitai Bot Jul 13, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Metadata grid field order doesn't match the intended 3-column layout.

With columns={3}, the grid fills row-major: row 1 = Kody id / Package id / App, row 2 = Source id / Created / Updated. The PR's own reference screenshots/description show row 1 = Kody id / Source id / Package id, row 2 = Created / App / Updated. Reorder the items array to match.

🎨 Proposed fix
 								<MetadataGrid
 									columns={3}
 									items={[
 										{ label: 'Kody id', value: selectedPackage.kodyId },
-										{
-											label: 'Package id',
-											value: (
-												<code mix={css({ overflowWrap: 'anywhere' })}>
-													{selectedPackage.id}
-												</code>
-											),
-										},
-										{
-											label: 'App',
-											value: selectedPackage.hasApp
-												? 'Declares a package app'
-												: 'No app',
-										},
 										{
 											label: 'Source id',
 											value: (
 												<code mix={css({ overflowWrap: 'anywhere' })}>
 													{selectedPackage.sourceId}
 												</code>
 											),
 										},
+										{
+											label: 'Package id',
+											value: (
+												<code mix={css({ overflowWrap: 'anywhere' })}>
+													{selectedPackage.id}
+												</code>
+											),
+										},
 										{
 											label: 'Created',
 											value: formatTimestamp(selectedPackage.createdAt),
 										},
+										{
+											label: 'App',
+											value: selectedPackage.hasApp
+												? 'Declares a package app'
+												: 'No app',
+										},
 										{
 											label: 'Updated',
 											value: formatTimestamp(selectedPackage.updatedAt),
 										},
 									]}
 								/>
📝 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
items={[
{ label: 'Kody id', value: selectedPackage.kodyId },
{
label: 'Package id',
value: (
<code mix={css({ overflowWrap: 'anywhere' })}>
{selectedPackage.id}
</code>
),
},
{
label: 'App',
value: selectedPackage.hasApp
? 'Declares a package app'
: 'No app',
},
{
label: 'Source id',
value: (
<code mix={css({ overflowWrap: 'anywhere' })}>
{selectedPackage.sourceId}
</code>
),
},
{
label: 'Created',
value: formatTimestamp(selectedPackage.createdAt),
},
{
label: 'Updated',
value: formatTimestamp(selectedPackage.updatedAt),
},
]}
items={[
{ label: 'Kody id', value: selectedPackage.kodyId },
{
label: 'Source id',
value: (
<code mix={css({ overflowWrap: 'anywhere' })}>
{selectedPackage.sourceId}
</code>
),
},
{
label: 'Package id',
value: (
<code mix={css({ overflowWrap: 'anywhere' })}>
{selectedPackage.id}
</code>
),
},
{
label: 'Created',
value: formatTimestamp(selectedPackage.createdAt),
},
{
label: 'App',
value: selectedPackage.hasApp
? 'Declares a package app'
: 'No app',
},
{
label: 'Updated',
value: formatTimestamp(selectedPackage.updatedAt),
},
]}
🤖 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-packages.tsx` around lines 605 - 637,
Reorder the items array in the metadata grid so the 3-column row-major layout
is: Kody id, Source id, Package id on the first row, followed by Created, App,
Updated on the second row. Keep each existing field’s label and value unchanged.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The current order is intentional — the screenshots in the PR description were rendered by this exact code, so there's no divergent "intended" layout to match. Row 1 groups the identifiers and app declaration (Kody id / Package id / App), row 2 groups provenance (Source id / Created / Updated). Skipping the reorder.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Skipped: comment is from another GitHub bot.

@kody-bot
kody-bot merged commit 09e1d3c into main Jul 13, 2026
7 checks passed
@kody-bot
kody-bot deleted the cursor/account-packages-route-db5c branch July 13, 2026 22:43
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