fix: repair memory-list project filter + add type-ahead project dropdown #7

Merged
jknapp merged 1 commits from fix/memory-list-project-filter into main 2026-07-01 23:22:13 +00:00
Owner

Problem

Filtering the memory list by project threw a Next.js server-side exception (Application error … Digest). Root cause: the filter built a raw Drizzle sql fragment interpolating a JS string[] into ANY(${accessibleIds}::uuid[]), which does not bind as a Postgres array literal — the same array-binding bug class fixed in #1 (commits 3019446, b3f7e60). The search path avoided it via the pg tag; the plain list path did not.

Fix

  • Resolve the typed project key to a single project id from the user's accessible set (owned ∪ shared; owned wins on key collision, matching project.identify and resolveProjectIdForKey in the search path), then filter with a parameterized eq(memories.projectId, resolvedId). Returns empty when the key matches no readable project. Removes the raw-SQL array interpolation entirely.
  • Makes list-view collision semantics consistent with the search view.

Feature

  • Replaces the plain 'Project key…' input with a type-ahead combobox (_project-combobox.tsx) populated with the user's accessible project keys; narrows as you type, selecting applies the filter immediately, free-typed keys still submit.
  • Hoists getAccessibleProjects to the page, reused for both the dropdown options and the list WHERE clause.

Verification

  • tsc typecheck clean; production next build succeeds.
  • Code review + security review run; one React-18 requestSubmit stale-value bug found and fixed (sync DOM value before submit). No access-control regression: combobox only receives keys the user can already read; filter narrows, never widens; injection surface eliminated.
## Problem Filtering the memory list by project threw a Next.js server-side exception (`Application error … Digest`). Root cause: the filter built a raw Drizzle `sql` fragment interpolating a JS `string[]` into `ANY(${accessibleIds}::uuid[])`, which does not bind as a Postgres array literal — the same array-binding bug class fixed in #1 (commits 3019446, b3f7e60). The search path avoided it via the `pg` tag; the plain list path did not. ## Fix - Resolve the typed project key to a single project id from the user's accessible set (owned ∪ shared; owned wins on key collision, matching `project.identify` and `resolveProjectIdForKey` in the search path), then filter with a parameterized `eq(memories.projectId, resolvedId)`. Returns empty when the key matches no readable project. Removes the raw-SQL array interpolation entirely. - Makes list-view collision semantics consistent with the search view. ## Feature - Replaces the plain 'Project key…' input with a type-ahead combobox (`_project-combobox.tsx`) populated with the user's accessible project keys; narrows as you type, selecting applies the filter immediately, free-typed keys still submit. - Hoists `getAccessibleProjects` to the page, reused for both the dropdown options and the list WHERE clause. ## Verification - `tsc` typecheck clean; production `next build` succeeds. - Code review + security review run; one React-18 `requestSubmit` stale-value bug found and fixed (sync DOM value before submit). No access-control regression: combobox only receives keys the user can already read; filter narrows, never widens; injection surface eliminated.
jknapp added 1 commit 2026-07-01 23:18:44 +00:00
The memory list page threw a Next.js server-side exception whenever a
project filter was applied. The filter built a raw Drizzle `sql` fragment
that interpolated a JS string[] into `ANY(${accessibleIds}::uuid[])`,
which doesn't bind as a Postgres array literal — the same array-binding
bug class already fixed in #1 (commits 3019446, b3f7e60) for
memory.list/snippet.list. The search path avoided it by using the `pg`
tag; the plain list path did not.

Fix: resolve the typed project key to a single project id from the
user's accessible set (owned ∪ shared, owned winning on key collision to
match project.identify and the search path), then filter with a plain
`eq(memories.projectId, resolvedId)` — fully parameterized, no raw array
interpolation. Returns empty when the key matches no readable project.
This also makes the list view's collision semantics consistent with the
search view.

Feature: replace the plain "Project key…" text input with a type-ahead
combobox (_project-combobox.tsx) populated with the user's accessible
project keys, narrowing as they type; selecting a suggestion applies the
filter immediately. Free-typed keys still submit. getAccessibleProjects
is hoisted to the page and reused for both the dropdown options and the
list WHERE clause (no extra query on the list path).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
jknapp merged commit cb1bd31de6 into main 2026-07-01 23:22:13 +00:00
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: cybercove-labs/shared-memory#7