mirror of
https://github.com/prdlk/dotfiles.git
synced 2026-11-06 04:27:40 +00:00
shell: fish functions -> ~/.local/bin executables + bash/zsh ports; manage ~/.agents
- 16 cross-shell utilities converted to sh/bash chezmoi executables: ai c2p cp-appid extract_agents fab gdc gh-sonr-org-migrate ghg-clone ghn gho git-summary launch_omp_livediff_overlay lc-elir mdcp mdp pj (ghg-clone not named ghg: unrelated cargo binary owns that name) - cwd/history helpers stay per-shell: yy c cd clip-last + ghg/ghn/gho cd-wrappers in bash/functions/ and zsh/functions/, loaded by rc files - fish keeps only shell-state functions; converted ones now hit PATH - cp-appid ignore gating moved to .local/bin path - dot_agents/skills: 82 skills materialized from dotstate storage so dot_claude skill symlinks resolve on fresh bootstrap; dotstate profile/symlinks.json released .agents/skills .bashrc .zshrc
This commit is contained in:
1 parent
6776366ce6
commit
6406208b35
637 files changed
+100297
-497
No files matched your search
@@ -0,0 +1,265 @@
|
||||
---
|
||||
name: react
|
||||
description: React renderer for json-render that turns JSON specs into React components. Use when working with @json-render/react, building React UIs from JSON, creating component catalogs, or rendering AI-generated specs.
|
||||
---
|
||||
|
||||
# @json-render/react
|
||||
|
||||
React renderer that converts JSON specs into React component trees.
|
||||
|
||||
## Quick Start
|
||||
|
||||
```typescript
|
||||
import { defineRegistry, Renderer } from "@json-render/react";
|
||||
import { catalog } from "./catalog";
|
||||
|
||||
const { registry } = defineRegistry(catalog, {
|
||||
components: {
|
||||
Card: ({ props, children }) => <div>{props.title}{children}</div>,
|
||||
},
|
||||
});
|
||||
|
||||
function App({ spec }) {
|
||||
return <Renderer spec={spec} registry={registry} />;
|
||||
}
|
||||
```
|
||||
|
||||
## Creating a Catalog
|
||||
|
||||
```typescript
|
||||
import { defineCatalog } from "@json-render/core";
|
||||
import { schema } from "@json-render/react/schema";
|
||||
import { defineRegistry } from "@json-render/react";
|
||||
import { z } from "zod";
|
||||
|
||||
// Create catalog with props schemas
|
||||
export const catalog = defineCatalog(schema, {
|
||||
components: {
|
||||
Button: {
|
||||
props: z.object({
|
||||
label: z.string(),
|
||||
variant: z.enum(["primary", "secondary"]).nullable(),
|
||||
}),
|
||||
description: "Clickable button",
|
||||
},
|
||||
Card: {
|
||||
props: z.object({ title: z.string() }),
|
||||
description: "Card container with title",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Define component implementations with type-safe props
|
||||
const { registry } = defineRegistry(catalog, {
|
||||
components: {
|
||||
Button: ({ props }) => (
|
||||
<button className={props.variant}>{props.label}</button>
|
||||
),
|
||||
Card: ({ props, children }) => (
|
||||
<div className="card">
|
||||
<h2>{props.title}</h2>
|
||||
{children}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
## Spec Structure (Element Tree)
|
||||
|
||||
The React schema uses an element tree format:
|
||||
|
||||
```json
|
||||
{
|
||||
"root": {
|
||||
"type": "Card",
|
||||
"props": { "title": "Hello" },
|
||||
"children": [
|
||||
{ "type": "Button", "props": { "label": "Click me" } }
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Visibility Conditions
|
||||
|
||||
Use `visible` on elements to show/hide based on state. New syntax: `{ "$state": "/path" }`, `{ "$state": "/path", "eq": value }`, `{ "$state": "/path", "not": true }`, `{ "$and": [cond1, cond2] }` for AND, `{ "$or": [cond1, cond2] }` for OR. Helpers: `visibility.when("/path")`, `visibility.unless("/path")`, `visibility.eq("/path", val)`, `visibility.and(cond1, cond2)`, `visibility.or(cond1, cond2)`.
|
||||
|
||||
## Providers
|
||||
|
||||
| Provider | Purpose |
|
||||
|----------|---------|
|
||||
| `StateProvider` | Share state across components (JSON Pointer paths). Accepts optional `store` prop for controlled mode. |
|
||||
| `ActionProvider` | Handle actions dispatched via the event system |
|
||||
| `VisibilityProvider` | Enable conditional rendering based on state |
|
||||
| `ValidationProvider` | Form field validation |
|
||||
|
||||
### External Store (Controlled Mode)
|
||||
|
||||
Pass a `StateStore` to `StateProvider` (or `JSONUIProvider` / `createRenderer`) to use external state management (Redux, Zustand, XState, etc.):
|
||||
|
||||
```tsx
|
||||
import { createStateStore, type StateStore } from "@json-render/react";
|
||||
|
||||
const store = createStateStore({ count: 0 });
|
||||
|
||||
<StateProvider store={store}>{children}</StateProvider>
|
||||
|
||||
// Mutate from anywhere — React re-renders automatically:
|
||||
store.set("/count", 1);
|
||||
```
|
||||
|
||||
When `store` is provided, `initialState` and `onStateChange` are ignored.
|
||||
|
||||
## Dynamic Prop Expressions
|
||||
|
||||
Any prop value can be a data-driven expression resolved by the renderer before components receive props:
|
||||
|
||||
- **`{ "$state": "/state/key" }`** - reads from state model (one-way read)
|
||||
- **`{ "$bindState": "/path" }`** - two-way binding: reads from state and enables write-back. Use on the natural value prop (value, checked, pressed, etc.) of form components.
|
||||
- **`{ "$bindItem": "field" }`** - two-way binding to a repeat item field. Use inside repeat scopes.
|
||||
- **Filtered lists**: `repeat` plus an `$item` visible condition on the same container renders only matching items: `{ "repeat": { "statePath": "/tasks", "key": "id" }, "visible": { "$item": "status", "eq": "todo" }, "children": ["task-card"] }`. AND-composed `$state` conjuncts gate the container shell; `$item`/`$index` conjuncts filter items.
|
||||
- **`{ "$cond": <condition>, "$then": <value>, "$else": <value> }`** - conditional value
|
||||
- **`{ "$template": "Hello, ${/name}!" }`** - interpolates state values into strings
|
||||
- **`{ "$computed": "fn", "args": { ... } }`** - calls registered functions with resolved args
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "Input",
|
||||
"props": {
|
||||
"value": { "$bindState": "/form/email" },
|
||||
"placeholder": "Email"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Components do not use a `statePath` prop for two-way binding. Use `{ "$bindState": "/path" }` on the natural value prop instead.
|
||||
|
||||
Components receive already-resolved props. For two-way bound props, use the `useBoundProp` hook with the `bindings` map the renderer provides.
|
||||
|
||||
Register `$computed` functions via the `functions` prop on `JSONUIProvider` or `createRenderer`:
|
||||
|
||||
```tsx
|
||||
<JSONUIProvider
|
||||
functions={{ fullName: (args) => `${args.first} ${args.last}` }}
|
||||
>
|
||||
```
|
||||
|
||||
## Event System
|
||||
|
||||
Components use `emit` to fire named events, or `on()` to get an event handle with metadata. The element's `on` field maps events to action bindings:
|
||||
|
||||
```tsx
|
||||
// Simple event firing
|
||||
Button: ({ props, emit }) => (
|
||||
<button onClick={() => emit("press")}>{props.label}</button>
|
||||
),
|
||||
|
||||
// Event handle with metadata (e.g. preventDefault)
|
||||
Link: ({ props, on }) => {
|
||||
const click = on("click");
|
||||
return (
|
||||
<a href={props.href} onClick={(e) => {
|
||||
if (click.shouldPreventDefault) e.preventDefault();
|
||||
click.emit();
|
||||
}}>{props.label}</a>
|
||||
);
|
||||
},
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "Button",
|
||||
"props": { "label": "Submit" },
|
||||
"on": { "press": { "action": "submit" } }
|
||||
}
|
||||
```
|
||||
|
||||
The `EventHandle` returned by `on()` has: `emit()`, `shouldPreventDefault` (boolean), and `bound` (boolean).
|
||||
|
||||
## State Watchers
|
||||
|
||||
Elements can declare a `watch` field (top-level, sibling of type/props/children) to trigger actions when state values change:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "Select",
|
||||
"props": { "value": { "$bindState": "/form/country" }, "options": ["US", "Canada"] },
|
||||
"watch": { "/form/country": { "action": "loadCities" } },
|
||||
"children": []
|
||||
}
|
||||
```
|
||||
|
||||
## Built-in Actions
|
||||
|
||||
The `setState`, `pushState`, `removeState`, and `validateForm` actions are built into the React schema and handled automatically by `ActionProvider`. They are injected into AI prompts without needing to be declared in catalog `actions`:
|
||||
|
||||
```json
|
||||
{ "action": "setState", "params": { "statePath": "/activeTab", "value": "home" } }
|
||||
{ "action": "pushState", "params": { "statePath": "/items", "value": { "text": "New" } } }
|
||||
{ "action": "removeState", "params": { "statePath": "/items", "index": 0 } }
|
||||
{ "action": "validateForm", "params": { "statePath": "/formResult" } }
|
||||
```
|
||||
|
||||
`validateForm` validates all registered fields and writes `{ valid, errors }` to state.
|
||||
|
||||
Note: `statePath` in action params (e.g. `setState.statePath`) targets the mutation path. Two-way binding in component props uses `{ "$bindState": "/path" }` on the value prop, not `statePath`.
|
||||
|
||||
## useBoundProp
|
||||
|
||||
For form components that need two-way binding, use `useBoundProp` with the `bindings` map the renderer provides when a prop uses `{ "$bindState": "/path" }` or `{ "$bindItem": "field" }`:
|
||||
|
||||
```tsx
|
||||
import { useBoundProp } from "@json-render/react";
|
||||
|
||||
Input: ({ element, bindings }) => {
|
||||
const [value, setValue] = useBoundProp<string>(
|
||||
element.props.value,
|
||||
bindings?.value
|
||||
);
|
||||
return (
|
||||
<input
|
||||
value={value ?? ""}
|
||||
onChange={(e) => setValue(e.target.value)}
|
||||
/>
|
||||
);
|
||||
},
|
||||
```
|
||||
|
||||
`useBoundProp(propValue, bindingPath)` returns `[value, setValue]`. The `value` is the resolved prop; `setValue` writes back to the bound state path (no-op if not bound).
|
||||
|
||||
## BaseComponentProps
|
||||
|
||||
For building reusable component libraries not tied to a specific catalog (e.g. `@json-render/shadcn`):
|
||||
|
||||
```typescript
|
||||
import type { BaseComponentProps } from "@json-render/react";
|
||||
|
||||
const Card = ({ props, children }: BaseComponentProps<{ title?: string }>) => (
|
||||
<div>{props.title}{children}</div>
|
||||
);
|
||||
```
|
||||
|
||||
## defineRegistry
|
||||
|
||||
`defineRegistry` conditionally requires the `actions` field only when the catalog declares actions. Catalogs with `actions: {}` can omit it.
|
||||
|
||||
## Key Exports
|
||||
|
||||
| Export | Purpose |
|
||||
|--------|---------|
|
||||
| `defineRegistry` | Create a type-safe component registry from a catalog |
|
||||
| `Renderer` | Render a spec using a registry |
|
||||
| `schema` | Element tree schema (includes built-in state actions: setState, pushState, removeState, validateForm) |
|
||||
| `useStateStore` | Access state context |
|
||||
| `useStateValue` | Get single value from state |
|
||||
| `useBoundProp` | Two-way binding for `$bindState`/`$bindItem` expressions |
|
||||
| `useActions` | Access actions context |
|
||||
| `useAction` | Get a single action dispatch function |
|
||||
| `useOptionalValidation` | Non-throwing variant of useValidation (returns null if no provider) |
|
||||
| `useUIStream` | Stream specs from an API endpoint |
|
||||
| `createStateStore` | Create a framework-agnostic in-memory `StateStore` |
|
||||
| `StateStore` | Interface for plugging in external state management |
|
||||
| `BaseComponentProps` | Catalog-agnostic base type for reusable component libraries |
|
||||
| `EventHandle` | Event handle type (`emit`, `shouldPreventDefault`, `bound`) |
|
||||
| `ComponentContext` | Typed component context (catalog-aware) |
|
||||
Reference in new issue
Block a user