From 53338caa85c4ea726af11a311ba18e7110826312 Mon Sep 17 00:00:00 2001 From: Rob Simmons Date: Thu, 6 Aug 2026 11:52:42 -0400 Subject: [PATCH 1/7] refactor: add and use error boundaries apply pattern in a few more cases more agent commentary --- AGENTS.md | 21 +++++++- doc/DEVELOPMENT.md | 2 +- src/app/[userName]/NewProjectForm.tsx | 11 ++-- src/app/[userName]/[projectName]/page.tsx | 10 +--- src/app/[userName]/actions.ts | 31 ++++++----- src/app/admin/actions.ts | 3 +- src/app/admin/components/OAuthConfig.tsx | 4 +- .../components/{Error.tsx => ErrorBox.tsx} | 0 src/app/error.tsx | 52 +++++++++++++++++++ src/app/global-error.tsx | 21 ++++++++ src/app/page.tsx | 14 ++--- src/app/setup/page.tsx | 38 ++++++++++---- 12 files changed, 157 insertions(+), 50 deletions(-) rename src/app/components/{Error.tsx => ErrorBox.tsx} (100%) create mode 100644 src/app/error.tsx create mode 100644 src/app/global-error.tsx diff --git a/AGENTS.md b/AGENTS.md index 4bc66445..25b020a8 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -22,10 +22,27 @@ For example, prefer one state of type `'loading' | { error: E } | { result: T }` to three states `[loading, setLoading] = useState(); [error, setError] = useState(); [result, setResult] = useState()`. - For UI actions that hit the server, -prefer `useServerAction` (`@/lib/util`) or (if `useServerAction` doesn't work) `useActionState` +prefer `useServerAction` (`@/lib/client/util`) or (if `useServerAction` doesn't work) `useActionState` over manually storing response/error state with `useState`. - To call Server Functions on mount (e.g. to fetch data), use SWR. - + +# Error handling + +- Next.js interrupts are preferred when there is something appropriate available (e.g. authentication errors, forbidden()) +- ActionResponse { error } states are only for failures the user is expected to be able to encounter during usual operation, + and is expected to be able to act on (e.g. "A project with that name already exists"). +- Everything else (bugs, unreachable services, misconfigured hosts) should throw to an error boundary. +- Errors thrown within the app should not be converted to ActionResponse errors, and vice versa. + +Some details/consequences: + +- Only render-phase throws hit error boundaries; + avoid throwing in callbacks, event handlers, and timeouts that will be silently ignored. +- SWR puts a fetcher's throw in `error`; + errors from SWR should generally be re-raised inside of the React render function. + Sometimes SWR errors should be shown to the user instead, + but they should not be suppressed or ignored. + # Agent instructions - Less code is better. After writing any new piece of code, diff --git a/doc/DEVELOPMENT.md b/doc/DEVELOPMENT.md index 5a7a6ce9..d88509d6 100644 --- a/doc/DEVELOPMENT.md +++ b/doc/DEVELOPMENT.md @@ -8,7 +8,7 @@ It describes how to locally run and test the workbench software. - Docker installed and running, with at least 16GB memory allocated (in Docker Desktop, go to Settings -> Resources -> Memory). -- Node v24 or later is needed for `make` to work +- Node v24 or later is needed for `make container` to work ## Running the workbench server diff --git a/src/app/[userName]/NewProjectForm.tsx b/src/app/[userName]/NewProjectForm.tsx index 50f6fbd8..bb660326 100644 --- a/src/app/[userName]/NewProjectForm.tsx +++ b/src/app/[userName]/NewProjectForm.tsx @@ -16,11 +16,8 @@ export function NewProjectForm() { data: templates, error: templatesError, isLoading: templatesPending, - } = useSWR('listTemplates', async () => { - const result = await listTemplates() - if ('error' in result) throw new Error(result.error) - return result.ok - }) + } = useSWR('listTemplates', listTemplates) + if (templatesError) throw templatesError const [chosenTemplate, setChosenTemplate] = useState('blank') @@ -46,8 +43,6 @@ export function NewProjectForm() { ) } - const error = templatesError ?? createError - return (
))} - {error &&
{error}
} + {createError &&
{createError}
}