Skip to content

Command palette search + action tool, and schedules filters - #37

Merged
panteLx merged 2 commits into
mainfrom
feat/command-palette-search
Jul 31, 2026
Merged

Command palette search + action tool, and schedules filters#37
panteLx merged 2 commits into
mainfrom
feat/command-palette-search

Conversation

@panteLx

@panteLx panteLx commented Jul 31, 2026

Copy link
Copy Markdown
Owner

Summary

Command palette (⌘K/Strg+K)

  • Durchsucht jetzt Buchungen (Notizen/Kategorie/Einzahler/Konto) über alle Tracker, denen man angehört, parallel per useQueries, merged und sortiert die Treffer nach Datum und zeigt bei mehreren Trackern einen farbigen Tracker-Badge pro Treffer.
  • Ein Treffer-Klick (oder "Alle Treffer anzeigen") springt zu /transactions?q=…&tracker=… und landet direkt in der passenden Filteransicht.
  • Neue Schnellaktionen: "Neue Buchung" / "Neuer Termin" (öffnen das bestehende Quick-Add-Sheet direkt auf dem jeweiligen Formular, ohne den Auswahl-Schritt) sowie Theme wechseln.
  • Bugfix: Ein zweiter Sprung von der Palette nach /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 der q/tracker-Query-Parameter neu gesetzt.

Termine (Schedules)

  • Bekommt dieselbe on-screen Filterleiste wie die Buchungen-Seite: Suche (Name/Kategorie/Einzahler/Notiz) plus Filter nach Typ, Kategorie, Einzahler und Datumsbereich.

Sonstiges

  • .playwright-mcp (lokaler Playwright-MCP-Scratch-Ordner) wird jetzt in .gitignore/.dockerignore ignoriert.

Änderungen

  • components/layout/command-palette.tsx – Volltextsuche über alle aktiven Tracker, Schnellaktionen, manuelles Filtern statt cmdk-Fuzzy-Filter
  • components/layout/quick-add-sheet.tsx – neuer initialStep-Prop, um direkt ins Buchungs-/Termin-Formular zu springen
  • components/transactions/transactions-client.tsx – liest q/tracker aus der URL und synct bei jeder Navigation neu (nicht nur beim ersten Mount)
  • components/ui/command.tsxCommandDialog reicht shouldFilter durch
  • components/schedules/schedules-client.tsx – Filterleiste (Suche, Typ, Kategorie, Einzahler, Datumsbereich) analog zur Buchungen-Seite
  • .gitignore, .dockerignore.playwright-mcp ausschließen

Test plan

  • npx tsc --noEmit und npx eslint . sauber
  • Command Palette manuell im Browser getestet (test@test.de): Suche über zwei Tracker liefert gemischte, nach Datum sortierte Treffer mit Tracker-Badge; Klick navigiert korrekt inkl. Tracker-Umschaltung; zweite Suche von der bereits geöffneten Buchungen-Seite aktualisiert Filter und Liste korrekt; "Neue Buchung"/"Neuer Termin" öffnen das Sheet direkt im richtigen Schritt; Theme-Toggle funktioniert
  • Termine-Filterleiste manuell im Browser gegentesten

🤖 Generated with Claude Code

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>
@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Changes

Command palette search and navigation

Layer / File(s) Summary
Command filtering contract
components/ui/command.tsx
CommandDialog accepts and forwards the optional shouldFilter property.
Palette search and actions
components/layout/command-palette.tsx
CommandPalette searches transactions across active trackers, filters command groups, displays loading and result states, and adds navigation, theme, tracker, and creation actions.
Quick-add entry points and URL filters
components/layout/quick-add-sheet.tsx, components/transactions/transactions-client.tsx
QuickAddSheet can open at a requested step. TransactionsClient synchronizes its filters and pagination with URL parameters.

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
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the command palette search and action changes, and it also references the related schedule filter updates.
✨ 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 feat/command-palette-search

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.

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>
@panteLx panteLx changed the title Turn the command palette into a real search + action tool Command palette search + action tool, and schedules filters Jul 31, 2026

@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: 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

📥 Commits

Reviewing files that changed from the base of the PR and between e6990f2 and b8f830d.

📒 Files selected for processing (4)
  • components/layout/command-palette.tsx
  • components/layout/quick-add-sheet.tsx
  • components/transactions/transactions-client.tsx
  • components/ui/command.tsx

Comment on lines +117 to +140
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);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 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.

Comment on lines +355 to +365
{!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}

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

"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.

Suggested change
{!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";

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 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.json

Repository: 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.tsx

Repository: 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

Comment on lines +128 to +133
if (paramsKey !== seenParamsKey) {
setSeenParamsKey(paramsKey);
setSelectedTracker(searchParams.get("tracker") || "");
setQuery(searchParams.get("q") || "");
setPage(1);
}

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 | 🟠 Major | ⚡ Quick win

Re-seeding is partial and skips identical repeat navigations.

Two problems follow from keying the re-seed on paramsKey alone.

  1. 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.push produces the same query string, so paramsKey is unchanged and query stays "xyz". The page then shows different results than the palette hits.
  2. Secondary filters survive the re-seed. direction, categoryId, payeeId, from, and to keep their previous values. The palette search at components/layout/command-palette.tsx lines 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.

Suggested change
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.

@panteLx
panteLx merged commit 14e6858 into main Jul 31, 2026
2 checks passed
@panteLx
panteLx deleted the feat/command-palette-search branch July 31, 2026 17:03
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.

1 participant