Add totalItemsLoading to EntityListContextProps so consumers can
distinguish a stale count (being refreshed) from a fresh one. Rename
the internal asyncTotalItems to totalItems.
Update CatalogTable to show a dimmed count while the count is loading,
and only show the full-table spinner on the very first load (when no
entities exist yet). Subsequent fetches keep stale rows visible with
a small spinner next to the title.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Fredrik Adelöw <freben@gmail.com>
Restore the 'does not re-fetch when backend filter params are
unchanged' test from #34324 that was accidentally dropped during
the rebase. Add a new test verifying that cursor navigation does
not re-run the count query (the core split-count behavior).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Fredrik Adelöw <freben@gmail.com>
Reapply the split-count concept on top of the refactored
useEntityListProvider (from #34324). The list fetch now uses
totalItems: 'exclude' to skip the expensive count, and a separate
useAsyncFn fetches the count only when filters change — page
navigation no longer re-runs the count query.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Fredrik Adelöw <freben@gmail.com>
Cursor requests already carry the computed totalItems from page 1,
so the count query is skipped regardless of this value. Setting it
to 'exclude' reflects what actually happens, and the comment
explains why the value doesn't matter.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Fredrik Adelöw <freben@gmail.com>
Layer the remaining concepts from the original PR on top of the
split-query foundation:
- totalItems parameter on REST API (GET and POST) and CatalogApi
- TotalItemsMode type replacing internal skipTotalItems boolean
- Sort field key lowercasing for search table comparison
- whereNotNull(search.value) on the list CTE to exclude truncated values
- Simplified ORDER BY (NULLS LAST removed since NULL values are
already excluded by whereNotNull)
- New tests for NULL sort field values and multi-valued sort field
totalItems accuracy
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Fredrik Adelöw <freben@gmail.com>
Move the adjustedFilters useMemo above refresh and reuse it in both
the fetch callback and the frontend filtering memo.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Fredrik Adelöw <freben@gmail.com>
The lastFetchParamsRef starts as undefined, so the initial debounce
would have fetched with an empty filter (returning every entity in
the catalog). Guard against this by skipping the fetch when no filter
components have registered yet.
Also skip the URL sync effect until at least one filter has been set,
to avoid briefly clearing filter query params from the URL bar before
filter components initialize.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Fredrik Adelöw <freben@gmail.com>
The label is read by CatalogTable for the table title (e.g. "All
Components (42)"), so suppressing the label update would cause a
visible regression. The provider's ref-based fetch dedup already
prevents the redundant API call when only the label changes, so
the second updateFilters call is harmless.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Fredrik Adelöw <freben@gmail.com>
Replace useAsyncFn + outputState comparison with ref-based fetch dedup
and generation counter. Split entity filtering into a synchronous
useMemo so frontend-only filter changes are instant.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Fredrik Adelöw <freben@gmail.com>
The list CTE's INNER JOIN on search already excludes entities whose
sort-field value is NULL (truncated long values stored as NULL by
buildEntitySearch). Add the same whereNotNull to the count query's
EXISTS so totalItems matches the reachable set. Matters for fields
like metadata.description where ~128K entities have NULL values.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Fredrik Adelöw <freben@gmail.com>
The list CTE and count query both need the same filter, query, and
full-text-search predicates. Extract them into a shared
applyPredicates closure so they can't drift apart. Move the count
query construction next to the CTE for readability.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Fredrik Adelöw <freben@gmail.com>
Remove pr-33721, pr-34001, pr-34004 — all shipped in v1.51.0.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Fredrik Adelöw <freben@gmail.com>
The filtered CTE was referenced twice (count + data), preventing
Postgres 12+ from inlining it. This forced full materialization of
the filtered set before LIMIT could short-circuit. Split into two
separate queries run via Promise.all so the list CTE is only
referenced once and the planner can short-circuit on LIMIT.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Fredrik Adelöw <freben@gmail.com>
Only wrap the SELECT+UPDATE in a transaction for MySQL/PostgreSQL
where FOR UPDATE SKIP LOCKED is actually used. For sqlite3 the
transaction adds unnecessary BEGIN/COMMIT overhead.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Fredrik Adelöw <freben@gmail.com>
Same FOR UPDATE SKIP LOCKED race: the processing loop's claim query
had the SELECT and UPDATE as separate auto-committed statements.
The existing caller already wraps in a transaction, but this makes
the function self-protecting if called without one.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Fredrik Adelöw <freben@gmail.com>
The env vars used lowercase postgres18/postgres14 instead of uppercase
POSTGRES18/POSTGRES14, which didn't match the names expected by
TestDatabases. This caused all postgres-only test suites to see no
available databases and crash.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Fredrik Adelöw <freben@gmail.com>
When eachSupportedId() returned an empty array, describe.each([]) would
throw and crash the entire test suite. Return a placeholder ID instead,
so individual tests fail with a clear error rather than preventing the
entire suite from running.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Fredrik Adelöw <freben@gmail.com>
The SELECT FOR UPDATE SKIP LOCKED and the subsequent UPDATE of
next_stitch_at now run inside a single transaction. Previously the
row locks were released after the SELECT auto-committed, allowing
another worker to claim the same rows before the UPDATE ran.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Fredrik Adelöw <freben@gmail.com>