Skip to content

fix: replace loading/searched booleans with searchStatus enum in page search (#162)#164

Merged
zacharias-ona merged 1 commit intomainfrom
fix/162-search-empty-state-state-machine
Apr 17, 2026
Merged

fix: replace loading/searched booleans with searchStatus enum in page search (#162)#164
zacharias-ona merged 1 commit intomainfrom
fix/162-search-empty-state-state-machine

Conversation

@zacharias-ona
Copy link
Copy Markdown
Collaborator

Closes #162

What

The search empty state ("No pages match your search") never rendered reliably. This is the 5th occurrence of the same bug (previously #118, #126, #136, #144). Each previous fix addressed a specific race condition, but the underlying state machine — two independent boolean flags (loading and searched) coordinated across async operations — remained fragile. The flags could desynchronize in timing windows involving workspace resolution, debounce resets, and fetch abort/retry cycles.

How

Replace the independent loading and searched booleans with a single SearchStatus discriminated union ("idle" | "loading" | "done"). A single state variable cannot desynchronize with itself, eliminating the entire class of race conditions.

Key changes:

  • searchStatus="done" is now set in both the early return path (when workspaceId is null) and the finally block. The old code only set searched=true in the finally block, which the early return bypassed entirely — leaving searched=false permanently in certain timing windows.
  • Render conditions (showSkeleton, showEmpty) are computed from searchStatus + workspaceResolved, making the display logic explicit and auditable.
  • AbortError exceptions are no longer sent to Sentry (they are expected during rapid typing).

Testing

  • Added regression test: "shows empty state when workspace resolves to null" — verifies the early return path now transitions to searchStatus="done" so the empty state renders when workspaceId is null and workspaceResolved is true.
  • Added test: "does not capture AbortError in Sentry when user types quickly" — verifies the AbortError filtering.
  • All 180 unit tests pass (pnpm lint && pnpm typecheck && pnpm test).
  • All 5 search E2E tests pass locally (pnpm test:e2e -- e2e/search.spec.ts).

… search (#162)

The search empty state ('No pages match your search') never rendered
reliably because two independent boolean flags (loading, searched)
could get out of sync across async operations. This was the root cause
of issues #118, #126, #136, #144, and #162 — each previous fix
addressed a specific race condition but the underlying state machine
remained fragile.

Replace the independent loading and searched booleans with a single
SearchStatus discriminated union ('idle' | 'loading' | 'done'). This
makes it impossible for the flags to desynchronize because there is
only one state variable to transition.

Key changes:
- searchStatus='done' is set in both the early return path (when
  workspaceId is null) and the finally block, closing the gap where
  searched was never set to true
- Render conditions (showSkeleton, showEmpty) are computed from
  searchStatus + workspaceResolved, making the logic explicit
- AbortErrors are no longer sent to Sentry (they are expected during
  rapid typing)

Co-authored-by: Ona <no-reply@ona.com>
@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 17, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
memo Ready Ready Preview, Comment Apr 17, 2026 9:52am

Request Review

@zacharias-ona zacharias-ona merged commit 3158de2 into main Apr 17, 2026
6 checks passed
@zacharias-ona zacharias-ona deleted the fix/162-search-empty-state-state-machine branch April 17, 2026 09:56
@zacharias-ona
Copy link
Copy Markdown
Collaborator Author

✅ UI verification passed — design spec compliance confirmed.

Static analysis — all changed UI in src/components/sidebar/page-search.tsx checked against .agents/design.md:

Check Result
Color tokens ✅ All colors use CSS variables / design spec tokens
Typography text-sm, text-xs only — within scale
Spacing ✅ Tailwind scale values only, no arbitrary spacing
Loading states ✅ Skeleton placeholders (animate-pulse), no spinners
Empty states ✅ Centered text in dropdown — appropriate for context
Transitions transition-none on results, no decorative animations
Accessibility aria-label, role="combobox", role="listbox", aria-expanded, aria-activedescendant
Dark mode ✅ Token variables throughout
Borders / corners border-white/[0.06], rounded-sm on dropdown (floating element exception)
Responsive ✅ Mobile touch targets ≥44px on clear button

Visual verification — Playwright screenshots of the workspace route (desktop dark + mobile) confirmed:

  • Skeleton loading state renders correctly during search
  • Empty state ("No pages match your search") renders on both desktop and mobile
  • Search dropdown styling matches spec (colors, borders, corners, shadows)
  • Mobile sidebar sheet layout is correct with no horizontal scroll
  • No broken layouts or overlapping elements

@zacharias-ona
Copy link
Copy Markdown
Collaborator Author

❌ Post-merge verification failed.

E2E suite: 41/42 passed, 1 failed

  • e2e/search.spec.ts — "typing in search input shows matching results": search results dropdown (#search-results [role="option"]) did not render within 5s timeout

Ad-hoc smoke tests: all passed

  • ✅ Landing page (200, has title)
  • ✅ Sign-in page (email input present)
  • ✅ Health endpoint (200, not down)
  • ✅ Authenticated login + workspace load
  • ✅ Page navigation via sidebar
  • ⏭️ Skipped: editor element detection (not yet implemented), /dashboard (404)

See #166.

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 empty state still broken — "No pages match your search" never renders (4th recurrence)

1 participant