29 Commits

Author SHA1 Message Date
Railly Hugo 0f6798b193 feat(react): support named slots (#320)
Co-authored-by: wotnak <wotnak@pm.me>
2026-08-14 22:22:49 -03:00
Railly Hugo 9f58a3cade feat(core): support nested repeats with item paths (#319)
Resolve nested repeat state paths consistently across every renderer, validator, schema, prompt, and documentation surface.

Fixes #252

Co-authored-by: Trevin Chow <trevin@trevinchow.com>
2026-08-13 16:32:33 -03:00
Railly Hugo 9d3dfc8917 feat(core): forward params to named onSuccess/onError actions (#307)
* feat(core): forward params to named onSuccess/onError actions

Named actions in onSuccess/onError only received their name, so params
never reached the handler. Forward the whole binding through the core
executor and every renderer bridge so a named handler receives params
the same way top-level bindings do.

Closes #301

* fix(core): forward params on the onError action form too

ActionOnErrorSchema omitted the optional params field, so onError
action params were silently stripped during Zod validation even though
the type allowed them. Mirror the onSuccess form and add schema-level
tests for both.

* fix(svelte): forward params to named onSuccess/onError actions

The Svelte ActionProvider bridge rebuilt the sub-binding from the name
only, dropping params, same gap as the other renderers. Its component
script block is type-checked more loosely, so it passed CI while wrong.
Forward the whole binding and add integration tests that named
onSuccess and onError handlers each receive their params.

* refactor(core): tighten executeAction context param to ActionBinding

Core always passes the resolved binding to the executeAction context
callback, so the string half of the union was false back-compat. Drop it
to (binding: ActionBinding): the runtime change becomes a compile error
for anyone with a custom renderer bridge instead of a silent break, and
every bridge collapses to execute(binding).
2026-07-08 20:20:54 -03:00
Chris Tate 4e4dc46a37 Fix/autofix dangling children (#300)
* autoFixSpec prunes children references to undefined elements

Dangling references are the dominant remaining first-attempt validation
failure in benchmarks, and models frequently fail to repair them even given
the exact error (observed: three repair turns, same dangling footer each
time). The renderer already skips missing children at runtime, so pruning
yields the identical rendered output while letting the spec validate. Each
removal is reported in fixes.

* Classify autoFixSpec fixes as lossy or lossless

Pruning a dangling child reference changes what renders; relocating a
misplaced field does not. Callers with a repair loop need to tell these
apart: accept lossless fixes silently, prefer re-prompting over lossy fixes,
and keep the lossy-fixed spec as a last resort. Adds fixDetails alongside the
existing fixes strings (additive, no signature break).

* Validate visible conditions in validateSpec; document filtered-list pattern

Malformed visible conditions (e.g. mixing $state and $item in one object)
silently evaluate to hidden at runtime: evaluateCondition dispatches on the
first recognized key and non-strict parsing strips the rest, so whole regions
of UI disappear with a valid-looking spec. Benchmarked worst case: a kanban
board that rendered zero task cards.

- core: VisibilityConditionStrictSchema (strict objects, exported)
- core: validateSpec rejects malformed visible with a repairable message
  listing the valid forms (code: invalid_visible)
- framework prompts: FILTERED LISTS rule showing the repeat + per-item
  visible pattern models keep reaching for and inventing syntax around

* Support filtered lists: repeat + $item visible on the same element

Models across vendors consistently write {repeat, visible: {$item: ...}} on
one container to mean a filtered list (kanban columns, status sections).
Previously $item had no meaning outside the repeat scope, the condition
evaluated false, and the whole region silently disappeared — the worst
visual failures in benchmarks were boards rendering zero cards this way.

Outside a repeat scope that spelling was always broken, so claiming it is
backward compatible: the renderer now applies such a condition per item,
preserving original indices for item state paths. Container-level $state
conditions and per-child $item conditions behave as before.

- core: conditionUsesItemScope helper (exported)
- react: RepeatChildren filters items by the container's $item condition
- framework prompts: FILTERED LISTS rule teaches the container spelling
- react: repeat-filter test suite (filtering, no-filter, $state container
  visibility, per-child $item)

Other framework renderers (vue, svelte, solid, react-native) still evaluate
the container condition outside scope and should adopt the same semantics.

* Validate repeat containers: require children and matching state arrays

Two silent empty-region failures seen repeatedly in benchmarks, both passing
validation today: a repeat element with no children (nothing to clone per
item) and a repeat statePath pointing at a missing or non-array state value.
Both now fail validateSpec with repairable messages (repeat_without_children,
repeat_state_mismatch). State checks only run when the spec provides state;
runtime-fed state is unaffected.

* Address review: lossy-aware hook repair, react-only filtered-list rule, reuse getByPath

- ink/react-native useUIStream repair loops no longer accept lossy autofixes
  unconditionally: lossless relocations apply immediately, pruned content
  holds back while retries remain (so validation fails and the model repairs
  the missing elements) and applies only as a last resort.
- FILTERED LISTS prompt rule removed from schemas whose renderers do not
  implement the per-item filter yet (everything except react). Renderer
  parity tracked as follow-up.
- repeat_state_mismatch validation reuses getByPath instead of a local JSON
  Pointer lookup that skipped ~0/~1 unescaping.

* Address review: split mixed repeat visibility, apply lossless fixes eagerly

- splitRepeatVisibility (core, exported): AND-composed conditions on a repeat
  container partition into a container gate ($state conjuncts, hides the
  shell) and a per-item filter ($item/$index conjuncts). Mixed $or cannot
  partition soundly and stays fully per-item, documented.
- react renderer uses the split, so {$and: [{$state gate}, {$item filter}]}
  hides the empty shell when the gate is false instead of rendering a husk.
- autoFixSpec gains { lossy?: boolean } (default true, additive): ink and
  react-native repair loops now apply lossless relocations immediately and
  withhold only the pruning until retries are exhausted, matching the stated
  intent.

* Address review: RN final-validation error path, repeat prune guard, docs

- react-native useUIStream mirrors ink: when retries are exhausted and the
  spec still fails validation, report through onError instead of silently
  calling onComplete with an invalid spec.
- autoFixSpec never prunes a repeat container to zero children; that would
  trade missing_child for repeat_without_children and leave the last-resort
  spec unrenderable. The dangling template reference stays visible to repair.
- Docs for the new surface: core README + skill (validateSpec issue codes,
  fixDetails, lossy option), react README + skill and web visibility docs
  (filtered-list pattern, mixed-condition splitting, framework support note).
2026-06-10 22:06:29 -05:00
Chris Tate c731a9c607 Fix visible validation depending on consumer zod version (#299)
* Fix visible validation depending on consumer zod version; strengthen children prompt rule

catalog.validate() behavior changed under consumers' zod resolution: z.any()
object keys are optional on zod 4.3 but nonoptional on zod 4.4+, so a spec
omitting an element's visible field validates on 4.3 and fails on 4.4. The
core test suite (zod 4.3.6) asserts the optional behavior, so optional is the
intent; pin it explicitly with .optional() so all zod 4.x agree.

children stays required (long-standing, zod-version-independent contract;
relaxing it would change InferSpec types for consumers). Instead the default
prompt rules now state explicitly that every element must include a children
array, with [] for leaves, which benchmarking shows models otherwise omit on
roughly a third of first attempts.

- core: InferSpecObject honors SchemaType.optional at the type level (additive;
  no schema used optional before)
- all framework schemas: visible marked ...s.optional()
- framework prompt defaultRules: REQUIRED FIELDS rule for children
- core: regression tests locking visible-optional and children-required

* Fix Next schema optional fields
2026-06-10 14:48:43 -05:00
Chris Tate 714c38f2b8 feat: add custom directives API and @json-render/directives package (#279)
* feat: add custom directives API and @json-render/directives package

Add a `defineDirective` API that lets users register custom `$`-prefixed
dynamic values with schemas, resolvers, and prompt instructions — extending
the spec language without forking core.

* fixes

* perf(core): optimize findDirective to iterate registry instead of object keys

Flip the loop from O(object-keys) to O(registry-size) by iterating
the directive registry and checking `key in value` rather than scanning
all object keys with Object.keys() and startsWith("$").

* fix(core): reject directive names that conflict with built-in keys

defineDirective now throws at registration time if the name collides
with a built-in prop expression key ($state, $cond, etc.), making the
precedence contract explicit rather than relying on check ordering in
resolvePropValue.

* fix(directives): handle future dates in $format and warn on $math NaN coercion

$format relative dates now support future timestamps ("2h from now")
and return "just now" for zero diff. $math emits a console.warn in
dev mode when a non-numeric value is silently coerced to 0.

* fix(directives): remove process.env check that breaks DTS build

The directives package doesn't include @types/node, so referencing
process.env fails during tsup's DTS generation. The console.warn is
unconditional now — it only fires on actual misuse (non-numeric input)
so the cost is negligible.

* feat(directives): rename prompt to description, auto-describe schemas in prompt, add docs

- Rename `prompt` to `description` on DirectiveDefinition — a short
  behavioral label rather than the full AI prompt blob
- Auto-generate directive schema signatures in the system prompt using
  formatZodType, so the AI always sees every field, type, and optionality
- Add docs: guide page, API reference page, nav/title entries, docs-chat

* feat(directives): add standardDirectives export and composition hint

Export a pre-assembled standardDirectives array (all 7 non-factory
directives) for convenience. Add a composition hint to the generated
AI prompt so agents know directives can nest inside each other.

* docs: add directives skill, README entry, and docs-chat listing

- Add skills/directives/SKILL.md with full directive API reference
- Add @json-render/directives row to root README packages table
- Add "directives" to the Available skills list in docs-chat prompt

* perf(core): skip Zod parse in directive hot path

Resolvers are already defensive (coercion, fallbacks, switch defaults),
so runtime validation on every render adds overhead without safety.
The schema remains used for prompt generation and TypeScript inference.
2026-05-06 22:30:24 -05:00
Chris Tate ad0be0efc9 devtools (#273) 2026-04-17 14:29:04 -05:00
Chris Tate 30424659d8 test(core): add unit tests for Zod 4 record, default, and literal formatting (#272)
Cover the three type cases fixed in #239 to prevent regressions.
2026-04-16 09:18:23 -05:00
Matt Van Horn a7689129db fix(core): handle Zod 4 record, default, and literal types in formatZodType (#239)
Add ZodRecord and ZodDefault cases to formatZodType() in both core and
yaml packages. Fix ZodLiteral to support Zod 4's def.values array
in addition to Zod 3's def.value.

Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 08:53:07 -05:00
Chris Tate ee596e4901 improve output (#268)
* improve output

* fixes
2026-04-10 22:20:34 -05:00
Laurens van der Helm e16c5ef477 feat: add yaml format support to buildUserPrompt (#223) 2026-03-17 13:40:28 -05:00
Chris Tate dc489601be add yaml wire format and universal edit modes (#219)
* add yaml wire format and universal edit modes

**Summary**

- Add `@json-render/yaml` package with streaming parser, YAML prompt generation, and AI SDK transform
- Add universal edit modes (patch, merge, diff) to `@json-render/core`, usable across both JSONL and YAML formats
- Integrate YAML support and edit mode selection into the web playground with format toggle, token usage display, and prompt caching

* improvements

* fixes

* fixes ci

* docs
2026-03-13 13:37:21 -05:00
Chris Tate c180f529a0 feat: add strict mode to jsonSchema() for LLM structured outputs (#193)
* feat: add strict mode to jsonSchema() for LLM structured outputs

This PR adds support for generating strict JSON schemas compatible with LLM structured output APIs (OpenAI, Google Gemini, Anthropic, etc.).

## Problem
`catalog.jsonSchema()` was generating schemas that OpenAI's strict mode rejected due to:
- Use of `propertyNames` keyword (not permitted)
- Missing `additionalProperties: false` on nested objects
- Optional properties not being handled correctly for strict validation

## Changes
- Added `JsonSchemaOptions` interface with `strict` boolean flag
- Updated `catalog.jsonSchema()` to accept optional `JsonSchemaOptions` parameter
- When `strict: true`, the schema generator:
  - Sets `additionalProperties: false` on all object types
  - Removes `propertyNames` constraints
  - Lists all properties (including optional ones) in `required` arrays, using nullable types for optionals
  - Converts record types to fixed-key objects without additional property schemas
- Fixed Zod 4 compatibility issue in type name resolution
- Added comprehensive test suite covering all strict mode requirements
- Exported `JsonSchemaOptions` type from core package

## Usage
```typescript
// Default behavior (unchanged)
const schema = catalog.jsonSchema();

// Strict mode for LLM APIs
const strictSchema = catalog.jsonSchema({ strict: true });
```

The default behavior remains unchanged to maintain backward compatibility.

Fixes #180

* fix: document record type limitation and add anyOf nullable test

- Add clear JSDoc documenting that record/map types become opaque objects
  in strict mode (LLM strict schemas require additionalProperties: false)
- Improve inline comment on the record case in zodToJsonSchema
- Add test exercising the anyOf nullable wrapping for optional properties
  using a non-record schema so the path is directly verified

---------

Co-authored-by: ctate <366502+ctate@users.noreply.github.com>
2026-03-11 22:17:09 -05:00
Chris Tate dc9e9d17f3 rename modes (#197)
* rename modes

* improvements

* fixes

* fix homepage
2026-03-10 15:07:47 -05:00
Chris Tate ea47b66dfc add dynamic forms support: computed values, watchers, cross-field validation (#156)
* add dynamic forms support: computed values, watchers, cross-field validation

- `$computed` and `$template` prop expressions for derived values and string interpolation
- Element-level `watch` field for cascading state dependencies
- Cross-field validators (`lessThan`, `greaterThan`, `equalTo`, `requiredIf`) with deep arg resolution
- `validateForm` built-in action for form-level validation

* fixes

* fixes

* fixes

* fixes

* fixes

* fixes

* fixes

* tests

* fixes

* update turbo

* tests

* fixes

* fixes

* fixes
2026-02-24 23:04:48 -06:00
Juzisuan965 7de08ccdf7 fix: safely resolve inner type for Zod arrays (#140) 2026-02-24 00:02:04 -06:00
Chris Tate 3201854481 external store adapter for state management (#139)
* external store adapter for state management

Introduces a `StateStore` interface that lets users plug in their own state management (Redux, Zustand, XState, etc.) instead of being locked into the internal `useState`-based store.

- Added `StateStore` interface and `createStateStore()` factory to `@json-render/core`
- `StateProvider`, `JSONUIProvider`, and `createRenderer` now accept an optional `store` prop for controlled mode
- When `store` is provided, it becomes the single source of truth (`initialState`/`onStateChange` are ignored)
- When `store` is omitted, everything works exactly as before (fully backward compatible)
- Applied across all platform packages: react, react-native, react-pdf

* improvements

* update docs

* improvements

* fixes

* fix CI

* add store adapters

* fixes

* fixes

* fixes

* fixes

* e2e tests

* improvements

* fixes

* fixes

* fixes

* fixes

* update lockfile for widened react peer deps

* fix dashboard build
2026-02-23 23:12:57 -06:00
Chris Tate 320c935bfb shadcn (#115)
* shadcn

* fix build error

* fix readme

* fix CI error

* improvements

* fixes

* stronger types

* fix interleave

* fixes

* on arg

* minor fixes

* fix lock
2026-02-17 01:28:04 -06:00
Chris Tate ddae61805e inline mode (#107)
* ai sdk

* chat

* move more to lib

* fixes

* refactor

* fixes

* streamdown

* fixes

* fixes

* fixes

* fixes

* fixes

* fixes

* fixes

* fixes

* fixes

* fixes

* data-spec

* fixes

* sortable

* github

* tools

* fixes

* fixes

* fixes

* fixes

* fixes

* fixes

* fixes

* fixes

* fixes

* fixes

* fixes

* fixes

* fixes

* fixes

* fixes

* tables

* fixes

* fixes

* rename to chat

* fixes docs

* fixes

* fixes

* fixes

* fixes

* fixes

* fixes

* fixes

* fixes

* fixes

* fixes

* fix lint

* fixes

* fixes

* fixes

* fixes

* fixes

* fixes

* fixes

* fixes

* fixes

* fixes

* fixes
2026-02-13 17:53:03 -06:00
Chris Tate 0e16ca33d4 Fix LLM hallucinations by dynamically generating prompt examples from catalog (#89)
* smarter schema

* better examples
2026-02-09 08:37:03 -06:00
Chris Tate 801708a128 react-native and other things (#81)
* react native

* fixes

* pop/push

* fixes

* rename data -> state

* remove . tsbuildinfo

* fixes

* fixes

* user prompt

* fixes

* $id

* combine catalog.ts

* better actions

* repeat

* fix docs

* fixes

* fixes

* fixes

* better stream

* catalog

* fixes

* fixes

* fixes

* dialog

* sonner

* accordion

* catalog on homepage

* fixes

* more catalog

* fixes

* fixes

* refactor

* mv

* 404

* fixes

* nested

* fixes

* fixes

* fixes

* fixes

* mobile playground

* mdx

* fix mdx

* fixes

* streamdown

* fixes

* use haiku

* fixes

* great

* fixes

* fix ...

* fixes

* fixes

* fix build

* fix lint

* fix lint

* fix lint

* fix tests
2026-02-09 01:31:46 -06:00
Chris Tate 1eb6212dd7 fix json patch (#78) 2026-02-07 12:09:53 -06:00
Chris Tate 711e79b069 remove key/parentKey from flat specs (#77) 2026-02-07 11:46:16 -06:00
Chris Tate d38b281e5e include remove op in system prompt (#73) 2026-02-06 17:56:03 -06:00
Chris Tate c1b1aaaddb custom schema system (#56)
* add rate limits

* fix lint

* better

generate prompt

update examples

* fixes

* fixes

* new api

* update docs

* fixes

* fixes

* generated prompt

* Add tests for catalog validation and prompt generation

- Test generateSystemPrompt with components, actions, custom rules
- Test new defineCatalog API from schema system
- Test catalog.prompt() method with custom rules
- Test catalog.validate() for valid and invalid specs
- Test catalog.zodSchema() for custom validation
- Test catalog.jsonSchema() for structured outputs
- Add tests for nested specs with children
- Add tests for rejecting invalid component types

* Fix lint: pass children as nested elements in renderer

Change from children={...} prop to nested children pattern
to satisfy react/no-children-prop rule.

* Fix lint errors in dashboard and web apps

- Disable react/prop-types in both eslint configs (TypeScript handles this)
- Allow styled-jsx 'jsx' property in dashboard
- Add DATABASE_URL to turbo env allowlist
- Remove unused drizzle-orm imports (and, sql)
- Suppress unused variable warnings where intentional

* Add server entry point for @json-render/remotion

The main package entry includes React components that require
client-side context (React.createContext). This causes build
failures when importing in server-side API routes.

Added `@json-render/remotion/server` entry point that exports
only schema and catalog definitions without React dependencies:
- schema, RemotionSchema, RemotionSpec
- standardComponentDefinitions, standardTransitionDefinitions
- standardEffectDefinitions
- Type exports for catalogs

Updated remotion example to import from /server in catalog.ts

* Make dashboard database connection lazy-initialized

The database connection threw an error at module load time if
DATABASE_URL was not set, causing builds to fail in CI where
the database is not available.

Changed to lazy initialization using a Proxy so the error only
occurs when the database is actually used at runtime, not at
build time.

* Fix previousSpec property name and lazy rate limiting

- Fix property name mismatch: playground now passes previousSpec
  instead of previousTree to match what useUIStream hook expects
- Make rate limiting lazy-initialized to avoid runtime errors when
  Redis env vars (KV_REST_API_URL, KV_REST_API_TOKEN) are not set
- Rate limiting gracefully becomes a no-op when Redis is unavailable
2026-02-05 03:59:56 -06:00
Chris Tate 3141cc1881 upgrade to zod 4 2026-01-14 18:43:20 -06:00
Chris Tate 1336489352 add unit tests 2026-01-14 18:38:50 -06:00
Chris Tate a6bf3ef3da format 2026-01-14 18:19:57 -06:00
Chris Tate 7663fb22b0 init 2026-01-14 12:43:38 -06:00