Skip to content

React 19 Suspense refactor + template tool removal#60

Merged
chriskehayias merged 14 commits into
mainfrom
refactor/react-19-suspense-and-housekeeping
May 16, 2026
Merged

React 19 Suspense refactor + template tool removal#60
chriskehayias merged 14 commits into
mainfrom
refactor/react-19-suspense-and-housekeeping

Conversation

@chriskehayias

Copy link
Copy Markdown
Contributor

Summary

  • Suspense-based user profile loading: UserProvider now exposes a Promise<MPUserProfile | null> consumed via use(); errors flow to ErrorBoundary, loading to Suspense. Drops isLoading/error from the context surface. Fix included for a setState-in-render that occurred when the underlying server action triggered router invalidation during render.
  • Suspense-based contact lookup: /contactlookup/[guid] initiates getContactDetails and getContactLogsByContactId in the server component and passes promises down; the client unwraps with use() and refreshes via router.refresh().
  • Template tool removed entirely: route, tool/ framework (ToolContainer/Header/Footer/ParamsDebug), user-tools-debug/, tool-params.ts, ToolService, tests, dashboard card, and all doc references. Nothing else in the app consumed any of these pieces.
  • Misc: contact-logs switched to useWatch for scoped re-renders; session-context orphan createContext removed; lucide-react ^1.16, typescript ^6.0; coverage/ added to eslint ignores; dev-only MP API client vars documented in .env.example; tech-stack badges added to README to match MPNext-Tools.

Test plan

  • npm run test:run — 217/217 passing (12 fewer than before reflects deleted toolService.test.ts + user-tools-debug/actions.test.ts)
  • npm run lint — clean
  • Dev smoke: load / (no Template Tool card), /contactlookup/<guid> (Suspense fallback then details), sign-in/out, refresh contact logs
  • Verify no broken links in README/CLAUDE/components.md/testing.md

🤖 Generated with Claude Code

chriskehayias and others added 14 commits May 16, 2026 16:02
Replace useEffect/useState fetch with a memoized promise consumed via
React 19's use() hook. Drops isLoading/error from the context surface;
errors propagate to the nearest ErrorBoundary, loading to the nearest
Suspense fallback. Header no longer needs an isLoading branch and is
wrapped in Suspense to handle the pending state during initial load.

Tests updated to render through Suspense + ErrorBoundary instead of
asserting on isLoading/error fields.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Hoist getContactDetails / getContactLogsByContactId calls into the
server component as promises and pass them down. The client component
unwraps with use(), letting Suspense own the loading UI and the route
boundary own errors. Refresh now delegates to router.refresh() instead
of a local re-fetch, so server-side data stays the source of truth.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace useForm's watch() with a scoped useWatch on the contactLogType
field so only the Select that consumes it re-renders, instead of the
whole component re-rendering on every form value change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
useAppSession wraps authClient.useSession() directly; the empty
SessionContext was never provided or consumed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add MINISTRY_PLATFORM_DEV_CLIENT_ID / DEV_CLIENT_SECRET to the example
env with a warning that these are local-development only and must not
be deployed to production.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mirror the badge layout used in MPNext-Tools: reorder the status row
(Release / Tests / codecov) and add a second row of tech-stack badges
for Next.js, React, TypeScript, Tailwind, shadcn/ui, Radix UI, Lucide.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Calling the getCurrentUserProfile server action inside useMemo during
render triggered router cache invalidation while UserProvider was
rendering, producing "Cannot update a component (Router) while
rendering a different component" at runtime.

Move the fetch into useEffect, seeding userProfilePromise with a
resolved-null promise and replacing it once the effect runs. Drops
the now-unused useTransition; refreshUserProfile just bumps the
refresh key and lets the effect re-issue the request.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The template tool was a scaffold/demo and nothing else in the app
consumes its surrounding pieces. Remove the whole footprint:

- /tools/template route + the (web)/tools/ layout
- src/components/tool/ (ToolContainer, ToolHeader, ToolFooter, ToolParamsDebug)
- src/components/user-tools-debug/ + tests
- src/lib/tool-params.ts
- src/services/toolService.ts + tests
- the Template Tool card on the dashboard

Doc references in README.md, CLAUDE.md, and .claude/references/
{components,testing}.md updated to match.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Next 16 emits dev types under .next/dev/ instead of .next/.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
react-hooks/set-state-in-effect (new in eslint-plugin-react-hooks v6)
flagged the effect that seeds userProfilePromise. The pattern is
intentional: server actions trigger router cache invalidation, so the
fetch can't run during render — driving a Suspense-consumed promise
into state from useEffect is the React 19 escape hatch for this case.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
npm regenerated the lock on Windows without the Linux WASM optional
deps (@emnapi/runtime, @emnapi/core) used by
@tailwindcss/oxide-wasm32-wasi, breaking npm ci on CI runners.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Regenerated package-lock.json with --os=linux --cpu=x64 --libc=glibc
so the lock contains @emnapi/runtime and @emnapi/core entries that
@rolldown/binding-wasm32-wasi (a Linux-platform optional dep) needs.
Without these, npm ci on Linux runners refuses to install.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@codecov

codecov Bot commented May 16, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@chriskehayias chriskehayias merged commit 9f8aa38 into main May 16, 2026
2 checks passed
@chriskehayias chriskehayias deleted the refactor/react-19-suspense-and-housekeeping branch May 17, 2026 12:34
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