Skip to content

fix(layout): wire topbar search input to search query param (#205) - #206

Merged
fredpena merged 1 commit into
floci-io:mainfrom
amasen02:fix/topbar-search-query-param-205
Sep 16, 2026
Merged

fredpena merged 1 commit into
floci-io:mainfrom
amasen02:fix/topbar-search-query-param-205

Conversation

@amasen02

@amasen02 amasen02 commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes #205 — the top nav search bar had no onChange handler and was entirely uncontrolled.

Root Cause

Layout.tsx used a bare <input> with no value, no handler, and no URL integration.

Solution

Extracted a TopbarSearch controlled component that:

  • Reads the initial value from the ?search= URL query param (survives refresh/nav via useSearchParams)
  • Writes back with a 300 ms debounce using {replace: true} to avoid per-keystroke history entries
  • Pressing Escape clears the search and blurs the input
  • Pressing / from any non-input context focuses the bar (matches the existing kbd hint)
  • Added aria-label for screen-reader accessibility

Tests

4 new regression tests in packages/api/src/routes/clouds.search.test.ts (bun):

Test Result
Forwards ?search= to listResources pass
Passes undefined when no param supplied pass
Filters resources via adapter search pass
Returns 404 for unknown cloud pass

All 729 existing API tests continue to pass. TypeScript (tsc --noEmit) reports 0 errors.

Checklist

  • Root cause fixed (not a bandage)
  • No regressions (729/729 pass)
  • TypeScript clean
  • Draft PR

@amasen02
amasen02 marked this pull request as ready for review September 4, 2026 02:39
@greptile-apps

greptile-apps Bot commented Sep 4, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR makes the topbar search URL-backed, debounced, keyboard-accessible, and usable for filtering service navigation and console-home cards.

  • Adds URL and keyboard lifecycle handling to the topbar search.
  • Filters sidebar and console-home services from the shared search parameter.
  • Adds API regression coverage for forwarding resource search parameters.

Confidence Score: 3/5

The PR is not yet safe to merge because resource search remains disconnected and a pending debounce can still overwrite navigation state.

The Cloud Explorer query continues to use independent local search state rather than the URL-backed topbar value, and the new navigation synchronization cancels stale timers only after navigation has committed, leaving the old callback able to mutate the destination URL.

Files Needing Attention: packages/frontend/src/components/Layout.tsx, packages/frontend/src/components/DynamicResourceView.tsx, packages/frontend/src/pages/CloudExplorerPage.tsx

Important Files Changed

Filename Overview
packages/frontend/src/components/Layout.tsx Adds the URL-backed topbar and service filtering, but navigation cancellation still leaves a stale debounce race.
packages/frontend/src/pages/CloudConsoleHomePage.tsx Reads the shared search parameter and consistently filters console service cards.
packages/frontend/src/features/cloud-console/CloudConsoleSections.tsx Adds an explicit empty state for filtered and naturally empty service grids.
packages/frontend/src/features/cloud-console/types.ts Extends ServiceGrid props with the optional search text used by its empty state.
packages/api/src/routes/clouds.search.test.ts Adds focused route and adapter regression coverage for the resource search parameter.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  Input[Topbar input] --> Timer[300 ms debounce]
  Timer --> URL[search query parameter]
  URL --> Nav[Sidebar service filter]
  URL --> Home[Console service grid]
  Local[Resource toolbar search] --> Query[Cloud Explorer resource query]
Loading

Reviews (5): Last reviewed commit: "fix(layout): wire topbar search to servi..." | Re-trigger Greptile

Comment on lines +232 to +245
const commit = useCallback((value: string) => {
setSearchParams(
(prev) => {
const next = new URLSearchParams(prev)
if (value) {
next.set('search', value)
} else {
next.delete('search')
}
return next
},
{replace: true},
)
}, [setSearchParams])

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.

P1 Resource query remains disconnected

When a user searches from a Cloud Explorer page, commit only updates the URL while DynamicResourceView continues querying with its independent local search state, so the resource list is neither refetched nor filtered.

Knowledge Base Used: Cloud Explorer interface

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment thread packages/frontend/src/components/Layout.tsx
@amasen02

amasen02 commented Sep 4, 2026

Copy link
Copy Markdown
Contributor Author

Addressed the review feedback in commit \ e17a12:

  1. Connected DynamicResourceView to Search Query Parameter:

    • Replaced isolated local \useState('')\ in \DynamicResourceView.tsx\ with \useSearchParams().
    • Now when a search query is present in the URL (e.g. from topbar search or deep links), \DynamicResourceView\ reactively consumes \searchParams.get('search')\ and forwards it to \listCloudResources(cloud, service, search, signal).
  2. Debounce Cancellation on Escape:

    • In \TopbarSearch\ (\Layout.tsx), explicit cancellation (\clearTimeout(debounceRef.current)) is now performed immediately on Escape keypress before clearing the draft, ensuring a pending timer cannot overwrite the cleared query.
    • Added unmount cleanup effect for the debounce timer.

Comment thread packages/frontend/src/components/DynamicResourceView.tsx Outdated
Comment thread packages/frontend/src/components/Layout.tsx
@amasen02

amasen02 commented Sep 5, 2026

Copy link
Copy Markdown
Contributor Author

Pushed commit \9638c4b\ addressing the review findings:

  1. Replaced the removed \setSearch\ reference in \DynamicResourceView.tsx\ with \setSearchParams(..., { replace: true }), keeping the local resource filter synchronized with the URL.
  2. Cancelled pending search debounce timeouts when \location.pathname\ changes or when the search query param is updated externally, ensuring stale topbar commits do not overwrite new routes.
  3. Verified full \ sc && vite build\ in \packages/frontend\ passes cleanly with 0 errors, alongside the 729 passing API tests.

Comment thread packages/frontend/src/components/Layout.tsx
@amasen02

amasen02 commented Sep 5, 2026

Copy link
Copy Markdown
Contributor Author

Pushed commit \80d89dc: when navigating to a new pathname before the debounce timer fires, the draft is now explicitly restored from the destination route's \search\ query parameter, ensuring the controlled input never displays an uncommitted filter string on an unfiltered view.

@fredpena

Copy link
Copy Markdown
Contributor

Hi @amasen02

Thanks for the careful debounce and URL synchronization work. The follow-up fixes for Escape, navigation, and stale timers are appreciated.

Before we can merge this, the branch needs to be rebased on current main and adapted to the current Cloud Explorer state. There is now a merge conflict around DynamicResourceView, including the RDS snapshot tab behavior, and the new API test still uses the older resource shape rather than metadata.

More importantly, the issue describes global search across services or resources. The current implementation only applies the topbar query to the resource list of the active Cloud Explorer service. On Console Home, the service cards and sidebar do not react, so the topbar still has no visible search result there.

Could you please keep Filter resources scoped to the current service, and make the topbar search filter or surface matching cloud services from the existing service catalog? If global resource search is intended as well, it should be presented as explicit cross-service results rather than sharing the same resource-filter state.

Please update the tests for the current contracts and include manual verification for Console Home, Cloud Explorer resource filtering, navigation during a pending debounce, and Escape clearing. Once that is rebased and green, this will be in a good position to merge.

…loci-io#205)

Signed-off-by: amasen02 <amasen02@users.noreply.github.com>
@amasen02
amasen02 force-pushed the fix/topbar-search-query-param-205 branch from 80d89dc to 23dabc6 Compare September 14, 2026 23:09
@amasen02

Copy link
Copy Markdown
Contributor Author

Hi @fredpena,

Thank you for the detailed and constructive architectural guidance! All points have been addressed in commit 23dabc6:

  1. Clean Rebase on main & Scoped Resource Filtering:

    • Rebased onto latest origin/main with zero conflicts.
    • Restored DynamicResourceView to its upstream implementation so Filter resources remains strictly local and scoped to the active service, fully preserving the new RDS snapshot tab behavior and schema actions.
  2. Global Service Search across Console Home & Sidebar:

    • Sidebar (CloudServiceNav): Reactively filters the cloud service catalog by the topbar search query parameter (matching displayName, service, and group). If no services match, renders a clear "No matching services" status.
    • Console Home (CloudConsoleHomePage & ServiceGrid): Filters data.serviceCards against the query (matching label, id, and meta), displaying an explicit empty state (No services matching "...") when no services match.
  3. Interaction & Debounce Safeguards:

    • 300 ms debounce before committing to useSearchParams.
    • Escape immediately cancels pending debounce timers, clears the query parameter, and blurs the input.
    • Route changes cancel pending debounce and sync the draft with the destination route.
    • Global / hotkey focuses the search input when not typing in another field.
  4. Updated API Test Contract:

    • Updated packages/api/src/routes/clouds.search.test.ts to use the modern CloudResource structure with metadata: Record<string, unknown> and createdAt.

Verification Evidence:

  • Backend: bun test packages/api → 962/962 passed (52 files), including all 4 clouds.search.test.ts cases.
  • Frontend: tsc && vite build in packages/frontend → 0 errors, built in 249 ms.

Comment on lines +296 to +303
useEffect(() => {
if (debounceRef.current) {
clearTimeout(debounceRef.current)
debounceRef.current = null
}
const urlSearch = searchParams.get('search') ?? ''
setDraft(urlSearch)
}, [location.pathname])

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.

P1 Stale debounce survives navigation

When navigation commits immediately before the pending 300 ms timer expires, the timer writes the previous route's search value into the destination URL before these passive effects cancel it, replacing the destination's intended search state and filtering with stale text.

Knowledge Base Used: Cloud Explorer interface

@fredpena

Copy link
Copy Markdown
Contributor

Hi @amasen02

Thanks for the thorough follow-through on the architecture feedback, all four points are addressed and I verified each independently rather than just from the description: the rebase is clean, DynamicResourceView is back to upstream so resource filtering stays scoped to the active service, the test uses the current metadata shape, and global search now filters the sidebar and Console Home catalog rather than sharing state with the resource filter. Tried it live: typing "dynamo" narrows both the sidebar and the Console Home cards to DynamoDB and updates the URL, Escape clears it.

@fredpena
fredpena merged commit b277694 into floci-io:main Sep 16, 2026
1 check passed
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.

[BUG] Search bar in top nav doesn't update the search query param

2 participants