Skip to content

Add unified SvelteKit config wrapper and validation fixes - #50

Merged
cobraprojects merged 4 commits into
mainfrom
codex/validation-exception-flow
May 27, 2026
Merged

Add unified SvelteKit config wrapper and validation fixes#50
cobraprojects merged 4 commits into
mainfrom
codex/validation-exception-flow

Conversation

@cobraprojects

@cobraprojects cobraprojects commented May 27, 2026

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

  • New Features

    • Validation errors now surface via a client hook and persist across redirects using a flashed cookie; forms can restore flashed field values.
    • Native browser form submits are intercepted and routed through standardized client submitters; adapters include new client submit integrations.
    • Admin post image create+update flows improved in tests and UI.
  • Refactor

    • SvelteKit config is wrapped with a managed wrapper for hooks/preprocess wiring; Svelte preprocessor adds useForm reactivity support.
    • Validation exception serialization gains a compact digest and safer cookie encoding.
  • Documentation

    • Docs updated to use validate(...) + ValidationException patterns and show adapter integration.

Review Change Stack

@coderabbitai

coderabbitai Bot commented May 27, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@cobraprojects, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 25 minutes and 13 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 65259961-4d4a-4c79-a190-fd0a37c7c579

📥 Commits

Reviewing files that changed from the base of the PR and between 47a08b6 and d75a08f.

📒 Files selected for processing (3)
  • apps/blog-nuxt/tests/run.mjs
  • packages/security/src/drivers/file.ts
  • packages/security/tests/package.test.ts
📝 Walkthrough

Walkthrough

This PR implements flash-cookie persistence for SvelteKit form validation failures, enabling error state to survive 303 redirects without raw JSON responses. It adds a config wrapper to manage hook paths, introduces a preprocessor for useForm reactivity, extends forms client to normalize SvelteKit action results with digest-encoded validation exceptions, and updates validation serialization with human-readable messages and compact payload encoding.

Changes

SvelteKit Validation Flash & Form Integration

Layer / File(s) Summary
Validation exception digest foundation
packages/validation/src/contracts.ts, packages/validation/src/contracts-runtime.ts, packages/validation/src/contracts-support.ts, packages/validation/tests/contracts.test.ts
ValidationException now includes a computed digest string that compactly encodes errors, status, and values; serialization strips non-serializable fields; file size and date error messages now use human-readable formatting (e.g., "X bytes or smaller", "This field must be …").
SvelteKit config wrapper and useForm preprocessor
packages/adapter-sveltekit/src/config.ts, packages/adapter-sveltekit/src/preprocess.ts, packages/adapter-sveltekit/tests/config.test.ts, packages/adapter-sveltekit/tests/preprocess.test.ts, packages/adapter-sveltekit/tsup.config.ts, packages/adapter-sveltekit/package.json
withHoloSvelteKit config wrapper validates and injects managed hook paths, rejects custom overrides; holoSveltePreprocess transforms useForm const declarations to let with injected .subscribe() side-effects for reactivity; config entrypoint exported via package.json subpath.
SvelteKit client flash cookie and form handling
packages/adapter-sveltekit/src/client.ts, packages/adapter-sveltekit/tests/client.test.ts
Client reads/writes flash validation cookies, registers native POST forms for submission interception, installs capture-phase listeners to route submissions, implements submitSvelteKitAction using fetch with x-sveltekit-action header, integrates flashed state into useForm initialization and new useValidationErrors hook.
Forms client SvelteKit integration
packages/forms/src/internal/client.ts, packages/forms/src/internal/validation-exception.ts, packages/forms/tests/client.test.ts, packages/forms/tests/contracts.test.ts
Forms client normalizes SvelteKit action results ({ type: 'failure', status, data }), parses JSON failure payloads, tracks active browser form via runWithBrowserFormElement, extracts form data from active element, converts digest-encoded exceptions into field-level failures via fallback digest parsing.
SvelteKit adapter validation response mapping
packages/adapter-sveltekit/src/index.ts, packages/adapter-sveltekit/tests/runtime.test.ts, packages/auth/src/runtime/result.ts, packages/auth/tests/package.test.ts
mapValidationActionResponse conditionally returns flash-cookie-enabled JSON responses or 303 redirects with set-cookie headers; cookie encoding, path sanitization, and encoding helpers added; validation exception thrower skips non-API events and remembers flash payloads; auth result handler routes through throwValidationException.
CLI config patching for SvelteKit wrapper
packages/cli/src/project/registry-svelte.ts, packages/cli/src/project/scaffold/framework-renderers.ts, packages/cli/tests/cli.test.ts
CLI scaffolding wraps SvelteKit config with withHoloSvelteKit, removes legacy hook paths, rejects custom hook overrides during patch, strips old preprocess wiring, validates generated server hooks use isApiEvent check.
Blog app SvelteKit pages and tests
apps/blog-sveltekit/src/routes/admin/posts/[id]/edit/+page.svelte, apps/blog-sveltekit/src/routes/admin/posts/new/+page.svelte, apps/blog-sveltekit/svelte.config.js, apps/blog-sveltekit/tests/run.mjs, apps/blog-next/tests/run.mjs
Blog SvelteKit pages use useValidationErrors hook instead of form.errors; config wrapped with withHoloSvelteKit; tests add database helpers for post lookup/author assignment; image upload tests validate flash cookie behavior and redirect targets.

Nuxt Server Auto-Imports Reorganization

Layer / File(s) Summary
Nuxt auto-imports directory structure
packages/adapter-nuxt/src/module.ts, packages/adapter-nuxt/src/runtime/server/auto-imports/holo.ts, packages/adapter-nuxt/tests/module.test.ts, packages/adapter-nuxt/tests/serverUtils.test.ts, packages/adapter-nuxt/tests/setup.test.ts
Moves Nuxt holo server auto-import re-exports from /imports to /auto-imports directory; registers new path via addServerImportsDir; updates all test assertions to reference new location.
Broadcast auth validation message update
packages/broadcast/tests/auth.test.ts
Boolean field validation error message updated from generic "Expected boolean" to field-specific "editing: This field must be true or false."

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • cobraprojects/holo-js#30: Related changes to packages/adapter-sveltekit/src/client.ts touching useForm construction and client integration.

"Dear rabbit, I nibble at code’s bright root,
Flashed errors tucked in cookies suit.
Forms wake with values, gentle and clear,
Digests hum softly, errors disappear.
Hops of joy — tests green, ready to boot!" 🐇✨

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/validation-exception-flow

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🧹 Nitpick comments (2)
packages/forms/src/internal/client.ts (1)

518-530: ⚖️ Poor tradeoff

Potential race condition in concurrent form submissions.

If two forms submit concurrently, the activeBrowserForm module-level state could be overwritten. The guard at line 526-528 only prevents clearing the wrong form but doesn't prevent a second submission from seeing the first form's context. Consider using AsyncLocalStorage pattern or passing form context explicitly through the call chain.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/forms/src/internal/client.ts` around lines 518 - 530, The current
runWithBrowserFormElement uses module-level activeBrowserForm which allows races
when two forms submit concurrently; replace this pattern with an
AsyncLocalStorage-based context or explicit context passing: create an
AsyncLocalStorage<BrowserFormElement> (or similar) and change
runWithBrowserFormElement to call storage.run(form, callback) instead of setting
activeBrowserForm, then update all places that read activeBrowserForm to use
storage.getStore() (or accept a form argument) and remove the module-level
activeBrowserForm; ensure the new storage is exported to consumers so concurrent
submissions get isolated contexts.
packages/adapter-sveltekit/src/client.ts (1)

102-104: 💤 Low value

Consider cleanup for registeredForms on component unmount.

Forms added to registeredForms are never removed. In SPA navigation scenarios where form components mount/unmount repeatedly, the Set could grow unboundedly. Consider returning an unregister function from registerForm that useForm can call during cleanup.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/adapter-sveltekit/src/client.ts` around lines 102 - 104,
registeredForms is never cleaned up which can leak entries; modify registerForm
(the function that currently adds a RegisteredForm to the registeredForms Set)
to return an unregister function that removes that RegisteredForm from the Set
(and tears down any per-form listeners/refs if applicable), then update useForm
to call this unregister function during component unmount/cleanup; ensure the
unregister removes only the exact RegisteredForm instance and guards against
double-unregistering.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/adapter-sveltekit/src/client.ts`:
- Around line 499-501: When handling the branch where result.type === 'failure'
in the function that returns ClientSubmitResult<TData, TSuccess>, avoid
returning undefined when parseJsonObject(result.data) yields undefined; instead,
call parseJsonObject(result.data) into a local variable, and if it's undefined
return a well-formed failure result (e.g., a ClientSubmitResult with type:
'failure' and a transport/error field indicating invalid JSON or a parsing
error) or re-throw a descriptive error; update the code paths around
parseJsonObject, result, and the ClientSubmitResult return so callers always
receive a valid result object (reference parseJsonObject and the failure branch
handling of result.type === 'failure').

---

Nitpick comments:
In `@packages/adapter-sveltekit/src/client.ts`:
- Around line 102-104: registeredForms is never cleaned up which can leak
entries; modify registerForm (the function that currently adds a RegisteredForm
to the registeredForms Set) to return an unregister function that removes that
RegisteredForm from the Set (and tears down any per-form listeners/refs if
applicable), then update useForm to call this unregister function during
component unmount/cleanup; ensure the unregister removes only the exact
RegisteredForm instance and guards against double-unregistering.

In `@packages/forms/src/internal/client.ts`:
- Around line 518-530: The current runWithBrowserFormElement uses module-level
activeBrowserForm which allows races when two forms submit concurrently; replace
this pattern with an AsyncLocalStorage-based context or explicit context
passing: create an AsyncLocalStorage<BrowserFormElement> (or similar) and change
runWithBrowserFormElement to call storage.run(form, callback) instead of setting
activeBrowserForm, then update all places that read activeBrowserForm to use
storage.getStore() (or accept a form argument) and remove the module-level
activeBrowserForm; ensure the new storage is exported to consumers so concurrent
submissions get isolated contexts.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3d9cee94-7e55-4820-b56d-7a81ca3f6365

📥 Commits

Reviewing files that changed from the base of the PR and between 0eeea90 and 218dc81.

📒 Files selected for processing (35)
  • apps/blog-next/tests/run.mjs
  • apps/blog-sveltekit/src/routes/admin/posts/[id]/edit/+page.svelte
  • apps/blog-sveltekit/src/routes/admin/posts/new/+page.svelte
  • apps/blog-sveltekit/svelte.config.js
  • apps/blog-sveltekit/tests/run.mjs
  • packages/adapter-nuxt/src/module.ts
  • packages/adapter-nuxt/src/runtime/server/auto-imports/holo.ts
  • packages/adapter-nuxt/tests/module.test.ts
  • packages/adapter-nuxt/tests/serverUtils.test.ts
  • packages/adapter-nuxt/tests/setup.test.ts
  • packages/adapter-sveltekit/package.json
  • packages/adapter-sveltekit/src/client.ts
  • packages/adapter-sveltekit/src/config.ts
  • packages/adapter-sveltekit/src/index.ts
  • packages/adapter-sveltekit/src/preprocess.ts
  • packages/adapter-sveltekit/tests/client.test.ts
  • packages/adapter-sveltekit/tests/config.test.ts
  • packages/adapter-sveltekit/tests/package.test.ts
  • packages/adapter-sveltekit/tests/preprocess.test.ts
  • packages/adapter-sveltekit/tests/runtime.test.ts
  • packages/adapter-sveltekit/tsup.config.ts
  • packages/auth/src/runtime/result.ts
  • packages/auth/tests/package.test.ts
  • packages/broadcast/tests/auth.test.ts
  • packages/cli/src/project/registry-svelte.ts
  • packages/cli/src/project/scaffold/framework-renderers.ts
  • packages/cli/tests/cli.test.ts
  • packages/forms/src/internal/client.ts
  • packages/forms/src/internal/validation-exception.ts
  • packages/forms/tests/client.test.ts
  • packages/forms/tests/contracts.test.ts
  • packages/validation/src/contracts-runtime.ts
  • packages/validation/src/contracts-support.ts
  • packages/validation/src/contracts.ts
  • packages/validation/tests/contracts.test.ts

Comment thread packages/adapter-sveltekit/src/client.ts

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🧹 Nitpick comments (2)
packages/adapter-sveltekit/tests/runtime.test.ts (2)

480-482: ⚡ Quick win

Escape the cookie name in the RegExp constructor.

Constructing a regular expression from a variable without escaping can be vulnerable to ReDoS if the variable contains regex metacharacters. While the cookie name is likely controlled in this test context, using string methods is safer and sets a better pattern.

🛡️ Proposed fix using string methods instead of regex
-    const flashedCookieValue = mapped.headers.get('set-cookie')
-      ?.match(new RegExp(`${adapterSvelteKitInternals.validationFlashCookie}=([^;]+)`))?.[1]
+    const setCookieHeader = mapped.headers.get('set-cookie') ?? ''
+    const cookiePrefix = `${adapterSvelteKitInternals.validationFlashCookie}=`
+    const startIndex = setCookieHeader.indexOf(cookiePrefix)
+    const flashedCookieValue = startIndex !== -1
+      ? setCookieHeader.slice(startIndex + cookiePrefix.length).split(';')[0]
+      : undefined
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/adapter-sveltekit/tests/runtime.test.ts` around lines 480 - 482, The
test builds a RegExp from adapterSvelteKitInternals.validationFlashCookie which
can mis-handle regex metacharacters; replace the RegExp-based extraction with a
safe string-based parse: retrieve mapped.headers.get('set-cookie'), split on ';'
or search for the cookie name using indexOf/startsWith, and extract the value by
slicing after `${adapterSvelteKitInternals.validationFlashCookie}=`; update the
reference in the test where flashedCookieValue is computed (the
mapped.headers.get and adapterSvelteKitInternals.validationFlashCookie usage) so
no RegExp constructor is used.

483-483: 💤 Low value

The '{}' fallback may be redundant after the .toBeDefined() assertion.

Line 482 already asserts that flashedCookieValue is defined, so the ?? '{}' fallback on line 483 should never execute in a passing test. If you want to maintain defensive coding, this is fine, but it could also mask unexpected failures.

♻️ Optional simplification
 expect(flashedCookieValue).toBeDefined()
-expect(JSON.parse(decodeURIComponent(flashedCookieValue ?? '{}'))).toMatchObject({
+expect(JSON.parse(decodeURIComponent(flashedCookieValue!))).toMatchObject({
   values: {
     email: 'user@app.test',
   },
 })
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/adapter-sveltekit/tests/runtime.test.ts` at line 483, Remove the
redundant "?? '{}'" fallback on the JSON.parse call since the previous
expect(flashedCookieValue).toBeDefined() guarantees it's not null/undefined;
update the assertion to use JSON.parse(decodeURIComponent(flashedCookieValue))
(or keep a defensive check if you prefer) so the test fails when
flashedCookieValue is unexpectedly missing; key symbols: flashedCookieValue and
the expect(JSON.parse(decodeURIComponent(...))).toMatchObject(...) assertion.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/docs/docs/validation/index.md`:
- Line 36: The documentation has a contradiction about safeParse(...): remove
safeParse(...) from the “does not own” list and ensure it only appears in the
“owns” list for `@holo-js/validation`; update the bullet lists so that
safeParse(...) is listed alongside submission.fail() and submission.success() as
owned by `@holo-js/validation` and no longer appears in the example of server
helpers not owned by that package.

---

Nitpick comments:
In `@packages/adapter-sveltekit/tests/runtime.test.ts`:
- Around line 480-482: The test builds a RegExp from
adapterSvelteKitInternals.validationFlashCookie which can mis-handle regex
metacharacters; replace the RegExp-based extraction with a safe string-based
parse: retrieve mapped.headers.get('set-cookie'), split on ';' or search for the
cookie name using indexOf/startsWith, and extract the value by slicing after
`${adapterSvelteKitInternals.validationFlashCookie}=`; update the reference in
the test where flashedCookieValue is computed (the mapped.headers.get and
adapterSvelteKitInternals.validationFlashCookie usage) so no RegExp constructor
is used.
- Line 483: Remove the redundant "?? '{}'" fallback on the JSON.parse call since
the previous expect(flashedCookieValue).toBeDefined() guarantees it's not
null/undefined; update the assertion to use
JSON.parse(decodeURIComponent(flashedCookieValue)) (or keep a defensive check if
you prefer) so the test fails when flashedCookieValue is unexpectedly missing;
key symbols: flashedCookieValue and the
expect(JSON.parse(decodeURIComponent(...))).toMatchObject(...) assertion.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3ce957b8-5cb3-4300-8ab3-0b014c26ca48

📥 Commits

Reviewing files that changed from the base of the PR and between 218dc81 and ec15139.

📒 Files selected for processing (23)
  • apps/blog-nuxt/app/pages/admin/posts/[id]/edit.vue
  • apps/blog-nuxt/app/pages/admin/posts/new.vue
  • apps/docs/docs/auth/current-auth-client.md
  • apps/docs/docs/auth/email-verification.md
  • apps/docs/docs/auth/index.md
  • apps/docs/docs/auth/local-auth.md
  • apps/docs/docs/forms/client-usage.md
  • apps/docs/docs/forms/framework-integration.md
  • apps/docs/docs/forms/index.md
  • apps/docs/docs/forms/server-validation.md
  • apps/docs/docs/installation.md
  • apps/docs/docs/media.md
  • apps/docs/docs/security.md
  • apps/docs/docs/validation/index.md
  • apps/docs/docs/validation/rules-and-errors.md
  • packages/adapter-sveltekit/src/client.ts
  • packages/adapter-sveltekit/src/index.ts
  • packages/adapter-sveltekit/tests/client.test.ts
  • packages/adapter-sveltekit/tests/runtime.test.ts
  • packages/cli/src/project/scaffold/framework-renderers.ts
  • packages/cli/tests/cli.test.ts
  • packages/forms/src/internal/client.ts
  • packages/forms/tests/client.test.ts
✅ Files skipped from review due to trivial changes (2)
  • apps/docs/docs/installation.md
  • apps/docs/docs/validation/rules-and-errors.md
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/adapter-sveltekit/src/index.ts
  • packages/adapter-sveltekit/src/client.ts
  • packages/cli/tests/cli.test.ts

Comment thread apps/docs/docs/validation/index.md Outdated
@cobraprojects
cobraprojects merged commit f2f20a4 into main May 27, 2026
1 check passed
@cobraprojects
cobraprojects deleted the codex/validation-exception-flow branch May 27, 2026 11:13
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