Skip to content

fix(cases): UAT Section 4 — server-side search, context-aware empty state, doc rewrite (#27)#42

Merged
Taleef7 merged 3 commits into
mainfrom
fix/uat-section4-cases-worklist
May 20, 2026
Merged

fix(cases): UAT Section 4 — server-side search, context-aware empty state, doc rewrite (#27)#42
Taleef7 merged 3 commits into
mainfrom
fix/uat-section4-cases-worklist

Conversation

@Taleef7

@Taleef7 Taleef7 commented May 20, 2026

Copy link
Copy Markdown
Owner

Summary

Addresses UAT findings in #27 (Section 4 of the Cases Worklist).

Code

  • Backend searchGET /api/cases accepts an optional search param. CaseFlowService.listCases adds a case-insensitive LIKE on employees.name / employees.external_id, so search spans the whole worklist instead of being limited to the first 25 loaded cases on the client.
  • Frontend /cases
    • Debounced (300 ms) URL sync: typing into the search box updates ?search=… and reloads from the backend.
    • Removed the legacy useMemo client filter; backend is the source of truth.
    • loadMoreCases also forwards the search term so pagination stays consistent.
    • Empty state is now context-aware: when a search is active it reads No results match your search "<term>". instead of the misleading No open cases. Run a measure to generate cases..
    • Replaced two dev-speak strings in the page header / subhead with user-facing copy (Open worklist cases now persist from the seeded measure runs. → friendlier description; Loaded from the DB-backed case endpoints.Filter, search, and bulk-act on flagged cases.).

Docs — docs/WALKTHROUGH_GUIDE.md Section 4

Rewrote the section to match what the page actually does:

  • Clarified the dual sidebar entries (Cases vs Worklist).
  • Cards-in-a-grid, not a table; called out that Assignee / Next Action are not on the card.
  • Status filter is pill toggle buttons (Open / Closed / All / Excluded); Closed covers CLOSED and RESOLVED.
  • Documented the All Cases / My Cases tabs.
  • Corrected bulk-action labels (Assign to… / Escalate selected / Export selected) and noted the cases-selected.csv download.
  • Documented the 25-per-page grid with the Load more cases button.
  • Documented the two export buttons (Export cases CSV, Export audit CSV) and the current v1 column list.

Acceptance criteria from #27

  • Search for "Omar" surfaces emp-006 (Omar Siddiq) — now resolved server-side, even when Omar is outside the first page of results.
  • Empty state copy is context-aware (search active vs. truly no cases).
  • Internal implementation note removed from page header (Caseflow → Why Flagged cases subhead + section subhead).
  • Guide inaccuracies S4-1 through S4-8 corrected in docs/WALKTHROUGH_GUIDE.md.

Test plan

  • ./gradlew.bat test --tests com.workwell.web.CaseControllerTest — passes after the 9-arg signature update.
  • pnpm lint and npx tsc --noEmit clean in frontend/.
  • Bug reproduced against prod with Playwright (emp-001 search → misleading empty state). Fix will need a deploy to verify against the live stack.
  • Manual smoke after merge & deploy: search by name and by external ID, with the searched case outside the first 25; clear search to restore the full list; confirm the new empty-state copy with a non-matching term.

🤖 Generated with Claude Code

…4 doc rewrite (#27)

Issue #27 — UAT findings for Section 4 of the Cases Worklist.

Backend
- `GET /api/cases` accepts an optional `search` query param.
- `CaseFlowService.listCases` adds a case-insensitive ILIKE filter on
  `employees.name` and `employees.external_id` so search spans the full
  worklist instead of being limited to the first 25-page client filter.

Frontend (`/cases`)
- Search input is debounced (300 ms), syncs to `?search=` in the URL, and
  is forwarded to the backend on both the initial load and "Load more".
- The legacy client-side filter is removed; the backend is the source of
  truth so results no longer depend on which cases happen to be loaded.
- Empty state is now context-aware — when a search is active it reads
  "No results match your search \"<term>\"." instead of the misleading
  "No open cases. Run a measure to generate cases."
- Replaced two dev-speak copy strings in the page header / subhead with
  user-facing descriptions.

Docs (`docs/WALKTHROUGH_GUIDE.md` Section 4)
- Rewrote the section to match the implementation:
  - clarified the dual `Cases` / `Worklist` sidebar entries
  - cards, not a table; Assignee / Next Action live on case detail
  - status filter is pill toggle buttons (Open / Closed / All / Excluded);
    Closed covers `CLOSED` and `RESOLVED`
  - All Cases / My Cases tabs documented
  - bulk-action labels corrected (Assign to… / Escalate selected /
    Export selected); separate `cases-selected.csv` download
  - documented the 25-per-page grid with the "Load more cases" button
  - two export buttons (Export cases CSV, Export audit CSV) with the
    full v1 columns list

Tests
- `CaseControllerTest` stubs updated for the new 9-arg `listCases` signature.
- `CaseControllerTest` runs green (`./gradlew.bat test --tests
  com.workwell.web.CaseControllerTest`).
- Frontend `pnpm lint` and `tsc --noEmit` clean.
- Bug reproduced against prod with Playwright: searching `emp-001`
  returned the misleading "No open cases" empty state because emp-001
  was outside the first 25-case page load.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings May 20, 2026 18:28
@vercel

vercel Bot commented May 20, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
workwell-measure-studio Ready Ready Preview, Comment May 20, 2026 7:16pm

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6b0897c94e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines 97 to +101
const timer = window.setTimeout(() => {
setSearchTerm(urlSearch);
}, 0);
const params = new URLSearchParams(searchParams.toString());
const trimmed = searchTerm.trim();
if (trimmed) {
params.set("search", trimmed);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve URL-driven search changes

When urlSearch changes independently (for example via browser back/forward, a shared link, or another control updating query params), this effect treats any mismatch as a signal to rewrite the URL from stale searchTerm state instead of syncing state from the URL. After 300ms it can revert the new ?search= value, causing history navigation to “bounce back” and loading results for the old term rather than the URL the user navigated to.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Addressed in 34fcaed.

Split the sync into two effects guarded by a lastWrittenSearchRef:

  • URL → state pulls into the input whenever urlSearch differs from what we last wrote.
  • state → URL debounces typing, updates the ref before router.replace, so the URL→state effect treats the resulting URL change as our own write and skips it.

Browser back/forward and deep links now update both the input and the query without being overwritten.

Copilot AI left a comment

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.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Codex flagged that the single-direction URL sync clobbers external
URL changes. When `urlSearch` updates from browser back/forward, a
shared link, or another control mutating query params, the previous
effect saw a mismatch with stale local `searchTerm` state and, after
the 300 ms debounce, rewrote the URL back to the old term — bouncing
history navigation back and loading results for the wrong query.

Split the sync into two effects with a `lastWrittenSearchRef` guard:

- URL → state pulls external URL changes into the input whenever
  `urlSearch` differs from what we last wrote.
- state → URL debounces user typing and updates the ref before
  calling `router.replace`, so the URL→state effect treats the
  resulting URL change as our own write and skips it.

Result:
- Typing still updates the URL after 300 ms.
- Browser back/forward and deep links update both the input and the
  query without being overwritten.
- No regression on the original Section 4 search fix — the backend
  query still receives the active term from the URL.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

@Taleef7 Taleef7 left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Found one additional pagination consistency issue beyond the existing URL/search sync comment. Once those are addressed, the Section 4 behavior should line up with the UAT acceptance criteria.

else if (siteId) params.set("site", siteId);
if (from) params.set("from", from);
if (to) params.set("to", to);
if (urlSearch.trim()) params.set("search", urlSearch.trim());

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

loadCases scopes the initial request with effectiveAssignee when view === "mine", but loadMoreCases still only forwards assigneeFilter. In the My Cases tab, clicking Load more cases will drop the current-user assignee scope and append cases from everyone else, even though the PR now forwards the search term. Please mirror the same effective-assignee logic here so pagination stays consistent with the active view.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Addressed in 72cbc0dloadMoreCases now derives the same effectiveAssignee = view === 'mine' ? user.email : assigneeFilter value as loadCases before appending the next page, so the My Cases scope is preserved across pagination.

…#42)

Review noted that `loadCases` scopes the initial request with
`effectiveAssignee = view === "mine" ? user.email : assigneeFilter`,
but `loadMoreCases` only forwarded the raw `assigneeFilter`. On the
"My Cases" tab, clicking **Load more cases** dropped the
current-user assignee scope and appended cases from everyone else.

Mirror the same effective-assignee logic in `loadMoreCases` so
pagination stays consistent with the active view.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@Taleef7 Taleef7 merged commit a7ccdff into main May 20, 2026
8 checks passed
@Taleef7 Taleef7 deleted the fix/uat-section4-cases-worklist branch May 20, 2026 19:40
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.

2 participants