Skip to content

Releases: vertz-dev/vertz

v0.2.80

02 May 02:35
d8096ad

Choose a tag to compare

What's Changed

Read more

vertz@0.2.80

02 May 02:34
d8096ad

Choose a tag to compare

Patch Changes

  • #2965 06605b0 Thanks @viniciusdacal! - fix(compiler): disambiguate context stable ids when two createContext calls share a variable name in the same file

    Closes #2786.

    injectContextStableIds generated the id as {filePath}::{varName}. Two createContext() calls in the same file with the same variable name (e.g. an inlined/formatted pair on one line, or a code-generated module) produced the same id, so the runtime context registry silently returned the same object for both — breaking Provider/useContext pairing.

    Both the Rust transform (native/vertz-compiler-core/src/context_stable_ids.rs) and the TypeScript sibling (packages/ui-server/src/build-plugin/context-stable-ids.ts) now track a per-name occurrence counter and suffix @N on repeats. The first occurrence of a name keeps the original {filePath}::{varName} id (so existing single-context files are unchanged); the second becomes {filePath}::{varName}@1, the third @2, and so on.

    A per-name counter is used rather than a source span because counters only shift when contexts are added or removed, whereas spans shift on any edit to earlier code — counters are more HMR-stable.

  • #2938 682eb30 Thanks @viniciusdacal! - fix(compiler): AOT threads hydration id onto the first element child of a fragment root

    Closes #2784.

    Previously, when an interactive component (a component with a let declaration) returned a JSX fragment, the AOT SSR transformer silently dropped the data-v-id marker. fragment_to_string took no hydration_id parameter, and the call site in expr_to_string passed the id into element_to_string but not into fragment_to_string. Result: the server-rendered HTML had no root marker the client hydrator could locate, so event handlers and signal subscriptions never attached — an invisible failure for any component written as return <>...</>.

    The AOT path now matches the runtime SSR behavior (where inject_hydration_attr already skips document.createDocumentFragment() and targets the first __element(...) call): the hydration id is threaded into fragment_to_string, and gets attached to the first element-or-fragment child. Text and expression children are skipped; nested fragments recurse, carrying the id down until an element is found or the fragment is exhausted.

  • #3019 3accbd6 Thanks @viniciusdacal! - fix(compiler): use span-overlap (not point) check for mutation ranges in signal transformer

    Closes #2785.

    is_in_mutation_range tested whether ident.span.start was a member of a recorded mutation range (pos >= start && pos < end). That contract relied on mutation_analyzer always recording a span that begins exactly at the identifier's first character — so any future tightening (e.g., the operator-only span for +=, or a span recorded on an inner sub-expression) would silently misclassify the identifier as outside the range and double-handle it by appending .value on top of the mutation rewrite.

    Replaced the point check with a span-overlap check (ident.start < range.end && ident.end > range.start), renamed the predicate to overlaps_mutation_range, and applied it at all three call sites: identifier reads, assignment-expression LHS, and update-expression targets.

  • #2991 27ea038 Thanks @viniciusdacal! - fix(compiler): make early-return guards reactive in component bodies

    Closes #2987.

    A component of the shape if (cond) return <Loading/>; return <Ready/>; froze at the guard branch: the body ran once at mount and never re-ran when cond flipped. This broke the documented loading-UX pattern of query().loading + early return.

    The compiler now detects the guard shape (N consecutive if (cond) return <jsx>; at the top of a component body, followed by a single trailing return <jsx>;) and rewrites the body to wrap the main return in a chain of __conditional(() => cond, () => branch, () => fallback) calls, so the condition is re-evaluated reactively and the DOM swaps between branches without re-mounting the component.

    Guards with an else branch, multi-statement blocks, or non-guard statements between guards are left alone — the per-return mount-frame wrapper still handles them.

  • #2955 2c1616c Thanks @viniciusdacal! - feat(client): type import.meta.main in vertz/client and @vertz/ui/client

    Closes #2811.

    Follow-up to #2777. The vtz runtime (via deno_core) already sets import.meta.main on every module — true for the entry module, false for imported modules — so the standard "run if main" idiom works without any polyfill:

    // src/api/server.ts
    const app = createServer({
      /* ... */
    });
    export default app;
    
    if (import.meta.main) app.listen(env.PORT);

    Previously the type was only available to projects that pulled in bun-types. The client augmentation (packages/ui/client.d.ts) now declares readonly main: boolean alongside hot, so any tsconfig that includes "types": ["vertz/client"] (or "@vertz/ui/client") gets it automatically. Scaffolded apps already have this entry.

    bun-types removed from sites/dev-orchestrator where it was only kept for this type.

  • Updated dependencies [06605b0, 4855184, 513fe1e, 2840af4, 5b3838b, f426ce5, 85707b2, 2c1616c, e84adde]:

    • @vertz/ui-server@0.2.80
    • @vertz/ui@0.2.80
    • @vertz/schema@0.2.80
    • @vertz/fetch@0.2.80
    • @vertz/server@0.2.80
    • @vertz/cli@0.2.80
    • @vertz/cloudflare@0.2.80
    • @vertz/db@0.2.80
    • @vertz/errors@0.2.80
    • @vertz/testing@0.2.80
    • @vertz/tui@0.2.80
    • @vertz/ui-auth@0.2.80
    • @vertz/ui-primitives@0.2.80

create-vertz@0.2.80

02 May 02:34
d8096ad

Choose a tag to compare

Patch Changes

  • Updated dependencies []:
    • @vertz/create-vertz-app@0.2.80

@vertz/ui@0.2.80

02 May 02:34
d8096ad

Choose a tag to compare

Patch Changes

  • #2996 4855184 Thanks @viniciusdacal! - fix(ui,schema): coerce form values for JSON bodies, drop fields not in the schema

    Closes #2980.

    form() was sending checkbox values as raw strings in JSON bodies ("concluida":"true") when the SDK lacked meta.bodySchema, causing the server to reject the request with 422 Expected boolean, received string.

    Two changes:

    • form() no-schema fallback now uses formDataToObject(fd, { nested: true, coerce: true }), so plain checkbox/number inputs serialize as boolean / number in the JSON body.
    • coerceFormDataToSchema now treats the schema as the contract: only fields declared in the schema's shape are included in the result. Unknown form keys are dropped instead of being forwarded to the server. This prevents accidental leakage (e.g. an attacker injecting tenantId via DevTools, or a stale <input> from another form) and removes a class of .strict()-rejection footguns.

    Custom non-Vertz schema adapters keep their existing behavior — values pass through untouched so user-supplied parsers stay in control.

  • #2983 513fe1e Thanks @viniciusdacal! - fix(ui): prevent concurrent form submissions (double-click creates duplicates)

    Closes #2982.

    form() did not lock against re-entrant submissions. A double-clicked submit button fired two submit events back-to-back; both entered submitPipeline, passed validation, and called the SDK — creating duplicate records.

    submitPipeline now checks submitting.peek() synchronously at entry and returns early if a submission is already in flight. submitting.value = true is set before any work (so the second synchronous call sees the guard), and a try/finally ensures it is reset on every exit path. The pipeline returns a boolean so the onSubmit / submit wrappers skip their post-processing (form reset) when a call was rejected.

  • #2994 2840af4 Thanks @viniciusdacal! - fix(ui,fetch): map server 422 validation errors back to form field error signals

    Closes #2981.

    Server 422 responses were being swallowed. The server emits per-field errors under body.error.details, but the fetch client was looking at body.error.errors, so FetchValidationError never fired. form() then fell back to the generic _form handler, and per-field UI feedback never appeared.

    Two changes make the round-trip work:

    • @vertz/fetch now reads error.details (matching packages/server/src/entity/error-handler.ts) and normalizes paths: array paths (e.g. ['items', 0, 'name']) become dot-notation strings, and empty paths ('' / []) become _form — matching the convention already used by client-side validation. An empty details array falls through to the regular HTTP error path so the top-level message still reaches the UI. FetchValidationError and isFetchValidationError are re-exported from @vertz/fetch for consumer use.
    • form() checks for FetchValidationError in submitPipeline and walks the already-normalized .errors, writing each message to the matching field's error signal. onError receives the same per-field record. Non-validation errors keep the existing _form fallback.
  • #2988 5b3838b Thanks @viniciusdacal! - fix(query): refetch on remount when a mutation occurred while the query was unmounted [#2986]

    When a user navigated from a list page to a form, created an entity, and navigated back via router.navigate(), the list kept showing the old cached data until a full page reload. Between unmount and remount, the list query had unsubscribed from the MutationEventBus, so the emit() fired by the form's create mutation had no live listener — yet the cached entry (and its query indices) remained, and was served on remount.

    MutationEventBus now tracks a monotonic per-entity-type version that increments on every emit(). MemoryCache.set() accepts an optional version argument and records it alongside the value; CacheStore<T> gained an optional getVersion(key) accessor. When query() gets a cache hit for an entity-backed query on mount, it compares the cached entry's version with the current bus version for the same entity type and treats the entry as stale when the bus version is newer, falling through to a fresh fetch.

    This is additive — custom CacheStore implementations that don't implement getVersion continue to work and simply keep the previous (cached) behavior.

  • #3004 85707b2 Thanks @viniciusdacal! - fix(ui,runtime): return correct HTTP status for SSR routes

    Closes #3001.

    The dev server returned 404 GET / for the task-manager example even though SSR rendered the page successfully. Two related bugs collapsed three states into two:

    • matchForSSR() only set ctx.matchedRoutePatterns when a route matched, leaving it undefined for an unmatched URL — indistinguishable from "no router was rendered". Now it explicitly records [] for "router rendered, no match".
    • The vtz JS↔Rust bridge in persistent_isolate.rs serialized result.matchedRoutePatterns ?? null instead of || [], preserving the undefined-vs-empty distinction so the Rust handler can return 200 for routerless apps and 404 only when a router actually failed to match.

    Status mapping is now uniform across @vertz/ui-server's handlers and the vtz dev server: missing/null → 200, [] → 404, […] → 200.

  • #2955 2c1616c Thanks @viniciusdacal! - feat(client): type import.meta.main in vertz/client and @vertz/ui/client

    Closes #2811.

    Follow-up to #2777. The vtz runtime (via deno_core) already sets import.meta.main on every module — true for the entry module, false for imported modules — so the standard "run if main" idiom works without any polyfill:

    // src/api/server.ts
    const app = createServer({
      /* ... */
    });
    export default app;
    
    if (import.meta.main) app.listen(env.PORT);

    Previously the type was only available to projects that pulled in bun-types. The client augmentation (packages/ui/client.d.ts) now declares readonly main: boolean alongside hot, so any tsconfig that includes "types": ["vertz/client"] (or "@vertz/ui/client") gets it automatically. Scaffolded apps already have this entry.

    bun-types removed from sites/dev-orchestrator where it was only kept for this type.

  • #2990 e84adde Thanks @viniciusdacal! - feat(ui,ui-server)!: remove Suspense — use early-return guards for loading states

    Closes #2985.

    Suspense is removed from @vertz/ui. Vertz's reactivity model handles loading states via query().loading and the compiler-supported early-return guard pattern — if (q.loading) return <Loading/>; return <Real/> — which gives you a fully-typed q.data past the guard and avoids the Promise-throwing machinery Suspense inherits from React.

    Breaking changes

    • @vertz/uiSuspense and SuspenseProps are no longer exported. Replace with an early-return guard (see the new "Early return when you need loaded data" section in the data fetching guide).
    • @vertz/ui-servercreateSlotPlaceholder, resetSlotCounter, createTemplateChunk, and RenderToStreamOptions are removed. The internal __suspense VNode tag (never produced by any shipped code) is gone too. renderToStream(tree, options?) is now renderToStream(tree) — it walks the tree synchronously and serializes into a single HTML chunk.

    Cleanup

    • error-boundary-context.ts (the async-error handler stack used only by Suspense) is removed. ErrorBoundary keeps its synchronous try/catch + retry behavior unchanged.
  • Updated dependencies [4855184, 2840af4]:

    • @vertz/schema@0.2.80
    • @vertz/fetch@0.2.80

@vertz/ui-server@0.2.80

02 May 02:34
d8096ad

Choose a tag to compare

Patch Changes

  • #2965 06605b0 Thanks @viniciusdacal! - fix(compiler): disambiguate context stable ids when two createContext calls share a variable name in the same file

    Closes #2786.

    injectContextStableIds generated the id as {filePath}::{varName}. Two createContext() calls in the same file with the same variable name (e.g. an inlined/formatted pair on one line, or a code-generated module) produced the same id, so the runtime context registry silently returned the same object for both — breaking Provider/useContext pairing.

    Both the Rust transform (native/vertz-compiler-core/src/context_stable_ids.rs) and the TypeScript sibling (packages/ui-server/src/build-plugin/context-stable-ids.ts) now track a per-name occurrence counter and suffix @N on repeats. The first occurrence of a name keeps the original {filePath}::{varName} id (so existing single-context files are unchanged); the second becomes {filePath}::{varName}@1, the third @2, and so on.

    A per-name counter is used rather than a source span because counters only shift when contexts are added or removed, whereas spans shift on any edit to earlier code — counters are more HMR-stable.

  • #2990 e84adde Thanks @viniciusdacal! - feat(ui,ui-server)!: remove Suspense — use early-return guards for loading states

    Closes #2985.

    Suspense is removed from @vertz/ui. Vertz's reactivity model handles loading states via query().loading and the compiler-supported early-return guard pattern — if (q.loading) return <Loading/>; return <Real/> — which gives you a fully-typed q.data past the guard and avoids the Promise-throwing machinery Suspense inherits from React.

    Breaking changes

    • @vertz/uiSuspense and SuspenseProps are no longer exported. Replace with an early-return guard (see the new "Early return when you need loaded data" section in the data fetching guide).
    • @vertz/ui-servercreateSlotPlaceholder, resetSlotCounter, createTemplateChunk, and RenderToStreamOptions are removed. The internal __suspense VNode tag (never produced by any shipped code) is gone too. renderToStream(tree, options?) is now renderToStream(tree) — it walks the tree synchronously and serializes into a single HTML chunk.

    Cleanup

    • error-boundary-context.ts (the async-error handler stack used only by Suspense) is removed. ErrorBoundary keeps its synchronous try/catch + retry behavior unchanged.
  • Updated dependencies [4855184, 513fe1e, 2840af4, 5b3838b, 85707b2, 2c1616c, e84adde]:

    • @vertz/ui@0.2.80
    • @vertz/core@0.2.80

@vertz/ui-primitives@0.2.80

02 May 02:34
d8096ad

Choose a tag to compare

Patch Changes

@vertz/ui-canvas@0.2.80

02 May 02:35
d8096ad

Choose a tag to compare

Patch Changes

@vertz/ui-auth@0.2.80

02 May 02:34
d8096ad

Choose a tag to compare

Patch Changes

@vertz/tui@0.2.80

02 May 02:34
d8096ad

Choose a tag to compare

Patch Changes

@vertz/theme-shadcn@0.2.80

02 May 02:34
d8096ad

Choose a tag to compare

Patch Changes