React 19 Suspense refactor + template tool removal#60
Merged
chriskehayias merged 14 commits intoMay 16, 2026
Conversation
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 Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
UserProvidernow exposes aPromise<MPUserProfile | null>consumed viause(); errors flow to ErrorBoundary, loading to Suspense. DropsisLoading/errorfrom the context surface. Fix included for a setState-in-render that occurred when the underlying server action triggered router invalidation during render./contactlookup/[guid]initiatesgetContactDetailsandgetContactLogsByContactIdin the server component and passes promises down; the client unwraps withuse()and refreshes viarouter.refresh().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.contact-logsswitched touseWatchfor scoped re-renders;session-contextorphancreateContextremoved; 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 deletedtoolService.test.ts+user-tools-debug/actions.test.ts)npm run lint— clean/(no Template Tool card),/contactlookup/<guid>(Suspense fallback then details), sign-in/out, refresh contact logs🤖 Generated with Claude Code