Command palette search + action tool, and schedules filters - #37
Conversation
The ⌘K palette only did static page navigation. It now searches transactions (notes/payee/category) across every tracker you belong to, deep-links into a pre-filtered transactions view, and adds quick actions for new transactions/schedules and theme toggling. Also fixes a stale-state bug: navigating from the palette to /transactions a second time while already on that page didn't update the list, since the URL's q/tracker params were only read once on mount. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
📝 WalkthroughWalkthroughChangesCommand palette search and navigation
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant User
participant CommandPalette
participant ActiveTrackersAPI
participant TransactionsAPI
participant TransactionsClient
User->>CommandPalette: Enter search query
CommandPalette->>ActiveTrackersAPI: Fetch active trackers
CommandPalette->>TransactionsAPI: Search transactions per tracker
TransactionsAPI-->>CommandPalette: Return transaction results
CommandPalette-->>User: Display grouped results
User->>CommandPalette: Select a result
CommandPalette->>TransactionsClient: Navigate with q and tracker
TransactionsClient-->>User: Apply URL-filtered transaction view
Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
Schedules gets the same on-screen filter bar as transactions: search plus direction/category/payee/date-range, mirroring the pattern so switching between the two pages feels consistent. .playwright-mcp is local Playwright MCP output and doesn't belong in either the repo or Docker build context. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 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 `@components/layout/command-palette.tsx`:
- Around line 117-140: Track whether any query in resultsQueries has failed,
derive a searchFailed state from the query errors, and render the distinct
failure CommandItem inside the transaction results group before the no-results
fallback. Ensure the existing “Keine Ergebnisse gefunden” message is shown only
when searching has not failed and there are no hits.
- Around line 355-365: Update the “show-all-results” CommandItem rendering in
the command palette so it appears only when a target tracker exists, using the
available tracker result/default tracker state rather than just !isSearching.
Preserve the existing onSelect behavior while preventing rendering during
loading when hits[0]?.trackerId is unavailable.
In `@components/transactions/transactions-client.tsx`:
- Line 4: Wrap the TransactionsClient rendering path in a React Suspense
boundary so its useSearchParams dependency is supported during static
prerendering. Update the TransactionsPage or nearest server-rendered parent that
imports TransactionsClient, preserving the existing client component behavior
and providing an appropriate fallback.
- Around line 128-133: Update the re-seeding logic guarded by paramsKey !==
seenParamsKey so it also clears direction, categoryId, payeeId, from, and to
alongside q and tracker. Ensure identical palette searches re-seed local state
by either syncing local query edits back to the URL or having the
command-palette navigation append a changing nonce parameter, while preserving
the existing palette search behavior.
🪄 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: bb7e98ca-0a12-4f94-93d7-9eff1c1b624a
📒 Files selected for processing (4)
components/layout/command-palette.tsxcomponents/layout/quick-add-sheet.tsxcomponents/transactions/transactions-client.tsxcomponents/ui/command.tsx
| const resultsQueries = useQueries({ | ||
| queries: activeTrackers.map((tracker) => ({ | ||
| queryKey: ["command-search", tracker.id, debouncedSearch], | ||
| queryFn: () => | ||
| fetchJson<{ items: TransactionHit[] }>( | ||
| `/api/transactions?trackerId=${tracker.id}&q=${encodeURIComponent(debouncedSearch)}&page=1`, | ||
| ), | ||
| enabled: searchReady, | ||
| })), | ||
| }); | ||
| const isSearching = resultsQueries.some((q) => q.isFetching); | ||
| const hits: TransactionHitWithTracker[] = resultsQueries | ||
| .flatMap((result, index) => { | ||
| const tracker = activeTrackers[index]; | ||
| return (result.data?.items ?? []).map((item) => ({ | ||
| ...item, | ||
| trackerId: tracker.id, | ||
| trackerName: tracker.name, | ||
| trackerColor: tracker.color, | ||
| currency: tracker.currency, | ||
| })); | ||
| }) | ||
| .sort((a, b) => b.date.localeCompare(a.date)) | ||
| .slice(0, 6); |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
Surface search failures instead of reporting no results.
resultsQueries is only inspected through isFetching and data. fetchJson throws for any non-OK response (see lib/client-fetch.ts). If every tracker request fails, isSearching is false and hits is empty, so the palette shows "Keine Ergebnisse gefunden." The user cannot distinguish a failed search from an empty search.
Track the error state and render a distinct message.
🛠️ Proposed change
const isSearching = resultsQueries.some((q) => q.isFetching);
+ const searchFailed =
+ resultsQueries.length > 0 && resultsQueries.every((q) => q.isError);Then render the failure inside the transaction group, for example:
{searchFailed ? (
<CommandItem disabled className="justify-center text-muted-foreground">
Suche fehlgeschlagen. Bitte erneut versuchen.
</CommandItem>
) : null}🤖 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 `@components/layout/command-palette.tsx` around lines 117 - 140, Track whether
any query in resultsQueries has failed, derive a searchFailed state from the
query errors, and render the distinct failure CommandItem inside the transaction
results group before the no-results fallback. Ensure the existing “Keine
Ergebnisse gefunden” message is shown only when searching has not failed and
there are no hits.
| {!isSearching ? ( | ||
| <CommandItem | ||
| value="show-all-results" | ||
| onSelect={() => | ||
| openTransactionSearch(debouncedSearch, hits[0]?.trackerId) | ||
| } | ||
| > | ||
| <Search className="mr-2 h-4 w-4" /> | ||
| Alle Treffer für „{debouncedSearch}“ anzeigen | ||
| </CommandItem> | ||
| ) : null} |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
"Alle Treffer …" can do nothing silently.
showTransactionGroup depends only on debouncedSearch.length >= 2, so this item renders while trackersQuery is still loading. In that state hits is empty and defaultTracker is undefined, so openTransactionSearch returns at line 171 without closing the palette and without feedback.
Render this item only when a target tracker exists.
🛠️ Proposed change
- {!isSearching ? (
+ {!isSearching && (hits[0]?.trackerId || defaultTracker) ? (
<CommandItem
value="show-all-results"📝 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.
| {!isSearching ? ( | |
| <CommandItem | |
| value="show-all-results" | |
| onSelect={() => | |
| openTransactionSearch(debouncedSearch, hits[0]?.trackerId) | |
| } | |
| > | |
| <Search className="mr-2 h-4 w-4" /> | |
| Alle Treffer für „{debouncedSearch}“ anzeigen | |
| </CommandItem> | |
| ) : null} | |
| {!isSearching && (hits[0]?.trackerId || defaultTracker) ? ( | |
| <CommandItem | |
| value="show-all-results" | |
| onSelect={() => | |
| openTransactionSearch(debouncedSearch, hits[0]?.trackerId) | |
| } | |
| > | |
| <Search className="mr-2 h-4 w-4" /> | |
| Alle Treffer für „{debouncedSearch}“ anzeigen | |
| </CommandItem> | |
| ) : null} |
🤖 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 `@components/layout/command-palette.tsx` around lines 355 - 365, Update the
“show-all-results” CommandItem rendering in the command palette so it appears
only when a target tracker exists, using the available tracker result/default
tracker state rather than just !isSearching. Preserve the existing onSelect
behavior while preventing rendering during loading when hits[0]?.trackerId is
unavailable.
| "use client"; | ||
|
|
||
| import { Fragment, useState } from "react"; | ||
| import { useSearchParams } from "next/navigation"; |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Read the bundled Next.js docs for the version in use, per coding guidelines.
fd -t f . node_modules/next/dist/docs --exec rg -l -i 'useSearchParams'
fd -t f . node_modules/next/dist/docs --iglob '*search*' --exec sed -n '1,120p'
# Locate the route that renders TransactionsClient and look for a Suspense boundary.
rg -n -C 8 'TransactionsClient' --type=tsx --type=ts -g '!components/transactions/transactions-client.tsx'
rg -n 'Suspense' --type=tsx -g 'app/**'
# Confirm the installed Next.js version.
rg -n '"next"' package.jsonRepository: panteLx/BetterTracker
Length of output: 1455
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== relevant bundled Next.js docs =="
for f in \
node_modules/next/dist/docs/01-app/03-api-reference/04-functions/use-search-params.md \
node_modules/next/dist/docs/01-app/03-api-reference/03-file-conventions/layout.md \
node_modules/next/dist/docs/02-pages/04-api-reference/03-functions/use-search-params.md
do
echo "--- $f"
sed -n '1,180p' "$f"
done
echo
echo "== imports/usages of TransactionsClient =="
rg -n -C 8 '{[^}]*TransactionsClient[^}]*}|TransactionsClient' . -g '*.ts' -g '*.tsx' -g '!components/transactions/transactions-client.tsx' || true
echo
echo "== Suspense occurrences in app directories =="
rg -n -C 4 '<Suspense|</Suspense|Suspense' app components -g '*.ts' -g '*.tsx' || true
echo
echo "== files in app directory =="
git ls-files 'app/*' 'app/**' | sed 's#^`#-` #'Repository: panteLx/BetterTracker
Length of output: 23941
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== all TransactionsClient occurrences =="
rg -n -C 6 'TransactionsClient|transactions-client' . -g '*.ts' -g '*.tsx' || true
echo
echo "== app/transactions/page.tsx =="
sed -n '1,220p' app/transactions/page.tsx
echo
echo "== app/layout.tsx =="
sed -n '1,140p' app/layout.tsxRepository: panteLx/BetterTracker
Length of output: 6136
Wrap TransactionsClient in a Suspense boundary.
TransactionsPage imports and renders TransactionsClient, but that route and its parent layouts do not add <Suspense>. Next.js 16.2.12 docs state that a static prerendered route using useSearchParams must wrap the Client Component in Suspense to avoid a production build failure.
🤖 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 `@components/transactions/transactions-client.tsx` at line 4, Wrap the
TransactionsClient rendering path in a React Suspense boundary so its
useSearchParams dependency is supported during static prerendering. Update the
TransactionsPage or nearest server-rendered parent that imports
TransactionsClient, preserving the existing client component behavior and
providing an appropriate fallback.
Source: Coding guidelines
| if (paramsKey !== seenParamsKey) { | ||
| setSeenParamsKey(paramsKey); | ||
| setSelectedTracker(searchParams.get("tracker") || ""); | ||
| setQuery(searchParams.get("q") || ""); | ||
| setPage(1); | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Re-seeding is partial and skips identical repeat navigations.
Two problems follow from keying the re-seed on paramsKey alone.
- An identical navigation does not re-apply. Sequence: the palette pushes
?q=abc&tracker=t1; the user then edits the page search input to "xyz", which does not update the URL; the user runs the same palette search "abc" again.router.pushproduces the same query string, soparamsKeyis unchanged andquerystays "xyz". The page then shows different results than the palette hits. - Secondary filters survive the re-seed.
direction,categoryId,payeeId,from, andtokeep their previous values. The palette search atcomponents/layout/command-palette.tsxlines 117-126 applies no such filters, so a hit shown in the palette can be absent from the filtered page.
Reset the secondary filters together with q and tracker. To make repeat navigations take effect, either sync local edits back to the URL, or have the palette append a nonce parameter so each search produces a new query string.
🛠️ Proposed change for the stale secondary filters
if (paramsKey !== seenParamsKey) {
setSeenParamsKey(paramsKey);
setSelectedTracker(searchParams.get("tracker") || "");
setQuery(searchParams.get("q") || "");
+ setDirection(ALL_FILTER_VALUE);
+ setCategoryId(ALL_FILTER_VALUE);
+ setPayeeId(ALL_FILTER_VALUE);
+ setFrom("");
+ setTo("");
setPage(1);
}📝 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.
| if (paramsKey !== seenParamsKey) { | |
| setSeenParamsKey(paramsKey); | |
| setSelectedTracker(searchParams.get("tracker") || ""); | |
| setQuery(searchParams.get("q") || ""); | |
| setPage(1); | |
| } | |
| if (paramsKey !== seenParamsKey) { | |
| setSeenParamsKey(paramsKey); | |
| setSelectedTracker(searchParams.get("tracker") || ""); | |
| setQuery(searchParams.get("q") || ""); | |
| setDirection(ALL_FILTER_VALUE); | |
| setCategoryId(ALL_FILTER_VALUE); | |
| setPayeeId(ALL_FILTER_VALUE); | |
| setFrom(""); | |
| setTo(""); | |
| setPage(1); | |
| } |
🤖 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 `@components/transactions/transactions-client.tsx` around lines 128 - 133,
Update the re-seeding logic guarded by paramsKey !== seenParamsKey so it also
clears direction, categoryId, payeeId, from, and to alongside q and tracker.
Ensure identical palette searches re-seed local state by either syncing local
query edits back to the URL or having the command-palette navigation append a
changing nonce parameter, while preserving the existing palette search behavior.
Summary
Command palette (⌘K/Strg+K)
useQueries, merged und sortiert die Treffer nach Datum und zeigt bei mehreren Trackern einen farbigen Tracker-Badge pro Treffer./transactions?q=…&tracker=…und landet direkt in der passenden Filteransicht./transactions, während man schon dort war, aktualisierte die Liste nicht (URL-Query wurde nur beim Mount gelesen). Der Zustand wird jetzt bei jeder tatsächlichen Änderung derq/tracker-Query-Parameter neu gesetzt.Termine (Schedules)
Sonstiges
.playwright-mcp(lokaler Playwright-MCP-Scratch-Ordner) wird jetzt in.gitignore/.dockerignoreignoriert.Änderungen
components/layout/command-palette.tsx– Volltextsuche über alle aktiven Tracker, Schnellaktionen, manuelles Filtern statt cmdk-Fuzzy-Filtercomponents/layout/quick-add-sheet.tsx– neuerinitialStep-Prop, um direkt ins Buchungs-/Termin-Formular zu springencomponents/transactions/transactions-client.tsx– liestq/trackeraus der URL und synct bei jeder Navigation neu (nicht nur beim ersten Mount)components/ui/command.tsx–CommandDialogreichtshouldFilterdurchcomponents/schedules/schedules-client.tsx– Filterleiste (Suche, Typ, Kategorie, Einzahler, Datumsbereich) analog zur Buchungen-Seite.gitignore,.dockerignore–.playwright-mcpausschließenTest plan
npx tsc --noEmitundnpx eslint .sauber🤖 Generated with Claude Code