Skip to content

docs: add Vertz manifesto#1

Merged
viniciusdacal merged 2 commits intomainfrom
docs/manifesto
Feb 4, 2026
Merged

docs: add Vertz manifesto#1
viniciusdacal merged 2 commits intomainfrom
docs/manifesto

Conversation

@viniciusdacal
Copy link
Copy Markdown
Contributor

Defines the vision and principles behind Vertz:

  • "Not just for humans anymore" - LLM-native framework design
  • Type safety over decorator elegance
  • One way to do things - predictability over flexibility
  • Compile-time over runtime errors
  • Full-stack type flow vision

Defines the vision and principles behind Vertz:
- "Not just for humans anymore" - LLM-native framework design
- Type safety over decorator elegance
- One way to do things - predictability over flexibility
- Compile-time over runtime errors
- Full-stack type flow vision

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@viniciusdacal viniciusdacal self-assigned this Feb 3, 2026
Comment thread MANIFESTO.md
Comment thread MANIFESTO.md
Comment thread MANIFESTO.md Outdated
- Add origin story (building our own products, saw the pain firsthand)
- Add "clean, simple code" and "modular architecture" to audience
- Add "clean for humans, performant for agents" to Vertz Is NOT

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@viniciusdacal viniciusdacal merged commit 2bdebb8 into main Feb 4, 2026
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-tech-lead Bot pushed a commit that referenced this pull request Feb 14, 2026
- Remove demo-toolkit package (unrelated feature)
- Remove old changeset
- Revert ui-server package.json, dev-server.ts, and bun.lock changes

Addresses reviewer feedback issue #1: PR scope violation.
vertz-tech-lead Bot pushed a commit that referenced this pull request Feb 14, 2026
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.
github-actions Bot pushed a commit that referenced this pull request Feb 14, 2026
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.
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 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 pushed a commit that referenced this pull request Mar 14, 2026
…vel allowWhere/allowOrderBy validation, POCs

- Apply expose.select in crud-pipeline to restrict response fields (list, get, create, update)
- Add entity-level allowWhere/allowOrderBy validation to validateVertzQL()
- Wire expose config through route-generator to validateVertzQL()
- Export ExposeValidationConfig from vertzql-parser
- POC #1: allowWhere/allowOrderBy constrained to PublicColumnKeys at type level,
  subset-of-select enforced at runtime (fallback approach — avoids generic bloat)
- POC #2: T | null typing for descriptor-guarded fields via conditional mapped type
- Update design doc with POC results

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
github-actions Bot pushed a commit that referenced this pull request Mar 14, 2026
…vel allowWhere/allowOrderBy validation, POCs

- Apply expose.select in crud-pipeline to restrict response fields (list, get, create, update)
- Add entity-level allowWhere/allowOrderBy validation to validateVertzQL()
- Wire expose config through route-generator to validateVertzQL()
- Export ExposeValidationConfig from vertzql-parser
- POC #1: allowWhere/allowOrderBy constrained to PublicColumnKeys at type level,
  subset-of-select enforced at runtime (fallback approach — avoids generic bloat)
- POC #2: T | null typing for descriptor-guarded fields via conditional mapped type
- Update design doc with POC results

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

* feat(server): add Entity Expose API — types, factory, and runtime support

Replace `relations` config on EntityConfig/EntityDefinition with a new
`expose` API that unifies field exposure, relation config, filter/sort
allowlists, and AccessRule descriptors into a fractal structure mirroring
the DB query API shape (select, allowWhere, allowOrderBy, include).

- Add ExposeConfig and RelationExposeConfig types with full generics
- select values accept `true | AccessRule` for descriptor-guarded fields
- include constrains to model relation keys with typed target table columns
- Nested include supports recursive relation exposure
- Update entity() factory, crud-pipeline, route-generator, vertzql-parser
- Update extractAllowKeys() to handle object-shaped allowWhere/allowOrderBy
- Comprehensive type-level tests in expose-types.test-d.ts

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

* feat(server): complete Phase 1 — expose.select enforcement, entity-level allowWhere/allowOrderBy validation, POCs

- Apply expose.select in crud-pipeline to restrict response fields (list, get, create, update)
- Add entity-level allowWhere/allowOrderBy validation to validateVertzQL()
- Wire expose config through route-generator to validateVertzQL()
- Export ExposeValidationConfig from vertzql-parser
- POC #1: allowWhere/allowOrderBy constrained to PublicColumnKeys at type level,
  subset-of-select enforced at runtime (fallback approach — avoids generic bloat)
- POC #2: T | null typing for descriptor-guarded fields via conditional mapped type
- Update design doc with POC results

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

* feat(server): add descriptor runtime evaluation for expose API (Phase 2)

- Add expose-evaluator.ts with evaluateExposeDescriptors() that pre-evaluates
  AccessRule descriptors once per request, producing static field sets
- Descriptor-guarded select fields return null when user lacks access
- Descriptor-guarded allowWhere/allowOrderBy fields reject with "not filterable"/"not sortable"
- Add nullGuardedFields() to field-filter for nulling descriptor-denied fields
- Wire evaluation through route-generator for all CRUD handlers
- Add EvaluatedExposeValidation to validateVertzQL() for dynamic field checks

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

* docs(server): add entity exposure guide — Fields, Relations & Filters (Phase 3)

- New page guides/server/entity-exposure.mdx covering expose config:
  select, allowWhere, allowOrderBy, include, descriptors, null semantics
- Add navigation entry in docs.json after entities
- Add card and feature row in server overview
- Cross-reference from entities.mdx to new guide

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

* refactor(server): address review findings — consolidate types, fix unsafe cast

- Change applySelect to accept Record<string, unknown> (it only checks
  key presence, not values — the Record<string, true> type was a lie
  when expose.select contains AccessRule descriptors)
- Remove exposeSelect cast to Record<string, true> in crud-pipeline
- Remove duplicate EvaluatedExposeValidation type — import EvaluatedExpose
  from expose-evaluator instead

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

* chore: add changeset for entity expose API

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 Mar 23, 2026
Enable `{ prepare: true }` on postgres.js `sql.unsafe()` calls in
@vertz/db's postgres driver. This enables server-side prepared statement
caching, reducing per-query overhead by ~60% (0.57ms → 0.24ms/op).

Remove the manual `coerceValue` timestamp coercion layer — postgres.js
handles type conversion natively via built-in OID parsers when connected
normally.

In @vertz/schema, optimize the hot parse path:
- Memoize shape keys Set in ObjectSchema constructor (avoid per-parse alloc)
- Lazy issues array in ParseContext (skip alloc for valid parses)
- Skip unknown key filtering in strip mode (the default)

Benchmarked via rinha-de-backend URL shortener challenge: Vertz achieved
2443 req/s (#1) vs Go 1528 req/s (#2), with p95 latency of 54ms vs 79ms.

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

Enable `{ prepare: true }` on postgres.js `sql.unsafe()` calls in
@vertz/db's postgres driver. This enables server-side prepared statement
caching, reducing per-query overhead by ~60% (0.57ms → 0.24ms/op).

Remove the manual `coerceValue` timestamp coercion layer — postgres.js
handles type conversion natively via built-in OID parsers when connected
normally.

In @vertz/schema, optimize the hot parse path:
- Memoize shape keys Set in ObjectSchema constructor (avoid per-parse alloc)
- Lazy issues array in ParseContext (skip alloc for valid parses)
- Skip unknown key filtering in strip mode (the default)

Benchmarked via rinha-de-backend URL shortener challenge: Vertz achieved
2443 req/s (#1) vs Go 1528 req/s (#2), with p95 latency of 54ms vs 79ms.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
viniciusdacal added a commit that referenced this pull request Mar 29, 2026
#2020)

SSRDocumentFragment had three bugs that caused conditional/list comment
markers to be silently dropped from single-pass SSR output:

1. insertBefore was inherited from SSRNode and only updated childNodes,
   not the children array used by toVNode for HTML serialization.

2. appendChild used child.children (which was incomplete due to bug #1)
   when flattening nested fragments, instead of child.childNodes.

3. The toVNode function for fragments didn't handle SSRComment children,
   causing a crash when the root component returned a fragment.

Fixed by:
- Adding insertBefore override to SSRDocumentFragment that syncs both
  children and childNodes arrays, with proper fragment flattening
- Changing appendChild fragment flattening to use childNodes as source
  of truth instead of children
- Adding explicit SSRComment handling in appendChild
- Adding SSRComment → rawHtml conversion in fragment toVNode
- Updating children type to include SSRComment

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
github-actions Bot pushed a commit that referenced this pull request Mar 29, 2026
#2020)

SSRDocumentFragment had three bugs that caused conditional/list comment
markers to be silently dropped from single-pass SSR output:

1. insertBefore was inherited from SSRNode and only updated childNodes,
   not the children array used by toVNode for HTML serialization.

2. appendChild used child.children (which was incomplete due to bug #1)
   when flattening nested fragments, instead of child.childNodes.

3. The toVNode function for fragments didn't handle SSRComment children,
   causing a crash when the root component returned a fragment.

Fixed by:
- Adding insertBefore override to SSRDocumentFragment that syncs both
  children and childNodes arrays, with proper fragment flattening
- Changing appendChild fragment flattening to use childNodes as source
  of truth instead of children
- Adding explicit SSRComment handling in appendChild
- Adding SSRComment → rawHtml conversion in fragment toVNode
- Updating children type to include SSRComment

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
github-actions Bot pushed a commit that referenced this pull request Mar 29, 2026
#2020)

SSRDocumentFragment had three bugs that caused conditional/list comment
markers to be silently dropped from single-pass SSR output:

1. insertBefore was inherited from SSRNode and only updated childNodes,
   not the children array used by toVNode for HTML serialization.

2. appendChild used child.children (which was incomplete due to bug #1)
   when flattening nested fragments, instead of child.childNodes.

3. The toVNode function for fragments didn't handle SSRComment children,
   causing a crash when the root component returned a fragment.

Fixed by:
- Adding insertBefore override to SSRDocumentFragment that syncs both
  children and childNodes arrays, with proper fragment flattening
- Changing appendChild fragment flattening to use childNodes as source
  of truth instead of children
- Adding explicit SSRComment handling in appendChild
- Adding SSRComment → rawHtml conversion in fragment toVNode
- Updating children type to include SSRComment

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

* fix(ui-server): preserve comment markers in SSR fragment serialization (#2020)

SSRDocumentFragment had three bugs that caused conditional/list comment
markers to be silently dropped from single-pass SSR output:

1. insertBefore was inherited from SSRNode and only updated childNodes,
   not the children array used by toVNode for HTML serialization.

2. appendChild used child.children (which was incomplete due to bug #1)
   when flattening nested fragments, instead of child.childNodes.

3. The toVNode function for fragments didn't handle SSRComment children,
   causing a crash when the root component returned a fragment.

Fixed by:
- Adding insertBefore override to SSRDocumentFragment that syncs both
  children and childNodes arrays, with proper fragment flattening
- Changing appendChild fragment flattening to use childNodes as source
  of truth instead of children
- Adding explicit SSRComment handling in appendChild
- Adding SSRComment → rawHtml conversion in fragment toVNode
- Updating children type to include SSRComment

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

* fix(ui-server): add removeChild/replaceChild overrides to SSRDocumentFragment (#2020)

Address review findings:
- Add removeChild override to keep children in sync with childNodes
- Add replaceChild override with fragment flattening support
- Fix insertBefore to no-op when referenceNode is not found (matches
  base SSRNode behavior instead of silently appending)
- Extract toChildEntry helper to eliminate code duplication
- Remove double casts in tests (SSRComment is in appendChild union)
- Add tests: insertBefore with null ref, unknown ref, fragment ref,
  text node, removeChild, replaceChild

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

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
viniciusdacal added a commit that referenced this pull request Apr 17, 2026
Under `vtz test`, parallel test files each run in their own V8 isolate
but share the process. Previously `op_env_{get,set,remove,keys}` operated
on `std::env`, which is process-global — so two parallel files both
mutating `process.env.NODE_ENV` raced, giving whichever wrote last.

This broke `@vertz/core` env-validator.test.ts: it sets NODE_ENV='production'
and asserts; a concurrent ctx-builder.test.ts sets NODE_ENV='development'
and the assertion fails "expected development to be production".

Fix: per-isolate `IsolateEnv` in `OpState`, seeded from `std::env::vars()`
at isolate creation. JS mutations operate on the isolate's HashMap, not
std::env. Matches bun/vitest worker-isolation semantics.

child_process.spawn/exec also updated: when opts.env is null, children
inherit the isolate's env (not process env), so a JS mutation like
`process.env.NODE_ENV = 'production'` is visible to spawned children —
matches Node.js parent-env-inheritance semantics.

Regression test:
- test_process_env_isolated_across_isolates verifies two isolates see
  independent values and no leak to std::env.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
viniciusdacal added a commit that referenced this pull request Apr 17, 2026
Under `vtz test`, parallel test files each run in their own V8 isolate
but share the process. Previously `op_env_{get,set,remove,keys}` operated
on `std::env`, which is process-global — so two parallel files both
mutating `process.env.NODE_ENV` raced, giving whichever wrote last.

This broke `@vertz/core` env-validator.test.ts: it sets NODE_ENV='production'
and asserts; a concurrent ctx-builder.test.ts sets NODE_ENV='development'
and the assertion fails "expected development to be production".

Fix: per-isolate `IsolateEnv` in `OpState`, seeded from `std::env::vars()`
at isolate creation. JS mutations operate on the isolate's HashMap, not
std::env. Matches bun/vitest worker-isolation semantics.

child_process.spawn/exec also updated: when opts.env is null, children
inherit the isolate's env (not process env), so a JS mutation like
`process.env.NODE_ENV = 'production'` is visible to spawned children —
matches Node.js parent-env-inheritance semantics.

Regression test:
- test_process_env_isolated_across_isolates verifies two isolates see
  independent values and no leak to std::env.

Co-authored-by: Claude Opus 4.6 <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.

2 participants