Skip to content

docs: add design whys document#4

Merged
viniciusdacal merged 2 commits intomainfrom
docs/design-whys
Feb 4, 2026
Merged

docs: add design whys document#4
viniciusdacal merged 2 commits intomainfrom
docs/design-whys

Conversation

@viniciusdacal
Copy link
Copy Markdown
Contributor

Summary

  • Adds plans/vertz-design-whys.md — a document that answers the natural "why?" questions developers ask when reading Vertz code and conventions
  • Each entry traces a specific design choice back to the manifesto's beliefs
  • Covers: functions over decorators, middleware returns, deps vs ctx, immutability, generics, builder vs compound, response schemas, mock by reference, test app mirroring, natural speech naming, schema files, flat modules

Relationship to other docs

  • Manifesto declares what we believe
  • Design Whys explains why the code looks the way it does
  • Core API Design shows how the API works
  • Testing Design shows how testing works

Test plan

  • Review each "Why?" entry for accuracy against current API design
  • Verify no overlap with manifesto (beliefs stay there, reasoning stays here)

🤖 Generated with Claude Code

Answers the natural "why?" questions developers ask when reading Vertz
code and conventions. Each entry traces a specific design choice back to
the manifesto's beliefs — no restated principles, just reasoning.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Not a single file with all schemas, and not inline in the router. Separate files because:
- An LLM generating a new endpoint creates one file — clear scope
- Code review shows exactly what changed per endpoint
- No merge conflicts from multiple people editing the same schema file
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's also because we would need to split schema files when it gets big enough, which often happens, then we would have two patterns of organizing schemas, and we want to avoid ambiguity

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added as the first bullet point — a single schema file inevitably grows large enough to split, which creates two organizational patterns. Starting with one file per endpoint avoids that fork entirely. aaddb6a

viniciusdacal added a commit that referenced this pull request Feb 4, 2026
Moved to its own PR (#4) as vertz-design-whys.md.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
A single schema file inevitably grows large enough to need splitting,
which creates two organizational patterns. Starting with one file per
endpoint avoids that fork entirely.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@viniciusdacal viniciusdacal self-assigned this Feb 4, 2026
@viniciusdacal viniciusdacal merged commit 69b834c into main Feb 4, 2026
viniciusdacal added a commit that referenced this pull request Feb 5, 2026
- #4: Define BootSequence contract — ServiceFactory type, example compiler
  output showing JS module with live imports (not JSON manifest)
- #5: Document HEAD (auto-generated from GET) and OPTIONS (handled by CORS)
- #6: Explicitly state no AsyncLocalStorage — services receive request data
  via function arguments only
- #7: Add reserved ctx property names with runtime guard in dev mode
- #8: Clarify testing lives in @vertz/core, packages/testing/ superseded
- #9: Add app.handler getter for edge runtimes with usage examples
- Address deepFreeze concern: skip freeze in production, rely on dev proxy
- Address Object.assign for middleware state accumulation performance
- Address native .env loading: use Node/Bun native support, minimal fallback

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
vertz-tech-lead Bot pushed a commit that referenced this pull request Feb 11, 2026
- Rename find() to findOne() for clarity (Josh #1)
- Separate read/write visibility: $insert includes .hidden() columns (Josh #2, Ben C3)
- Fix select union type with never-keyed branches for mutual exclusivity (Ben B4)
- Move cache-readiness primitives to v1.1 preview section (PM scope creep)
- Fix Non-Goal #7 to not claim v1 ships cache primitives (PM)
- Add exhaustiveness guarantee for error hierarchy with Assert pattern (Ben B1)
- Add type error quality section with branded error messages (Ben B2)
- Flag d.ref.many().through() as unvalidated, cap depth at 1 (Ben B3)
- Fix E2E type test assertions to match actual type semantics (Josh #3)
- Fix first example to compile under strict mode with ! assertion (Josh #4)
- Add vertz db init onboarding flow (Josh #5)
- Document zero-match behavior for all mutation methods (Josh #8)
- Add SQL injection prevention / parameter binding note (PM minor)
- Add dry-run mode for migrations (PM minor)
- Clarify d.email() is metadata-only, no runtime validation (Josh #6, Ben N4)
- Add vertz.env() pattern for type-safe DATABASE_URL access

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
viniciusdacal pushed a commit that referenced this pull request Feb 11, 2026
* docs(db): add @vertz/db v1 API design plan

Comprehensive design doc for the thin ORM layer covering schema definitions,
type inference, query builder, relations, migrations, error hierarchy,
and metadata-only multi-tenancy markers. Based on approved roadmap,
POC 1 results (28.5% of budget), and all exploration research.

Includes self-review notes from Josh (DX), Ben (feasibility), and PM (scope).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(db-design): address review feedback from Josh, Ben, and PM

- Rename find() to findOne() for clarity (Josh #1)
- Separate read/write visibility: $insert includes .hidden() columns (Josh #2, Ben C3)
- Fix select union type with never-keyed branches for mutual exclusivity (Ben B4)
- Move cache-readiness primitives to v1.1 preview section (PM scope creep)
- Fix Non-Goal #7 to not claim v1 ships cache primitives (PM)
- Add exhaustiveness guarantee for error hierarchy with Assert pattern (Ben B1)
- Add type error quality section with branded error messages (Ben B2)
- Flag d.ref.many().through() as unvalidated, cap depth at 1 (Ben B3)
- Fix E2E type test assertions to match actual type semantics (Josh #3)
- Fix first example to compile under strict mode with ! assertion (Josh #4)
- Add vertz db init onboarding flow (Josh #5)
- Document zero-match behavior for all mutation methods (Josh #8)
- Add SQL injection prevention / parameter binding note (PM minor)
- Add dry-run mode for migrations (PM minor)
- Clarify d.email() is metadata-only, no runtime validation (Josh #6, Ben N4)
- Add vertz.env() pattern for type-safe DATABASE_URL access

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: vertz-tech-lead[bot] <2828099+vertz-tech-lead[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: vertz-dev-dx[bot] <260432280+vertz-dev-dx[bot]@users.noreply.github.com>
vertz-dev-front Bot pushed a commit that referenced this pull request Feb 12, 2026
Address mike's review on PR #222 — all 3 blocking issues:

1. Created public barrel files (router/public.ts, form/public.ts,
   query/public.ts, css/public.ts) that export only the curated public
   API. Internal symbols (matchRoute, executeLoaders, matchPath,
   MemoryCache, deriveKey, generateClassName, compileTheme, etc.) are
   no longer leaked through subpath imports.

2. Rewrote tests with exhaustive assertions: Object.keys(mod).sort()
   matched against expected list catches both missing exports and
   unexpected leaks. Added reference identity checks (subpath.X ===
   main.X) per non-blocking suggestion #4.

3. Added minor changeset for @vertz/ui (new public API surface).

Also addressed non-blocking #5: reordered types before import in all
exports map entries for correct TS moduleResolution behavior.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
github-actions Bot pushed a commit that referenced this pull request Feb 12, 2026
Address mike's review on PR #222 — all 3 blocking issues:

1. Created public barrel files (router/public.ts, form/public.ts,
   query/public.ts, css/public.ts) that export only the curated public
   API. Internal symbols (matchRoute, executeLoaders, matchPath,
   MemoryCache, deriveKey, generateClassName, compileTheme, etc.) are
   no longer leaked through subpath imports.

2. Rewrote tests with exhaustive assertions: Object.keys(mod).sort()
   matched against expected list catches both missing exports and
   unexpected leaks. Added reference identity checks (subpath.X ===
   main.X) per non-blocking suggestion #4.

3. Added minor changeset for @vertz/ui (new public API surface).

Also addressed non-blocking #5: reordered types before import in all
exports map entries for correct TS moduleResolution behavior.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
github-actions Bot pushed a commit that referenced this pull request Feb 12, 2026
Address mike's review on PR #222 — all 3 blocking issues:

1. Created public barrel files (router/public.ts, form/public.ts,
   query/public.ts, css/public.ts) that export only the curated public
   API. Internal symbols (matchRoute, executeLoaders, matchPath,
   MemoryCache, deriveKey, generateClassName, compileTheme, etc.) are
   no longer leaked through subpath imports.

2. Rewrote tests with exhaustive assertions: Object.keys(mod).sort()
   matched against expected list catches both missing exports and
   unexpected leaks. Added reference identity checks (subpath.X ===
   main.X) per non-blocking suggestion #4.

3. Added minor changeset for @vertz/ui (new public API surface).

Also addressed non-blocking #5: reordered types before import in all
exports map entries for correct TS moduleResolution behavior.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
github-actions Bot pushed a commit that referenced this pull request Feb 12, 2026
Address mike's review on PR #222 — all 3 blocking issues:

1. Created public barrel files (router/public.ts, form/public.ts,
   query/public.ts, css/public.ts) that export only the curated public
   API. Internal symbols (matchRoute, executeLoaders, matchPath,
   MemoryCache, deriveKey, generateClassName, compileTheme, etc.) are
   no longer leaked through subpath imports.

2. Rewrote tests with exhaustive assertions: Object.keys(mod).sort()
   matched against expected list catches both missing exports and
   unexpected leaks. Added reference identity checks (subpath.X ===
   main.X) per non-blocking suggestion #4.

3. Added minor changeset for @vertz/ui (new public API surface).

Also addressed non-blocking #5: reordered types before import in all
exports map entries for correct TS moduleResolution behavior.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
viniciusdacal pushed a commit that referenced this pull request Feb 12, 2026
* feat(ui): add subpath exports for router/form/query/css [UI-029]

Add focused subpath imports so developers can do:
- import { Router } from '@vertz/ui/router'
- import { Form } from '@vertz/ui/form'
- import { query } from '@vertz/ui/query'
- import { css } from '@vertz/ui/css'

Changes:
- Add ./router, ./form, ./query, ./css to package.json exports map
- Add corresponding entry points to bunup.config.ts build config
- Add subpath-exports.test.ts verifying all exports resolve correctly
- Main barrel (@vertz/ui) continues to re-export everything (backward compat)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(ui): curate subpath export barrels, add changeset [UI-029]

Address mike's review on PR #222 — all 3 blocking issues:

1. Created public barrel files (router/public.ts, form/public.ts,
   query/public.ts, css/public.ts) that export only the curated public
   API. Internal symbols (matchRoute, executeLoaders, matchPath,
   MemoryCache, deriveKey, generateClassName, compileTheme, etc.) are
   no longer leaked through subpath imports.

2. Rewrote tests with exhaustive assertions: Object.keys(mod).sort()
   matched against expected list catches both missing exports and
   unexpected leaks. Added reference identity checks (subpath.X ===
   main.X) per non-blocking suggestion #4.

3. Added minor changeset for @vertz/ui (new public API surface).

Also addressed non-blocking #5: reordered types before import in all
exports map entries for correct TS moduleResolution behavior.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: vertz-dev-front[bot] <2828126+vertz-dev-front[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
vertz-tech-lead Bot pushed a commit that referenced this pull request Feb 14, 2026
TDD RED → GREEN cycle #4:
- Add test for aliased imports (query as fetchData)
- Add test for plain properties (refetch) - already passes
- Implement buildImportAliasMap to track import aliases
- Update ReactivityAnalyzer to resolve aliases before checking signal APIs
- All tests pass (232 tests total)

Edge cases covered: import aliases, plain vs signal property distinction.
github-actions Bot pushed a commit that referenced this pull request Feb 14, 2026
TDD RED → GREEN cycle #4:
- Add test for aliased imports (query as fetchData)
- Add test for plain properties (refetch) - already passes
- Implement buildImportAliasMap to track import aliases
- Update ReactivityAnalyzer to resolve aliases before checking signal APIs
- All tests pass (232 tests total)

Edge cases covered: import aliases, plain vs signal property distinction.
viniciusdacal pushed a commit that referenced this pull request Feb 14, 2026
…rap (#283)

* test(ui-compiler): add test for query().data auto-unwrap

TDD RED → GREEN cycle #1:
- Write test expecting tasks.data → tasks.data.value
- Implement signal API registry (query with data property)
- Update ReactivityAnalyzer to detect signal API calls
- Update SignalTransformer to auto-unwrap signal properties
- Test passes, typecheck passes, lint passes

Part of signal auto-unwrap feature to eliminate .value from public API.

* test(ui-compiler): add test for query().loading auto-unwrap

TDD RED → GREEN cycle #2:
- Write test expecting tasks.loading → tasks.loading.value
- Add 'loading' to query signal properties
- Test passes, all quality gates pass

Expanding signal auto-unwrap coverage.

* feat(ui-compiler): complete signal auto-unwrap for query, form, createLoader

TDD RED → GREEN cycle #3:
- Add tests for .error property on query()
- Add tests for form() with submitting, errors, values
- Add tests for createLoader() with data, loading, error
- Update signal API registry with all properties
- All tests pass (230 tests total)

Feature complete: Auto-unwrap eliminates .value from public API for all three signal-returning functions.

* feat(ui-compiler): support aliased imports for signal auto-unwrap

TDD RED → GREEN cycle #4:
- Add test for aliased imports (query as fetchData)
- Add test for plain properties (refetch) - already passes
- Implement buildImportAliasMap to track import aliases
- Update ReactivityAnalyzer to resolve aliases before checking signal APIs
- All tests pass (232 tests total)

Edge cases covered: import aliases, plain vs signal property distinction.

* docs(ui-compiler): document plain properties in signal API registry

Add plainProperties field to SignalApiConfig to explicitly document
which properties are NOT signals (refetch, reset, submit, handleSubmit).

This improves code clarity and serves as documentation for developers.
The implementation already handles these correctly - they're not unwrapped
because they're not in the signalProperties set.

No functional change, just documentation.

* chore: add changeset for signal auto-unwrap feature

* fix: prevent double .value bug + classify as MAJOR (TDD: RED→GREEN)

**TDD Cycle:**
1. RED: Add test for double .value case (migration) — fails as expected
2. GREEN: Add guard logic in signal-transformer to skip when .value exists — test passes

**Changes:**
- Add guard in transformSignalApiProperties() to detect existing .value
- Add test: 'should NOT double-unwrap when .value already exists'
- Change changeset from minor → major (BREAKING CHANGE)
- Add comprehensive migration guide to changeset
- Document Set<string> serialization limitation in VariableInfo type

**Addresses reviewer feedback:**
✅ Fix double .value bug with guard logic
✅ Add TDD test FIRST (RED), then fix (GREEN)
✅ Reclassify as major breaking change
✅ Add migration docs
✅ Document Set<string> non-serializable concern

All 233 tests pass (8/8 signal-unwrap tests).

* fix: use vitest import instead of bun:test for CI compatibility

CI uses vitest runner, not bun:test. Changed import to fix module resolution error.

---------

Co-authored-by: auditor <auditor@vertz.dev>
viniciusdacal added a commit that referenced this pull request Feb 22, 2026
Moved to its own PR (#4) as vertz-design-whys.md.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
viniciusdacal added a commit that referenced this pull request Feb 22, 2026
viniciusdacal added a commit that referenced this pull request Feb 22, 2026
- #4: Define BootSequence contract — ServiceFactory type, example compiler
  output showing JS module with live imports (not JSON manifest)
- #5: Document HEAD (auto-generated from GET) and OPTIONS (handled by CORS)
- #6: Explicitly state no AsyncLocalStorage — services receive request data
  via function arguments only
- #7: Add reserved ctx property names with runtime guard in dev mode
- #8: Clarify testing lives in @vertz/core, packages/testing/ superseded
- #9: Add app.handler getter for edge runtimes with usage examples
- Address deepFreeze concern: skip freeze in production, rely on dev proxy
- Address Object.assign for middleware state accumulation performance
- Address native .env loading: use Node/Bun native support, minimal fallback

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
viniciusdacal pushed a commit that referenced this pull request Feb 22, 2026
* docs(db): add @vertz/db v1 API design plan

Comprehensive design doc for the thin ORM layer covering schema definitions,
type inference, query builder, relations, migrations, error hierarchy,
and metadata-only multi-tenancy markers. Based on approved roadmap,
POC 1 results (28.5% of budget), and all exploration research.

Includes self-review notes from Josh (DX), Ben (feasibility), and PM (scope).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(db-design): address review feedback from Josh, Ben, and PM

- Rename find() to findOne() for clarity (Josh #1)
- Separate read/write visibility: $insert includes .hidden() columns (Josh #2, Ben C3)
- Fix select union type with never-keyed branches for mutual exclusivity (Ben B4)
- Move cache-readiness primitives to v1.1 preview section (PM scope creep)
- Fix Non-Goal #7 to not claim v1 ships cache primitives (PM)
- Add exhaustiveness guarantee for error hierarchy with Assert pattern (Ben B1)
- Add type error quality section with branded error messages (Ben B2)
- Flag d.ref.many().through() as unvalidated, cap depth at 1 (Ben B3)
- Fix E2E type test assertions to match actual type semantics (Josh #3)
- Fix first example to compile under strict mode with ! assertion (Josh #4)
- Add vertz db init onboarding flow (Josh #5)
- Document zero-match behavior for all mutation methods (Josh #8)
- Add SQL injection prevention / parameter binding note (PM minor)
- Add dry-run mode for migrations (PM minor)
- Clarify d.email() is metadata-only, no runtime validation (Josh #6, Ben N4)
- Add vertz.env() pattern for type-safe DATABASE_URL access

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: vertz-tech-lead[bot] <2828099+vertz-tech-lead[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: vertz-dev-dx[bot] <260432280+vertz-dev-dx[bot]@users.noreply.github.com>
viniciusdacal pushed a commit that referenced this pull request Feb 22, 2026
* feat(ui): add subpath exports for router/form/query/css [UI-029]

Add focused subpath imports so developers can do:
- import { Router } from '@vertz/ui/router'
- import { Form } from '@vertz/ui/form'
- import { query } from '@vertz/ui/query'
- import { css } from '@vertz/ui/css'

Changes:
- Add ./router, ./form, ./query, ./css to package.json exports map
- Add corresponding entry points to bunup.config.ts build config
- Add subpath-exports.test.ts verifying all exports resolve correctly
- Main barrel (@vertz/ui) continues to re-export everything (backward compat)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(ui): curate subpath export barrels, add changeset [UI-029]

Address mike's review on PR #222 — all 3 blocking issues:

1. Created public barrel files (router/public.ts, form/public.ts,
   query/public.ts, css/public.ts) that export only the curated public
   API. Internal symbols (matchRoute, executeLoaders, matchPath,
   MemoryCache, deriveKey, generateClassName, compileTheme, etc.) are
   no longer leaked through subpath imports.

2. Rewrote tests with exhaustive assertions: Object.keys(mod).sort()
   matched against expected list catches both missing exports and
   unexpected leaks. Added reference identity checks (subpath.X ===
   main.X) per non-blocking suggestion #4.

3. Added minor changeset for @vertz/ui (new public API surface).

Also addressed non-blocking #5: reordered types before import in all
exports map entries for correct TS moduleResolution behavior.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: vertz-dev-front[bot] <2828126+vertz-dev-front[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
viniciusdacal pushed a commit that referenced this pull request Feb 22, 2026
…rap (#283)

* test(ui-compiler): add test for query().data auto-unwrap

TDD RED → GREEN cycle #1:
- Write test expecting tasks.data → tasks.data.value
- Implement signal API registry (query with data property)
- Update ReactivityAnalyzer to detect signal API calls
- Update SignalTransformer to auto-unwrap signal properties
- Test passes, typecheck passes, lint passes

Part of signal auto-unwrap feature to eliminate .value from public API.

* test(ui-compiler): add test for query().loading auto-unwrap

TDD RED → GREEN cycle #2:
- Write test expecting tasks.loading → tasks.loading.value
- Add 'loading' to query signal properties
- Test passes, all quality gates pass

Expanding signal auto-unwrap coverage.

* feat(ui-compiler): complete signal auto-unwrap for query, form, createLoader

TDD RED → GREEN cycle #3:
- Add tests for .error property on query()
- Add tests for form() with submitting, errors, values
- Add tests for createLoader() with data, loading, error
- Update signal API registry with all properties
- All tests pass (230 tests total)

Feature complete: Auto-unwrap eliminates .value from public API for all three signal-returning functions.

* feat(ui-compiler): support aliased imports for signal auto-unwrap

TDD RED → GREEN cycle #4:
- Add test for aliased imports (query as fetchData)
- Add test for plain properties (refetch) - already passes
- Implement buildImportAliasMap to track import aliases
- Update ReactivityAnalyzer to resolve aliases before checking signal APIs
- All tests pass (232 tests total)

Edge cases covered: import aliases, plain vs signal property distinction.

* docs(ui-compiler): document plain properties in signal API registry

Add plainProperties field to SignalApiConfig to explicitly document
which properties are NOT signals (refetch, reset, submit, handleSubmit).

This improves code clarity and serves as documentation for developers.
The implementation already handles these correctly - they're not unwrapped
because they're not in the signalProperties set.

No functional change, just documentation.

* chore: add changeset for signal auto-unwrap feature

* fix: prevent double .value bug + classify as MAJOR (TDD: RED→GREEN)

**TDD Cycle:**
1. RED: Add test for double .value case (migration) — fails as expected
2. GREEN: Add guard logic in signal-transformer to skip when .value exists — test passes

**Changes:**
- Add guard in transformSignalApiProperties() to detect existing .value
- Add test: 'should NOT double-unwrap when .value already exists'
- Change changeset from minor → major (BREAKING CHANGE)
- Add comprehensive migration guide to changeset
- Document Set<string> serialization limitation in VariableInfo type

**Addresses reviewer feedback:**
✅ Fix double .value bug with guard logic
✅ Add TDD test FIRST (RED), then fix (GREEN)
✅ Reclassify as major breaking change
✅ Add migration docs
✅ Document Set<string> non-serializable concern

All 233 tests pass (8/8 signal-unwrap tests).

* fix: use vitest import instead of bun:test for CI compatibility

CI uses vitest runner, not bun:test. Changed import to fix module resolution error.

---------

Co-authored-by: auditor <auditor@vertz.dev>
@viniciusdacal viniciusdacal deleted the docs/design-whys branch February 22, 2026 16:19
viniciusdacal pushed a commit that referenced this pull request Mar 13, 2026
Replace hand-written auth patterns with framework abstractions:
- ProtectedRoute replaces AuthGuard component (#1214)
- OAuthButton replaces hardcoded OAuth URL + GitHub icon (#1215)
- sessionResolver added to dev server for SSR session injection (#1216)
- Update auth-ui-framework-gaps.md to mark gaps #1, #2, #4 as done

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
viniciusdacal added a commit that referenced this pull request Mar 13, 2026
… fixes (#1208)

* feat(auth): per-provider mapProfile callback for OAuth user creation

Add typed mapProfile callbacks to OAuth providers so provider-specific
data (name, avatarUrl, custom fields) flows through to the created user.

- OAuthProviderConfig<TProfile> generic with typed mapProfile callback
- OAuthUserInfo.raw passes full provider API response
- OAuthProvider.mapProfile required on provider instances
- Typed profiles: GithubProfile, GoogleProfile, DiscordProfile
- Secure spread: framework fields (id, email, emailVerified, role,
  createdAt, updatedAt) always override mapProfile output
- mapProfile errors redirect with ?error=profile_mapping_failed
- Non-object mapProfile returns treated as empty

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(auth): onUserCreated callback and auth-entity bridge

Replace mapProfile with onUserCreated callback that fires after auth
user creation. Developers populate their own entity tables in the
callback instead of mapping profile data into auth_users.

- Add onUserCreated to AuthConfig with discriminated union payload
  (OAuth: { user, provider, profile }, email: { user, signUpData })
- Add deleteUser to UserStore for rollback on callback failure
- Wire entity registry proxy via _entityProxy in createServer
- Remove mapProfile from OAuthProvider, OAuthProviderConfig, providers
- Remove name/avatarUrl from OAuthUserInfo (raw carries full profile)
- Close AuthUser interface (remove [key: string]: unknown)
- Remove safeFields spread into AuthUser on email sign-up
- Add 'general' to AuthValidationError.field for callback errors
- Rollback with error logging on both OAuth and email/password paths

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(auth): correctly append error params to OAuth error redirect URLs

The OAuth callback handler was naively appending `?error=...` to the
error redirect URL without checking if it already contained query
parameters. When oauthErrorRedirect was set to `/login?error=oauth`,
the resulting URL was `/login?error=oauth?error=token_exchange_failed`
(double `?`). Added an `errorUrl()` helper that uses `&` when `?`
already exists. Also improved GitHub provider to surface token exchange
errors instead of silently swallowing them, and added DOM shim stubs
for addEventListener/removeEventListener needed during SSR.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(auth): resolve OAuth login flow bugs across server and client

Server fixes:
- Auto-wire DbOAuthAccountStore in createServer (was missing, OAuth accounts never persisted)
- Fix cookie maxAge to match JWT TTL (was using hardcoded value causing premature expiry)
- Fix db-session-store findActiveSessionById to use raw SQL (ORM gt operator broken)

Client fixes:
- Move useAuth from signal-api to reactive-source in compiler registry and reactivity.json
  (compiler was adding .value on top of wrapSignalProps getters, causing undefined)
- Defer refresh() via setTimeout(0) when no SSR session exists
  (SSR flushes microtasks, so status stayed idle during server render instead of redirecting)
- Update auth-context tests to match deferred-refresh behavior

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(auth): remove unused SessionRecord type and recordToSession method

Leftover from switching findActiveSessionById to raw SQL.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* test(auth): update DbSessionStore tests for raw SQL findActiveSessionById

Tests were mocking the ORM-based auth_sessions.get() path, but the
implementation now uses db.query() with raw SQL. Updated mocks to
match the new interface.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* docs(auth): self-review of auth UI framework gaps from Linear clone

Identifies 7 patterns hand-written in the Linear example that should
be framework-level: ProtectedRoute, OAuth buttons, dev server handler
composition, SSR session injection, sign-out redirect, user profile
helpers, and router initialPath auto-detection.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(auth): address review findings — race condition and conditional auto-wiring

- Cancel deferred refresh timer when signIn/signUp is called, preventing
  a race where refresh() could overwrite authenticated status with
  unauthenticated (the refresh cookie isn't set yet from the sign-in)
- Only auto-wire DbOAuthAccountStore when OAuth providers are configured
- Only create entity registry proxy when onUserCreated callback exists

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* review(auth): adversarial review for OAuth login flow fixes

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(server): remove erroneous OrgPlan re-export from rebase conflict

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* chore(auth): add issue reference for raw SQL workaround in DbSessionStore (#1209)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(auth): revert raw SQL workaround in DbSessionStore to ORM get()

Now that #1212 fixed null handling in where clauses (IS NULL instead of
= NULL), all three session lookup methods use the ORM consistently.
Restores recordToSession and SessionRecord for ORM camelCase records.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* chore(auth): use signOut({ redirectTo }) from #1213 in Linear clone

Now that the framework supports signOut with redirect, use it instead
of relying on AuthGuard's implicit redirect. Updated framework gaps
doc to mark item #5 as done.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* chore(linear): adopt framework auth components (#1214, #1215, #1216)

Replace hand-written auth patterns with framework abstractions:
- ProtectedRoute replaces AuthGuard component (#1214)
- OAuthButton replaces hardcoded OAuth URL + GitHub icon (#1215)
- sessionResolver added to dev server for SSR session injection (#1216)
- Update auth-ui-framework-gaps.md to mark gaps #1, #2, #4 as done

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* chore(linear): adopt createRouter auto-detect (#1219), fix error redirect test

- Remove initialPath boilerplate from router.tsx — createRouter now auto-detects
- Update error redirect test to match URL constructor behavior from #1218
- Mark gap #7 (initialPath auto-detect) as done in framework gaps doc

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* chore(linear): use app.requestHandler for unified routing (#1221)

Replace manual if/else auth+entity routing with framework's
requestHandler getter. Mark gap #3 as done in framework gaps doc.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* chore(linear): add Linear clone example app files

Commit all Linear clone example files that were previously untracked.
The bun.lock already references examples/linear — CI fails with
--frozen-lockfile because the package.json wasn't committed.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: vertz-dev-front[bot] <2828126+vertz-dev-front[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
viniciusdacal added a commit that referenced this pull request Apr 22, 2026
Design doc at plans/agent-store-entity-bridge.md for bridging AgentStore
with Vertz entities so agent sessions/messages become queryable with RLS
from app code. Resolves Gap #4 of plans/open-agents-clone.md.

Rev 6 after three rounds of adversarial review (DX, Product, Technical)
per .claude/rules/design-and-planning.md. All reviewers approved. Awaits
human final sign-off.

Key design decisions settled:
- Entities are a READ-VIEW over the store's tables. Writes stay on
  memoryStore/sqliteStore/d1Store; entity RLS applies to app-side reads.
- defineAgentEntities(db) factory + column packs (no extend API, no sugar
  helpers). Custom fields via normal d.table() spread.
- AgentStore.appendMessages gains session parameter (matches
  appendMessagesAtomic). Pre-v1 breaking change.
- agent_messages denormalizes userId/tenantId for flat rules.where().
  Non-adopter breaking change acknowledged explicitly.
- before.create hook injects ctx.userId/ctx.tenantId (ctx wins over input
  to prevent silent impersonation).
- Hook-bypass on agent-loop writes tracked as follow-up #2957.
- state/toolCalls → d.jsonb<T>() migration tracked as #2958.

Closes: none yet (design-only PR).
Refs: #2847, #2957, #2958, plans/open-agents-clone.md

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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