Repository navigation
Conversation
Adds symptom-based doctor search for issue DocForum#30: a WAI-ARIA 1.2 combobox on a new patient search page autocompletes against the backend's controlled symptom vocabulary (docforum-core#60), the applied symptom discloses which specialties it maps to ("Showing doctors in Cardiology and General practice"), the availability filter combines with it, and all state (symptom slug + availability) lives in the URL with validation for hand-edited links. Suggestion requests are debounced and superseded in-flight requests are cancelled through React Query's AbortSignal. A no-match result always offers a concrete way forward. Issue DocForum#30 depends on DocForum#1 (doctor search), which was still open, so this also lands the underlying doctor-search page, service and MSW handlers/fixtures built against the documented assumed contracts (docforum-core#23 doctor list, DocForum#60 symptom mapping), plus the reusable Combobox component. The mock bootstrap now awaits MSW readiness before the first render so deep-linked states don't race the worker's startup. Tests: Combobox keyboard/ARIA suite, SymptomCombobox debounce + cancellation suite, DoctorSearchView integration suite, URL-param and specialty-list units, and handler coverage for /symptoms, /symptoms/:slug and /doctors — 45 files, 407/407 passing, typecheck, build and check:dist green. ROADMAP.md and ARCHITECTURE_ESSENTIALS.md "Known assumptions" updated; desktop + mobile screenshots attached.
…search-with-autocomplete # Conflicts: # src/types/models.ts
|
@Rafiat30 Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits. You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀 |
|
@Rafiat30 Thanks for the PR. It can't be merged yet. To unblock it: Merge conflicts: this branch conflicts with Push to this same branch and CI will re-run. Once it's green and conflict-free, it'll be reviewed for merge. |
…search-with-autocomplete Resolve conflicts against upstream main (DocForum#156, DocForum#163, DocForum#184, DocForum#185, DocForum#208 and follow-ups): keep both sides in models/fixtures/handlers/ROADMAP/ ARCHITECTURE_ESSENTIALS, keep the resilient mock-worker bootstrap, and re-apply the PR's Find-a-doctor nav link, dashboard link and /patient/search route on top of upstream's responsive-layout and care-hub rewrites.
|
@Rafiat30 This PR now has merge conflicts with git fetch upstream # upstream = https://github.com/DocForum/docforum-web.git
git rebase upstream/main
# resolve the conflicts, re-run the tests, then:
git push --force-with-leaseThe usual culprits are shared docs like Automated message. It won't repeat unless the PR conflicts again after your next push. |
…search-with-autocomplete Resolve conflicts with upstream's W7 i18n work (DocForum#157, DocForum#151): keep the PR's Find-a-doctor nav item and dashboard link, adopt upstream's t()/messageId convention for both, and register nav.findADoctor and patient.nav.findDoctor in the en and es catalogues so check:i18n stays green.
|
@Rafiat30 CI is failing in the Browser job ( If the patient dashboard change is intentional, regenerate the baselines (see the visual-testing notes in the repo; they must be generated in the same Linux/Playwright environment CI uses) and commit them. If it isn't intentional, fix the layout regression. Then push. |
Closes #30
What this does
Implements the W2 patient core flow (PRD §4 — a patient finds the doctor who can treat what's wrong): symptom-based doctor search with an accessible autocomplete on a new page,
/patient/search, linked from the patient header nav and dashboard.Comboboximplements the WAI-ARIA 1.2 combobox pattern properly: therole="combobox"input keeps DOM focus at all times,aria-expanded/aria-controls/aria-autocomplete="list"are wired to the listbox, the highlighted option is communicated witharia-activedescendant(never by moving focus), Arrow/Home/End/Enter/Escape/Tab all behave per the APG, and a politerole="status"live region announces suggestion counts, the active option, loading and no-match states. Suggestions come from the backend's controlled vocabulary (GET /symptoms?q=) — free text never reaches the doctor-search endpoint, and typing is debounced (250 ms).GET /symptoms/:slugreturns its specialties and the page shows "Showing doctors in Cardiology and General practice." (with a Clear button and a matching live-region announcement).Any time/This week/Today) live in the URL (?symptom=chest-pain&availability=today), so a search is shareable and survives back/forward. Values parsed from the URL are validated; invalid ones are dropped with a visible notice and never sent to the backend.AbortSignaltofetch; when a newer keystroke supersedes a fetch,cancelQueriesaborts the old one — proven by a test assertingsignal.aborted === true.404) gets its own explanation instead of a generic error.GET /doctors?specialtyIds=…&availability=…returning doctors in any mapped specialty, soonest-available first, verified only.All data goes through
docforum-core's API viasrc/services/(ARCHITECTURE_ESSENTIALS.md hard rules 1–3); assumed shapes are documented under "Known assumptions — Phase W2 — Doctor search & symptom-based search".DoctorSearchResultreuses the existingDoctorAvailabilityshape rather than inventing a parallel one.Files
New files
src/components/Combobox.tsx,Combobox.module.css— accessible combobox/listboxsrc/features/search/search-params.ts— URL state parse/serialize + validationsrc/features/search/specialty-list.ts— "A, B and C" formattingsrc/features/search/hooks/useSymptomSuggestions.ts(debounce + abort),useSymptomMapping.ts,useDoctorSearch.ts,useSearchUrlState.tssrc/features/search/components/SymptomCombobox.tsx,DoctorSearchView.tsx,DoctorResultCard.tsx,AvailabilityFilter.tsx,DoctorSearch.module.csssrc/pages/DoctorSearchPage.tsx— route/patient/searchsrc/services/search-service.ts—/symptoms,/symptoms/:slug,/doctorssrc/mocks/handlers/search.ts— MSW handlers + vocabulary/specialty/doctor fixtures (perCONTRIBUTING.md, every new endpoint ships with mocks)screenshots/doctor-search-{1-initial,2-suggestions,3-results,4-no-match}-{desktop,mobile}.pngModified files
src/types/models.ts—AvailabilityFilter,SymptomSuggestion,SymptomSpecialtyMapping,DoctorSearchQuery,DoctorSearchResultsrc/pages/PatientDashboardPage.tsx(route + dashboard link),src/components/Layout.tsx(header link)src/mocks/fixtures.ts,src/mocks/handlers.ts,src/mocks/setup.tsx,src/main.tsx(mock bootstrap now awaits MSW readiness — deep-linked states previously raced the worker and bypassed it),src/mocks/handlers.test.tsROADMAP.md(item [Phase W2] Symptom-based search with autocomplete #30 marked done + changelog line),ARCHITECTURE_ESSENTIALS.md(assumed API shapes)Test files
src/components/Combobox.test.tsx(9 tests)src/features/search/components/SymptomCombobox.test.tsx(5)src/features/search/components/DoctorSearchView.test.tsx(8)src/features/search/search-params.test.ts(8)src/features/search/specialty-list.test.ts(1)src/mocks/handlers.test.ts(3 new tests, 29 total)Implementation details
Comboboxis pure presentation + interaction (text/options/intent via props); debounce, fetching and state live inSymptomCombobox, which makes the ARIA behaviour testable in isolation and reusable for future pickers.useSymptomSuggestionsdebounces the raw input, keys React Query on the debounced text, and cancels the wholesymptom-suggestionscache prefix whenever that text changes or the component unmounts, so a superseded response can never land on top of newer results.parseSearchParams/toSearchParamsround-trip state;useSearchUrlStateexposesselectSymptom/setAvailability/clearAll. No symptom ⇒ no specialty ids ⇒ the doctor query is disabled — an unfiltered "every doctor" request doesn't exist, so a mapping failure can't degrade into a directory listing./symptomsdoes case-insensitive substring matching withlimit;/symptoms/:slug404s for unknown slugs;/doctorsOR-matchesspecialtyIds, filterstoday(≈next 24 h in the mock) vsweek(7 days), sorts soonest-first and hides unverified doctors. Fixtures use relative availability windows so the demo never goes stale.startMockApi()is now awaited before first render (src/main.tsx/src/mocks/setup.tsx) — previouslyworker.start()was fire-and-forget, so deep-linked pages fired their first queries before the worker controlled the page and silently bypassed the mocks.Tests added
aria-expanded,aria-controls,aria-autocomplete, accessible description), open-on-type with live-region count announcement, ArrowDown/ArrowUp wrapping, Home/End,aria-activedescendanttracking the option DOM id while focus never leaves the input, Enter commits + closes, mouse click commits, two-stage Escape (close, then clear), Clear button, empty state, loading state.signals[0].aborted === trueand the new signal untouched.availability=weekread from the URL on load; no-match guidance incl. "Show doctors with any availability" resetting the URL and query; picking a symptom from the combobox updates?symptom=and searches the mapped specialties; invalid URL params dropped with notices and zero bogus requests; unknown slug shows the friendly 404 path with no doctor request; initial guidance state fires no requests.Test output
& npm run build` (this is exactly what CI runs).