Skip to content

[codex] perf(components): drop zod from browser client form#54

Merged
Jesssullivan merged 1 commit intomainfrom
perf/clientform-no-zod
Apr 16, 2026
Merged

[codex] perf(components): drop zod from browser client form#54
Jesssullivan merged 1 commit intomainfrom
perf/clientform-no-zod

Conversation

@Jesssullivan
Copy link
Copy Markdown
Owner

What Changed

  • replaced zod-based browser validation in ClientForm.svelte with lightweight inline field checks
  • updated the pure logic ClientForm test file to match the new validation contract
  • bumped @tummycrypt/scheduling-kit release metadata from 0.7.0 to 0.7.1

Why

ClientForm.svelte is a browser component, but it was importing full zod runtime validation for only three fields: first name, last name, and email. In a clean MassageIthaca build, that pulled a vendor-zod client chunk of 113.87 kB into the booking graph.

A throwaway app-level proof build showed that removing the zod import from the browser ClientForm removes that vendor-zod chunk entirely from the client output.

Impact

  • reduces client bundle weight on booking surfaces that render ClientForm
  • keeps the same user-visible validation messages for required names and email format
  • does not change public package APIs

Validation

  • pnpm exec vitest run src/tests/components/client-form.test.ts
  • pnpm exec vitest run --config vitest.component.config.ts tests/e2e/client-form.test.ts
  • pnpm run check:release-metadata
  • pnpm build

Refs #44

@Jesssullivan Jesssullivan marked this pull request as ready for review April 16, 2026 15:09
@Jesssullivan Jesssullivan merged commit 9b23a35 into main Apr 16, 2026
3 checks passed
@Jesssullivan Jesssullivan deleted the perf/clientform-no-zod branch April 16, 2026 15:09
@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Apr 16, 2026

Greptile Summary

This PR removes the zod runtime import from ClientForm.svelte, replacing three-field schema validation with lightweight inline regex/trim checks, and bumps the package version from 0.7.0 to 0.7.1 across package.json, BUILD.bazel, and MODULE.bazel. Zod is retained as a production dependency for server-side core modules (pipelines.ts, utils.ts) where tree-shaking prevents it from entering the browser bundle.

Confidence Score: 5/5

Safe to merge — the inline validation is functionally equivalent for all reachable UI states, and zod is correctly retained only for server-side code.

All findings are P2: a test/component divergence in intake radio strictness and mismatched error-message strings in the test helper. Neither affects runtime behavior reachable through the UI. No security issues, no breaking API changes, and the bundle-size goal is achieved.

src/tests/components/client-form.test.ts — the re-implemented validateIntake helper tests stricter invariants than the component enforces and uses different error message strings.

Important Files Changed

Filename Overview
src/components/ClientForm.svelte Replaces zod schema validation with inline trim/regex checks; logic is correct and equivalent for the UI's constrained input paths.
src/tests/components/client-form.test.ts Test re-implements the component's pure logic, but the inline validateIntake uses a stricter radio check (!== 'yes' && !== 'no') than the component (!painRadio), and the error messages don't match the component's user-visible strings.
package.json Version bumped to 0.7.1; zod remains a production dependency (expected, used by server-side core modules).
BUILD.bazel Version field in the npm_package target updated to 0.7.1; no functional build changes.
MODULE.bazel Module version bumped to 0.7.1; no other changes.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Form Submit] --> B[validate]
    B --> C{firstName.trim empty?}
    C -- yes --> D[errors.firstName = 'First name is required']
    C -- no --> E{lastName.trim empty?}
    D --> E
    E -- yes --> F[errors.lastName = 'Last name is required']
    E -- no --> G{email empty or regex fails?}
    F --> G
    G -- yes --> H[errors.email = 'Please enter a valid email address']
    G -- no --> I{showIntakeFields?}
    H --> I
    I -- yes --> J[Validate painRadio / clenching / headaches / howDidYouHear / medication / termsAccepted]
    J --> K{errors empty?}
    I -- no --> K
    K -- no --> L[Return early — show inline errors]
    K -- yes --> M[Build ClientInfo]
    M --> N[onSubmit callback]
Loading

Comments Outside Diff (2)

  1. src/tests/components/client-form.test.ts, line 61-68 (link)

    P2 Test validateIntake is stricter than the component

    The test's radio check (fields.painRadio !== 'yes' && fields.painRadio !== 'no') rejects any non-yes/no string (e.g. 'maybe'), but the component's actual validate() only tests falsiness (!painRadio), which would accept any truthy value. For the current UI this doesn't matter — the radio inputs can only produce '', 'yes', or 'no' — but the tests are asserting stronger invariants than the component enforces, which can mislead future contributors about the real validation contract.

    Consider aligning one of them: either tighten the component check to painRadio !== 'yes' && painRadio !== 'no', or loosen the test to match the !painRadio truthiness check.

  2. src/tests/components/client-form.test.ts, line 59-80 (link)

    P2 Test error messages don't match user-visible component strings

    The validateIntake helper returns strings like 'Pain question is required' and 'You must accept the terms', but the component emits 'Please answer this question' and 'You must accept the terms to continue'. Because this test file re-implements the logic rather than exercising the component directly, none of the user-visible intake error messages are actually covered by tests. If the wording changes in the component, the tests stay green regardless.

Reviews (1): Last reviewed commit: "perf(components): drop zod from browser ..." | Re-trigger Greptile

Jesssullivan added a commit to tinyland-inc/scheduling-kit that referenced this pull request Apr 16, 2026
* refactor!: remove middleware code (belongs in acuity-middleware) (#10)

Removed: src/middleware/ (33 files), modal-app.py, Dockerfile,
live tests, playwright deps. Version 0.3.1 to 0.4.0.

* chore: bump version to 0.5.0

* refactor!: remove acuity-scraper adapter

Scraper belongs in acuity-middleware, not the scheduling library.
Deprecated since extract-business.ts + middleware wizard steps
replaced all scraper functionality.

BREAKING: AcuityScraper, createScraperAdapter, scrapeServicesOnce,
scrapeAvailabilityOnce removed from @tummycrypt/scheduling-kit/adapters.

* build: add Bazel 8 configuration with subpackage targets

- MODULE.bazel: bzlmod config with rules_js 2.9.1, rules_ts 3.8.4, SWC, pnpm 9
- BUILD.bazel: svelte-package build, npm_package, 6 subpackage ts_project
  targets (core, adapters, payments, reconciliation, lib, testing), vitest,
  svelte-check typecheck
- .bazelrc: build/CI/debug/release configs with disk cache
- .bazelversion: pin to 8.1.1
- .npmrc: hoist=false (required by rules_js)

* feat: v0.5.0 - remove acuity-scraper, add Bazel 8 config (#11)

* chore: bump version to 0.5.0

* refactor!: remove acuity-scraper adapter

Scraper belongs in acuity-middleware, not the scheduling library.
Deprecated since extract-business.ts + middleware wizard steps
replaced all scraper functionality.

BREAKING: AcuityScraper, createScraperAdapter, scrapeServicesOnce,
scrapeAvailabilityOnce removed from @tummycrypt/scheduling-kit/adapters.

* build: add Bazel 8 configuration with subpackage targets

- MODULE.bazel: bzlmod config with rules_js 2.9.1, rules_ts 3.8.4, SWC, pnpm 9
- BUILD.bazel: svelte-package build, npm_package, 6 subpackage ts_project
  targets (core, adapters, payments, reconciliation, lib, testing), vitest,
  svelte-check typecheck
- .bazelrc: build/CI/debug/release configs with disk cache
- .bazelversion: pin to 8.1.1
- .npmrc: hoist=false (required by rules_js)

* feat(venmo): add payeeEmail option to route payments to practitioner

When payeeEmail is set in VenmoAdapterConfig, the PayPal order
creation includes payee.email_address in purchase_units. This
routes payments directly to the practitioner's PayPal account
without requiring their API credentials.

Ref: PayPal "Pay another account" docs

* chore: bump version to 0.5.1 (payee-email support)

* fix(ci): use @Jesssullivan scope for GitHub Packages mirror (Jesssullivan#18)

* feat(venmo): add returnUrl/cancelUrl to experience_context (Jesssullivan#19)

* fix(ci): use @Jesssullivan scope for GitHub Packages mirror

* feat(venmo): add returnUrl/cancelUrl to experience_context

PayPal requires return_url and cancel_url in the Venmo payment source
experience_context for proper popup handling. Without them, PayPal may
force additional buyer verification loops or block the popup flow.

New optional fields on VenmoAdapterConfig: returnUrl, cancelUrl.

* chore: bump to 0.5.2 (PayPal return URLs) (Jesssullivan#20)

* fix(ci): use @Jesssullivan scope for GitHub Packages mirror

* feat(venmo): add returnUrl/cancelUrl to experience_context

PayPal requires return_url and cancel_url in the Venmo payment source
experience_context for proper popup handling. Without them, PayPal may
force additional buyer verification loops or block the popup flow.

New optional fields on VenmoAdapterConfig: returnUrl, cancelUrl.

* chore: bump to 0.5.2 (PayPal return URLs)

* feat: onboarding subpackage — provider credential management (Jesssullivan#21-Jesssullivan#27) (Jesssullivan#28)

New @tummycrypt/scheduling-kit/onboarding subpackage:

Interfaces:
- CredentialStore: app-provided key-value storage (PG, Redis, etc.)
- EncryptionProvider: app-provided encryption (AES, Vault, etc.)
- StripeConnectConfig, StripeAccountStatus, WebhookSetupResult types

Stripe:
- buildStripeAuthorizeUrl() + exchangeStripeCode() — Connect OAuth
- getStripeAccountStatus() — account onboarding status
- validateStripeKeys() — key validation against Stripe API
- createStripeWebhook() + deleteStripeWebhooks() — webhook CRUD

PayPal:
- validatePayPalCredentials() — OAuth token validation
- createPayPalWebhook() — webhook creation

Build:
- Bazel //src/onboarding target (deps: :core, :payments, effect)
- Package.json ./onboarding export

Pattern: library defines interfaces + helpers, application provides
CredentialStore implementation. Same pattern as HomegrownAdapter's
getDb callback — scheduling-kit doesn't know about databases.

Closes Jesssullivan#21, Jesssullivan#22, Jesssullivan#23, Jesssullivan#24, Jesssullivan#27. Partial Jesssullivan#25, Jesssullivan#26.

* chore: bump to 0.6.0 (onboarding subpackage) (Jesssullivan#29)

* feat: adapter factory pattern + 21 onboarding tests (Jesssullivan#25, Jesssullivan#26) (Jesssullivan#30)

- createAdapterFactory(): settings-driven singleton with cache,
  promise dedup, reset, and disable lifecycle
- 21 tests: Stripe OAuth URL, key validation, account status,
  PayPal credential validation, factory lifecycle (cache, reset,
  disable, store passthrough)
- Updated vitest.config.ts to include onboarding test glob

Closes Jesssullivan#25, Jesssullivan#26.

* chore: strip sourcemaps from npm package (Jesssullivan#31)

* feat: adapter factory pattern + 21 onboarding tests (Jesssullivan#25, Jesssullivan#26)

- createAdapterFactory(): settings-driven singleton with cache,
  promise dedup, reset, and disable lifecycle
- 21 tests: Stripe OAuth URL, key validation, account status,
  PayPal credential validation, factory lifecycle (cache, reset,
  disable, store passthrough)
- Updated vitest.config.ts to include onboarding test glob

Closes Jesssullivan#25, Jesssullivan#26.

* chore: strip sourcemaps from npm package (2,711 .map files excluded)

* feat: provider status helpers + SetupStep type (Jesssullivan#32) (Jesssullivan#33)

* chore: bump to 0.6.1 (status helpers) (Jesssullivan#34)

* align build truth and package boundaries (Jesssullivan#39)

* ci: enforce Bazel release metadata truth (Jesssullivan#40)

* docs: add agent and llm operating brief (Jesssullivan#42)

* feat(payments)!: converge PaymentCapabilities contract from tinyland-inc (Jesssullivan#45)

* feat(payments)!: converge PaymentCapabilities contract from tinyland-inc

Cherry-pick tinyland-inc/main squash (v0.7.0) onto Jesssullivan/main.
Keeps Jess's CI/publish workflows and Bazel structure.
Bumps all version references to 0.7.0.

- PaymentCapabilities, StripeCapability, VenmoCapability types
- getDefaultCapabilities() factory
- HybridCheckoutDrawer: capabilities prop replaces individual payment props
- Cash at Visit structurally removed (cash: false)

* fix(ci): skip prepublish scripts in gh packages mirror

* docs(release): clarify scheduling-kit authority (Jesssullivan#46)

* ci(publish): validate bazel package artifact (Jesssullivan#47)

* build(bazel): publish scheduling-kit from bazel artifact

* fix(ui): dark-mode skeleton shimmer and border parity (Jesssullivan#49)

Replace hardcoded hex CSS with light-dark() for 8 components:
skeleton loading shimmer, border colors, scrollbar tracks.
Ensures proper dark-mode rendering when consumed by host apps.

* ci: support honey self-hosted runner stopgap (Jesssullivan#50)

* ci: isolate pnpm store on self-hosted runners (Jesssullivan#51)

* fix: make publish workflow self-hosted-safe (Jesssullivan#52)

* fix(ci): make github package artifact writable (Jesssullivan#53)

* perf(components): drop zod from browser client form (Jesssullivan#54)

* fix(ci): ignore npm scripts when publishing bazel pkg (Jesssullivan#55)

* fix(ci): ignore npm scripts when publishing bazel pkg

* fix(ci): clean stale bazel publish artifacts on runners

* fix(ci): partition pnpm caches by runner (Jesssullivan#56)
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