Compare commits

..
Author SHA1 Message Date
Chris Tate 72568f990e Prepare v0.21.1 release (#394) 2026-10-13 13:04:29 -05:00
Railly HugoandTrevin Chow 5473fe1314 fix(core): infer s.any() fields as any (#390)
* fix(core): infer s.any() fields as any

InferSpecField mapped SchemaType<"any"> to unknown, so catalog.validate() data with a visible: s.any() field was not assignable to Spec and needed an as unknown as Spec cast.

Co-Authored-By: Trevin Chow <trevin@trevinchow.com>

* ci(core): type-check the schema inference test

core had no check-types script, so turbo skipped it and reverting the s.any() inference fix still passed CI. A focused tsconfig checks src/schema.test.ts; the full core project still fails tsc on main (TS6059, test-only errors).

---------

Co-authored-by: Trevin Chow <trevin@trevinchow.com>
2026-10-12 15:02:47 -03:00
Railly Hugoandvitalii.semianchuk 85a7fa0091 fix(core): reject partial and empty numeric strings (#389)
parseFloat accepted "123abc" as 123, and Number() alone treats "" and whitespace as 0. Require a non-empty string that Number() parses fully.

Co-authored-by: vitalii.semianchuk <fix20152@gmail.com>
2026-10-12 15:02:28 -03:00
Railly Hugo fc2a696a50 docs: prepare native WebMCP migration (#363)
* docs: prepare native WebMCP migration

* docs: update Geistdocs to 2.7.5
2026-10-01 14:18:31 -03:00
37 changed files with 117 additions and 36 deletions
+23 -2
View File
@@ -1,9 +1,31 @@
# Changelog
## 0.21.0
## 0.21.1
<!-- release:start -->
### Bug Fixes
- **Dotted form values:** Preserve literal emails, URLs, and version strings in form value lookups instead of treating them as missing state paths (#353)
- **Numeric validation:** Reject partially numeric strings such as `"123abc"`, empty strings, and whitespace-only strings instead of accepting them as numbers (#389)
- **Schema type inference:** Infer `s.any()` fields as `any`, matching `z.any()`, so validated specs remain assignable to `Spec` without casts (#390)
### Documentation
- Migrated the **docs site** to Geistdocs while preserving public URLs, page content, and anchors (#340)
- Updated **native WebMCP discovery and search** with the Geistdocs MCP adapter (#363)
### Contributors
- @ctate
- @Railly
- @fix2015
- @kevin9327
- @tmchow
<!-- release:end -->
## 0.21.0
### New Features
- **TanStack Start renderer:** Added `@json-render/tanstack-start` for JSON-defined applications with file-based routes, reusable layouts, SSR loaders, head metadata, prerender paths, client navigation, and route fallbacks (#334)
@@ -19,7 +41,6 @@
- @ctate
- @Railly
<!-- release:end -->
## 0.20.0
+22
View File
@@ -4,6 +4,28 @@ title: "Changelog"
Notable changes and updates to json-render.
## v0.21.1
October 13, 2026
### Fixed: Dotted Form Values
Form value lookups now preserve literal emails, URLs, and version strings instead of treating them as missing state paths.
### Fixed: Numeric Validation
The `numeric` validator now rejects partially numeric strings such as `"123abc"`, empty strings, and whitespace-only strings instead of accepting them as numbers.
### Fixed: Schema Type Inference
`s.any()` fields now infer as `any`, matching `z.any()`, so validated specs remain assignable to `Spec` without casts.
### Improved: Documentation
Migrated the docs site to Geistdocs while preserving public URLs, page content, and anchors. Updated native WebMCP discovery and search with the Geistdocs MCP adapter.
---
## v0.21.0
September 18, 2026
+3 -3
View File
@@ -2139,9 +2139,9 @@
"title": "Changelog",
"metadataTitle": "Changelog | json-render",
"prefix": "import { pageMetadata } from \"@/lib/page-metadata\"\nexport const metadata = pageMetadata(\"docs/changelog\")\n\n# Changelog\n",
"sourceSha256": "645da4f33e85ac7bcb1642100bb0bde339dd2976c29f8501317f296c308e645e",
"bodySha256": "54a0b41153dd3afd0a0c69ef4c19575978615420eaf15075952b5c317b6a6b1c",
"markdownSha256": "73fa2d642e3235a7841b196625b948db486473c24c41deec1dbba07583706e2b",
"sourceSha256": "ad86126e05152567880b659abff2b27d88cc7b696d27b8c6640dda13c38dc9c3",
"bodySha256": "586921975f057696b57045a41fbdeba5d4f469a48592ea604f0262d65be3b74b",
"markdownSha256": "3836699e187adc31520d62b168b1f921b7f9df4272798ebc28d90bc76272b063",
"headings": [
{
"title": "v0.21.0",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@json-render/codegen",
"version": "0.21.0",
"version": "0.21.1",
"license": "Apache-2.0",
"description": "Utilities for generating code from json-render UI trees",
"keywords": [
+2 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@json-render/core",
"version": "0.21.0",
"version": "0.21.1",
"license": "Apache-2.0",
"description": "JSON becomes real things. Define your catalog, register your components, let AI generate.",
"keywords": [
@@ -47,6 +47,7 @@
"scripts": {
"build": "tsup",
"dev": "tsup --watch",
"check-types": "tsc -p tsconfig.check.json",
"typecheck": "tsc --noEmit"
},
"dependencies": {
+16
View File
@@ -1,6 +1,7 @@
import { describe, it, expect } from "vitest";
import { z } from "zod";
import { defineSchema, defineCatalog } from "./schema";
import type { Spec } from "./types";
// =============================================================================
// Shared test schema (mirrors the React schema shape)
@@ -565,6 +566,21 @@ describe("catalog.validate", () => {
actions: {},
});
it("returns data assignable to Spec", () => {
const result = catalog.validate({
root: "card-1",
elements: {
"card-1": { type: "Card", props: { title: "Hello" }, children: [] },
},
});
expect(result.success).toBe(true);
if (result.success && result.data) {
// s.any() fields such as `visible` must not infer as unknown.
const spec: Spec = result.data;
expect(spec.root).toBe("card-1");
}
});
it("validates a valid spec", () => {
const spec = {
root: "card-1",
+1 -1
View File
@@ -355,7 +355,7 @@ type InferSpecField<T, TCatalog> =
: T extends SchemaType<"propsOf", infer Path>
? InferPropsOfType<Path, TCatalog>
: T extends SchemaType<"any">
? unknown
? any
: unknown;
type InferRefType<Path, TCatalog> = Path extends "catalog.components"
+10
View File
@@ -152,6 +152,16 @@ describe("builtInValidationFunctions", () => {
expect(builtInValidationFunctions.numeric(NaN)).toBe(false);
expect(builtInValidationFunctions.numeric(null)).toBe(false);
});
it("fails for strings with trailing non-numeric characters", () => {
expect(builtInValidationFunctions.numeric("123abc")).toBe(false);
expect(builtInValidationFunctions.numeric("99bottles")).toBe(false);
});
it("fails for empty and whitespace-only strings", () => {
expect(builtInValidationFunctions.numeric("")).toBe(false);
expect(builtInValidationFunctions.numeric(" ")).toBe(false);
});
});
describe("url", () => {
+3 -1
View File
@@ -153,7 +153,9 @@ export const builtInValidationFunctions: Record<string, ValidationFunction> = {
*/
numeric: (value: unknown) => {
if (typeof value === "number") return !isNaN(value);
if (typeof value === "string") return !isNaN(parseFloat(value));
if (typeof value === "string") {
return value.trim() !== "" && !isNaN(Number(value));
}
return false;
},
+9
View File
@@ -0,0 +1,9 @@
{
// Type-checks the tests that assert inferred types. The full core
// project is not type-checked in CI yet.
"extends": "./tsconfig.json",
"compilerOptions": {
"noEmit": true
},
"include": ["src/schema.test.ts"]
}
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@json-render/devtools-react",
"version": "0.21.0",
"version": "0.21.1",
"license": "Apache-2.0",
"description": "React adapter for @json-render/devtools. Drop-in <JsonRenderDevtools /> component.",
"keywords": [
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@json-render/devtools-solid",
"version": "0.21.0",
"version": "0.21.1",
"license": "Apache-2.0",
"description": "SolidJS adapter for @json-render/devtools. Drop-in <JsonRenderDevtools /> component.",
"keywords": [
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@json-render/devtools-svelte",
"version": "0.21.0",
"version": "0.21.1",
"license": "Apache-2.0",
"description": "Svelte adapter for @json-render/devtools. Drop-in <JsonRenderDevtools /> component.",
"keywords": [
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@json-render/devtools-vue",
"version": "0.21.0",
"version": "0.21.1",
"license": "Apache-2.0",
"description": "Vue adapter for @json-render/devtools. Drop-in <JsonRenderDevtools /> component.",
"keywords": [
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@json-render/devtools",
"version": "0.21.0",
"version": "0.21.1",
"license": "Apache-2.0",
"description": "Framework-agnostic devtools core for json-render: event store, panel UI, picker, stream taps.",
"keywords": [
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@json-render/directives",
"version": "0.21.0",
"version": "0.21.1",
"license": "Apache-2.0",
"description": "Pre-built directives for @json-render/core — $format, $math, $concat, $count, $truncate, $pluralize, $join, and $t (i18n).",
"keywords": [
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@json-render/image",
"version": "0.21.0",
"version": "0.21.1",
"license": "Apache-2.0",
"description": "Image renderer for @json-render/core. JSON becomes SVG and PNG images via Satori.",
"keywords": [
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@json-render/ink",
"version": "0.21.0",
"version": "0.21.1",
"license": "Apache-2.0",
"description": "Ink terminal renderer for @json-render/core. JSON becomes terminal UIs.",
"keywords": [
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@json-render/jotai",
"version": "0.21.0",
"version": "0.21.1",
"license": "Apache-2.0",
"description": "Jotai adapter for json-render StateStore",
"keywords": [
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@json-render/mcp",
"version": "0.21.0",
"version": "0.21.1",
"license": "Apache-2.0",
"description": "MCP Apps integration for @json-render/core. Serve json-render UIs as interactive MCP Apps in Claude, ChatGPT, Cursor, and VS Code.",
"keywords": [
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@json-render/next",
"version": "0.21.0",
"version": "0.21.1",
"license": "Apache-2.0",
"description": "Next.js renderer for @json-render/core. JSON becomes full Next.js applications with routes, layouts, metadata, and SSR.",
"keywords": [
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@json-render/react-email",
"version": "0.21.0",
"version": "0.21.1",
"license": "Apache-2.0",
"description": "React Email renderer for @json-render/core. JSON becomes HTML emails.",
"keywords": [
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@json-render/react-native",
"version": "0.21.0",
"version": "0.21.1",
"license": "Apache-2.0",
"description": "React Native renderer for @json-render/core. JSON becomes React Native components.",
"keywords": [
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@json-render/react-pdf",
"version": "0.21.0",
"version": "0.21.1",
"license": "Apache-2.0",
"description": "React PDF renderer for @json-render/core. JSON becomes PDF documents.",
"keywords": [
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@json-render/react-three-fiber",
"version": "0.21.0",
"version": "0.21.1",
"license": "Apache-2.0",
"description": "React Three Fiber renderer for @json-render/core. JSON becomes 3D scenes.",
"keywords": [
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@json-render/react",
"version": "0.21.0",
"version": "0.21.1",
"license": "Apache-2.0",
"description": "React renderer for @json-render/core. JSON becomes React components.",
"keywords": [
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@json-render/redux",
"version": "0.21.0",
"version": "0.21.1",
"license": "Apache-2.0",
"description": "Redux adapter for json-render StateStore",
"keywords": [
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@json-render/remotion",
"version": "0.21.0",
"version": "0.21.1",
"license": "Apache-2.0",
"description": "Remotion renderer for @json-render/core. JSON becomes video compositions.",
"keywords": [
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@json-render/shadcn-svelte",
"version": "0.21.0",
"version": "0.21.1",
"license": "Apache-2.0",
"description": "shadcn-svelte component library for @json-render/svelte. JSON becomes beautiful Tailwind-styled Svelte components.",
"keywords": [
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@json-render/shadcn",
"version": "0.21.0",
"version": "0.21.1",
"license": "Apache-2.0",
"description": "shadcn/ui component library for @json-render/core. JSON becomes beautiful Tailwind-styled React components.",
"keywords": [
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@json-render/solid",
"version": "0.21.0",
"version": "0.21.1",
"license": "Apache-2.0",
"description": "SolidJS renderer for @json-render/core. JSON becomes Solid components.",
"keywords": [
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@json-render/svelte",
"version": "0.21.0",
"version": "0.21.1",
"license": "Apache-2.0",
"description": "Svelte 5 renderer for @json-render/core. JSON becomes Svelte components.",
"keywords": [
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@json-render/tanstack-start",
"version": "0.21.0",
"version": "0.21.1",
"license": "Apache-2.0",
"description": "TanStack Start renderer for @json-render/core. JSON becomes full TanStack Start applications with routes, layouts, head metadata, and SSR.",
"keywords": [
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@json-render/vue",
"version": "0.21.0",
"version": "0.21.1",
"license": "Apache-2.0",
"description": "Vue renderer for @json-render/core. JSON becomes Vue components.",
"keywords": [
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@json-render/xstate",
"version": "0.21.0",
"version": "0.21.1",
"license": "Apache-2.0",
"description": "XState Store adapter for json-render StateStore",
"keywords": [
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@json-render/yaml",
"version": "0.21.0",
"version": "0.21.1",
"license": "Apache-2.0",
"description": "YAML wire format for @json-render/core. Progressive rendering and surgical edits via streaming YAML.",
"keywords": [
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@json-render/zustand",
"version": "0.21.0",
"version": "0.21.1",
"license": "Apache-2.0",
"description": "Zustand adapter for json-render StateStore",
"keywords": [