Skip to content

fix(web): build a valid lquery for an exact ~ tree path - #181

Merged
murrayju merged 1 commit into
mainfrom
murrayju/tnt-248-internal-error-on-1-unnamed-memory-in
Jul 31, 2026
Merged

fix(web): build a valid lquery for an exact ~ tree path#181
murrayju merged 1 commit into
mainfrom
murrayju/tnt-248-internal-error-on-1-unnamed-memory-in

Conversation

@murrayju

Copy link
Copy Markdown
Member

Fixes TNT-248.

Symptom

After putting a memory at the root of the home folder:

me memory create --tree '~' foo

the UI's tree pane shows "Failed to load: Internal error". The failing request is the lazy exact-path fetch for the expanded ~ node:

// request
{"jsonrpc":"2.0","method":"memory.search","params":{"tree":"~|~","limit":1000},"id":8}
// response
{"jsonrpc":"2.0","error":{"code":-32603,"message":"Internal error"},"id":8}

Root cause

Two independent bugs.

1. The web UI built an invalid lquery for ~

exactTreeLquery() (packages/web/src/api/queries.ts) forced the server's tree-filter classifier down the lquery branch by duplicating the last label as an alternation:

labels[i] = `${labels[i]}|${labels[i]}`;   // "work" -> "work|work"

For a top-level home node the path is ~, so it produced ~|~ — the exact filter in the failing request. That isn't parseable lquery: ~ is home sugar, expanded server-side only as a leading segment, and it isn't a legal ltree label:

ERROR:  42601: lquery syntax error at character 1

The same trick also broke the synthetic root bucket (. -> .|.), a second latent case.

Fix: append the zero-label quantifier instead of an alternation. It forces lquery detection and pins the match to exactly that path, and stays valid with ~ leading:

if (path === "" || path === ROOT_PATH) return "*{0,0}";
return `${path}.*{0,0}`;   // "~" -> "~.*{0,0}", "share.auth" -> "share.auth.*{0,0}"

Verified against real PG: home.x ~ home.x.*{0,0} is true, home.x.a is false — same exact-match semantics as before.

2. The server reported a caller error as "Internal error"

mapSpaceError (packages/server/rpc/memory/memory.ts) mapped only 22023 / 22P02 to VALIDATION_ERROR. ltree's lquery/ltxtquery parsers report a malformed pattern as 42601 (syntax_error), which fell through to a generic -32603 Internal error, hiding the cause. A tree filter is caller-supplied, so 42601 now maps to VALIDATION_ERROR.

Why it surfaced only now

It requires a memory at the root of ~. That's the only way the UI's tree node is the bare ~ with directCount > 0, which is what triggers the lazy exact-path fetch on auto-expansion.

Tests

  • packages/web/src/api/queries.test.ts (new) — exactTreeLquery unit tests: the ~ regression, the root sentinel, and an assertion that no alternation is ever emitted.
  • packages/server/rpc/memory/memory.integration.test.ts — end-to-end against a real space schema: a memory at ~ root resolves via ~.*{0,0} (and ~.notes.*{0,0} one level down), and a malformed filter (~|~, share.a&&b) is a VALIDATION_ERROR rather than an internal error.

Both new tests fail with the two source fixes reverted (confirmed by stashing only the source files), so they genuinely pin the bug.

./bun run check:full is green: 1208 unit / 1588 db / 45 e2e, 0 fail.

A memory at the ROOT of `~` (`me memory create --tree '~' foo`) made the UI
show "Failed to load: Internal error". Two bugs:

1. `exactTreeLquery` forced the server's tree-filter classifier down the
   lquery branch by duplicating the last label as an alternation
   (`work` -> `work|work`). For a top-level home node the path IS `~`, so it
   emitted `~|~` — not parseable lquery, since `~` is home sugar the server
   expands only as a LEADING segment and is not a legal ltree label. PG
   rejected it with 42601. The synthetic root bucket (`.` -> `.|.`) was a
   second latent case. Use the zero-label quantifier instead
   (`~.*{0,0}`): it forces lquery detection AND pins the match to exactly
   that path, while staying valid with `~` leading.

2. `mapSpaceError` mapped only 22023/22P02 to VALIDATION_ERROR. ltree's
   lquery/ltxtquery parsers report a malformed pattern as 42601
   (syntax_error), which fell through to a generic -32603 Internal error,
   hiding the cause. A tree filter is caller-supplied, so 42601 now maps to
   VALIDATION_ERROR.

Fixes TNT-248
Copilot AI review requested due to automatic review settings July 31, 2026 20:21
@murrayju
murrayju requested review from cevian and jgpruitt as code owners July 31, 2026 20:21

Copilot AI left a comment

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.

Pull request overview

Fixes a web UI regression where expanding the ~ (home) tree node could send an invalid lquery (~|~), causing memory.search to fail and the UI to show “Internal error”. The PR updates the UI to generate a valid exact-match lquery for ~ and improves server-side error mapping so malformed ltree query patterns surface as validation errors.

Changes:

  • Web: exactTreeLquery() now pins exact matches by appending .*{0,0} (or *{0,0} for root), avoiding invalid alternation patterns for ~ and the synthetic root bucket.
  • Server: maps Postgres 42601 (syntax_error) from malformed lquery/ltxtquery patterns to VALIDATION_ERROR instead of returning a generic internal error.
  • Tests: adds web unit tests for exactTreeLquery() and new server integration tests covering exact ~ queries and malformed filters.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

File Description
packages/web/src/api/queries.ts Reworks exact-tree lquery generation to remain valid for ~ and root sentinel while preserving exact-match semantics.
packages/web/src/api/queries.test.ts Adds unit coverage for exactTreeLquery() including ~ and root-sentinel regressions.
packages/server/rpc/memory/memory.ts Maps 42601 SQLSTATE to VALIDATION_ERROR for caller-supplied malformed tree query filters.
packages/server/rpc/memory/memory.integration.test.ts Adds integration coverage for exact ~-root filtering and validation errors on malformed tree filters.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +1 to +3
/**
* Unit tests for the pure query-param helpers (no network, no React).
*/
@murrayju
murrayju merged commit 6a5da27 into main Jul 31, 2026
7 checks passed
@murrayju
murrayju deleted the murrayju/tnt-248-internal-error-on-1-unnamed-memory-in branch July 31, 2026 20:26
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