@barefootjs/cli 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/dist/docs/core/README.md +84 -0
  2. package/dist/docs/core/adapters/adapter-architecture.md +262 -0
  3. package/dist/docs/core/adapters/csr.md +78 -0
  4. package/dist/docs/core/adapters/custom-adapter.md +357 -0
  5. package/dist/docs/core/adapters/go-template-adapter.md +269 -0
  6. package/dist/docs/core/adapters/hono-adapter.md +199 -0
  7. package/dist/docs/core/adapters.md +37 -0
  8. package/dist/docs/core/advanced/code-splitting.md +142 -0
  9. package/dist/docs/core/advanced/compiler-internals.md +331 -0
  10. package/dist/docs/core/advanced/error-codes.md +261 -0
  11. package/dist/docs/core/advanced/ir-schema.md +65 -0
  12. package/dist/docs/core/advanced/performance.md +115 -0
  13. package/dist/docs/core/advanced/xyflow-browser-bundle.md +69 -0
  14. package/dist/docs/core/advanced.md +11 -0
  15. package/dist/docs/core/components/children-slots.md +150 -0
  16. package/dist/docs/core/components/component-authoring.md +207 -0
  17. package/dist/docs/core/components/context-api.md +236 -0
  18. package/dist/docs/core/components/portals.md +192 -0
  19. package/dist/docs/core/components/props-type-safety.md +97 -0
  20. package/dist/docs/core/components/styling.md +37 -0
  21. package/dist/docs/core/components.md +19 -0
  22. package/dist/docs/core/core-concepts/ai-native.md +83 -0
  23. package/dist/docs/core/core-concepts/backend-freedom.md +31 -0
  24. package/dist/docs/core/core-concepts/how-it-works.md +147 -0
  25. package/dist/docs/core/core-concepts/mpa-style.md +36 -0
  26. package/dist/docs/core/core-concepts/reactivity.md +35 -0
  27. package/dist/docs/core/core-concepts.md +28 -0
  28. package/dist/docs/core/introduction.md +87 -0
  29. package/dist/docs/core/llms.txt +53 -0
  30. package/dist/docs/core/quick-start.mdx +160 -0
  31. package/dist/docs/core/reactivity/create-effect.md +125 -0
  32. package/dist/docs/core/reactivity/create-memo.md +91 -0
  33. package/dist/docs/core/reactivity/create-signal.md +128 -0
  34. package/dist/docs/core/reactivity/on-cleanup.md +67 -0
  35. package/dist/docs/core/reactivity/on-mount.md +70 -0
  36. package/dist/docs/core/reactivity/props-reactivity.md +91 -0
  37. package/dist/docs/core/reactivity/untrack.md +69 -0
  38. package/dist/docs/core/reactivity.md +28 -0
  39. package/dist/docs/core/rendering/client-directive.md +82 -0
  40. package/dist/docs/core/rendering/fragment.md +43 -0
  41. package/dist/docs/core/rendering/jsx-compatibility.md +163 -0
  42. package/dist/docs/core/rendering.md +14 -0
  43. package/dist/index.js +25490 -0
  44. package/dist/tokens.json +74 -0
  45. package/package.json +37 -0
@@ -0,0 +1,236 @@
1
+ ---
2
+ title: Context API
3
+ description: Share state with deeply nested children without prop drilling using createContext and useContext.
4
+ ---
5
+
6
+ # Context API
7
+
8
+ Context shares state with deeply nested children without prop drilling. It is the foundation of compound components (Dialog, Accordion, Tabs).
9
+
10
+ ```tsx
11
+ "use client"
12
+ import { createContext, useContext } from '@barefootjs/client'
13
+ ```
14
+
15
+
16
+ ## `createContext`
17
+
18
+ Creates a new context with an optional default value.
19
+
20
+ ```tsx
21
+ const MyContext = createContext<T>(defaultValue?: T)
22
+ ```
23
+
24
+ **Type:**
25
+
26
+ ```tsx
27
+ type Context<T> = {
28
+ readonly id: symbol
29
+ readonly defaultValue: T | undefined
30
+ readonly Provider: (props: { value: T; children?: unknown }) => unknown
31
+ }
32
+ ```
33
+
34
+
35
+ ## `Context.Provider`
36
+
37
+ Provides a value to all descendants. Components inside the provider tree read it with `useContext`.
38
+
39
+ ```tsx
40
+ <MyContext.Provider value={someValue}>
41
+ {props.children}
42
+ </MyContext.Provider>
43
+ ```
44
+
45
+ The compiler transforms this into a `provideContext()` call. The value is set synchronously before children initialize.
46
+
47
+
48
+ ## `useContext`
49
+
50
+ Reads the current value from a context.
51
+
52
+ ```tsx
53
+ const value = useContext(MyContext)
54
+ ```
55
+
56
+ **Behavior:**
57
+
58
+ - If a `Provider` ancestor exists, returns the provided value
59
+ - If no `Provider` exists and a default value was passed to `createContext`, returns the default
60
+ - Otherwise returns `undefined` — guard with optional chaining (`store?.value`)
61
+
62
+
63
+ ## Basic Example
64
+
65
+ ```tsx
66
+ "use client"
67
+ import { createContext, useContext } from '@barefootjs/client'
68
+
69
+ // 1. Create the context
70
+ const ThemeContext = createContext<'light' | 'dark'>('light')
71
+
72
+ // 2. Provider component
73
+ export function ThemeProvider(props: { theme: 'light' | 'dark'; children?: Child }) {
74
+ return (
75
+ <ThemeContext.Provider value={props.theme}>
76
+ {props.children}
77
+ </ThemeContext.Provider>
78
+ )
79
+ }
80
+
81
+ // 3. Consumer component
82
+ export function ThemedButton(props: { children?: Child }) {
83
+ const handleMount = (el: HTMLButtonElement) => {
84
+ const theme = useContext(ThemeContext)
85
+ el.className = theme === 'dark' ? 'btn-dark' : 'btn-light'
86
+ }
87
+
88
+ return <button ref={handleMount}>{props.children}</button>
89
+ }
90
+ ```
91
+
92
+ ```tsx
93
+ // Usage
94
+ <ThemeProvider theme="dark">
95
+ <ThemedButton>Click me</ThemedButton> {/* Gets dark styling */}
96
+ </ThemeProvider>
97
+ ```
98
+
99
+
100
+ ## Compound Components
101
+
102
+ A group of related components sharing internal state. The root provides state; sub-components consume it.
103
+
104
+ ### Example: Accordion
105
+
106
+ ```tsx
107
+ "use client"
108
+ import { createSignal, createContext, useContext, createEffect } from '@barefootjs/client'
109
+
110
+ // Context type
111
+ interface AccordionContextValue {
112
+ activeItem: () => string | null
113
+ toggle: (id: string) => void
114
+ }
115
+
116
+ // Create context
117
+ const AccordionContext = createContext<AccordionContextValue>()
118
+
119
+ // Root component — provides state
120
+ function Accordion(props: { children?: Child }) {
121
+ const [activeItem, setActiveItem] = createSignal<string | null>(null)
122
+
123
+ const toggle = (id: string) => {
124
+ setActiveItem(prev => prev === id ? null : id)
125
+ }
126
+
127
+ return (
128
+ <AccordionContext.Provider value={{ activeItem, toggle }}>
129
+ <div data-slot="accordion">{props.children}</div>
130
+ </AccordionContext.Provider>
131
+ )
132
+ }
133
+
134
+ // Trigger — toggles the active item
135
+ function AccordionTrigger(props: { itemId: string; children?: Child }) {
136
+ const handleMount = (el: HTMLButtonElement) => {
137
+ const ctx = useContext(AccordionContext)
138
+
139
+ el.addEventListener('click', () => {
140
+ ctx.toggle(props.itemId)
141
+ })
142
+
143
+ createEffect(() => {
144
+ const isOpen = ctx.activeItem() === props.itemId
145
+ el.setAttribute('aria-expanded', String(isOpen))
146
+ })
147
+ }
148
+
149
+ return <button ref={handleMount}>{props.children}</button>
150
+ }
151
+
152
+ // Content — shows/hides based on active item
153
+ function AccordionContent(props: { itemId: string; children?: Child }) {
154
+ const handleMount = (el: HTMLElement) => {
155
+ const ctx = useContext(AccordionContext)
156
+
157
+ createEffect(() => {
158
+ const isOpen = ctx.activeItem() === props.itemId
159
+ el.hidden = !isOpen
160
+ })
161
+ }
162
+
163
+ return <div ref={handleMount}>{props.children}</div>
164
+ }
165
+ ```
166
+
167
+ **Usage:**
168
+
169
+ ```tsx
170
+ <Accordion>
171
+ <AccordionTrigger itemId="faq-1">What is BarefootJS?</AccordionTrigger>
172
+ <AccordionContent itemId="faq-1">
173
+ <p>A JSX-to-template compiler with signal-based reactivity.</p>
174
+ </AccordionContent>
175
+
176
+ <AccordionTrigger itemId="faq-2">How does hydration work?</AccordionTrigger>
177
+ <AccordionContent itemId="faq-2">
178
+ <p>Marker-driven: bf-* attributes tell the client JS where to attach.</p>
179
+ </AccordionContent>
180
+ </Accordion>
181
+ ```
182
+
183
+
184
+ ## Reactive Context Values
185
+
186
+ Context values can contain signal getters. Effects that read them re-run when the signal changes:
187
+
188
+ ```tsx
189
+ // Provider passes signal getter
190
+ <AccordionContext.Provider value={{ activeItem, toggle }}>
191
+ ```
192
+
193
+ ```tsx
194
+ // Consumer reads inside createEffect — reactive
195
+ const ctx = useContext(AccordionContext)
196
+ createEffect(() => {
197
+ const isOpen = ctx.activeItem() === props.itemId // Tracks activeItem signal
198
+ el.hidden = !isOpen
199
+ })
200
+ ```
201
+
202
+ `ctx.activeItem()` inside the effect subscribes to `activeItem`. When it changes, only affected effects re-run.
203
+
204
+
205
+ ## Context Without a Default
206
+
207
+ Without a default value, `useContext` throws if no `Provider` ancestor exists. Recommended for compound components:
208
+
209
+ ```tsx
210
+ const DialogContext = createContext<DialogContextValue>()
211
+
212
+ // If DialogTrigger is used outside a Dialog, useContext throws
213
+ function DialogTrigger(props: { children?: Child }) {
214
+ const handleMount = (el: HTMLElement) => {
215
+ const ctx = useContext(DialogContext) // Throws if no Dialog ancestor
216
+ // ...
217
+ }
218
+ return <button ref={handleMount}>{props.children}</button>
219
+ }
220
+ ```
221
+
222
+ This catches composition errors early — the error identifies the missing provider.
223
+
224
+
225
+ ## Context With a Default
226
+
227
+ With a default, `useContext` always succeeds:
228
+
229
+ ```tsx
230
+ const ThemeContext = createContext<'light' | 'dark'>('light')
231
+
232
+ // Works even without a ThemeProvider ancestor — returns 'light'
233
+ const theme = useContext(ThemeContext)
234
+ ```
235
+
236
+ Use for optional contexts with a sensible fallback.
@@ -0,0 +1,192 @@
1
+ ---
2
+ title: Portals
3
+ description: Render elements outside their parent DOM hierarchy for overlays, modals, and tooltips.
4
+ ---
5
+
6
+ # Portals
7
+
8
+ Portals render elements outside their parent DOM hierarchy — useful for overlays, modals, and tooltips that need to escape `overflow: hidden` or `z-index` stacking contexts.
9
+
10
+ ```tsx
11
+ "use client"
12
+ import { createPortal } from '@barefootjs/client'
13
+ ```
14
+
15
+
16
+ ## `createPortal`
17
+
18
+ Moves an element to a different DOM container.
19
+
20
+ ```tsx
21
+ createPortal(children, container?, options?)
22
+ ```
23
+
24
+ **Type:**
25
+
26
+ ```tsx
27
+ type Portal = {
28
+ element: HTMLElement
29
+ unmount: () => void
30
+ }
31
+
32
+ interface PortalOptions {
33
+ ownerScope?: Element // Component scope for scoped queries
34
+ }
35
+
36
+ function createPortal(
37
+ children: HTMLElement | string,
38
+ container?: HTMLElement, // Default: document.body
39
+ options?: PortalOptions
40
+ ): Portal
41
+ ```
42
+
43
+ **Returns** a `Portal` object with:
44
+ - `element` — the mounted DOM element
45
+ - `unmount()` — removes the element from the container
46
+
47
+
48
+ ## Basic Usage
49
+
50
+ Create portals inside `ref` callbacks:
51
+
52
+ ```tsx
53
+ "use client"
54
+ import { createSignal, createEffect, createPortal, isSSRPortal } from '@barefootjs/client'
55
+
56
+ export function Tooltip(props: { text: string; children?: Child }) {
57
+ const [visible, setVisible] = createSignal(false)
58
+
59
+ const handleMount = (el: HTMLElement) => {
60
+ // Move to document.body to avoid overflow/z-index issues
61
+ if (el.parentNode !== document.body && !isSSRPortal(el)) {
62
+ const ownerScope = el.closest('[bf-s]') ?? undefined
63
+ createPortal(el, document.body, { ownerScope })
64
+ }
65
+
66
+ createEffect(() => {
67
+ el.hidden = !visible()
68
+ })
69
+ }
70
+
71
+ return (
72
+ <div>
73
+ <span
74
+ onMouseEnter={() => setVisible(true)}
75
+ onMouseLeave={() => setVisible(false)}
76
+ >
77
+ {props.children}
78
+ </span>
79
+ <div className="tooltip" ref={handleMount}>
80
+ {props.text}
81
+ </div>
82
+ </div>
83
+ )
84
+ }
85
+ ```
86
+
87
+
88
+ ## SSR Portal Detection
89
+
90
+ `isSSRPortal` checks whether an element was already portaled during SSR to prevent double-portaling:
91
+
92
+ ```tsx
93
+ "use client"
94
+ import { isSSRPortal, createPortal } from '@barefootjs/client'
95
+
96
+ const handleMount = (el: HTMLElement) => {
97
+ // Skip if already portaled during SSR
98
+ if (el.parentNode !== document.body && !isSSRPortal(el)) {
99
+ createPortal(el, document.body)
100
+ }
101
+ }
102
+ ```
103
+
104
+ After hydration, remove SSR placeholders:
105
+
106
+ ```tsx
107
+ "use client"
108
+ import { cleanupPortalPlaceholder } from '@barefootjs/client'
109
+
110
+ declare const portalId: string
111
+ cleanupPortalPlaceholder(portalId)
112
+ ```
113
+
114
+
115
+ ## Owner Scope
116
+
117
+ A portaled element is outside its original component's scope, so `find()` cannot locate it. The `ownerScope` option links it back:
118
+
119
+ ```tsx
120
+ const handleMount = (el: HTMLElement) => {
121
+ const ownerScope = el.closest('[bf-s]') ?? undefined
122
+ createPortal(el, document.body, { ownerScope })
123
+ }
124
+ ```
125
+
126
+ `bf-po` on the moved element lets scoped queries from the owner still find it.
127
+
128
+
129
+ ## Dialog Example
130
+
131
+ Dialog overlays are a common portal use case:
132
+
133
+ ```tsx
134
+ "use client"
135
+ import { createPortal, isSSRPortal, useContext, createEffect } from '@barefootjs/client'
136
+
137
+ function DialogOverlay() {
138
+ const handleMount = (el: HTMLElement) => {
139
+ // Portal to body
140
+ if (el.parentNode !== document.body && !isSSRPortal(el)) {
141
+ const ownerScope = el.closest('[bf-s]') ?? undefined
142
+ createPortal(el, document.body, { ownerScope })
143
+ }
144
+
145
+ const ctx = useContext(DialogContext)
146
+
147
+ // Reactive visibility
148
+ createEffect(() => {
149
+ const isOpen = ctx.open()
150
+ el.dataset.state = isOpen ? 'open' : 'closed'
151
+ el.className = isOpen ? 'overlay overlay-visible' : 'overlay overlay-hidden'
152
+ })
153
+
154
+ // Click overlay to close
155
+ el.addEventListener('click', () => {
156
+ ctx.onOpenChange(false)
157
+ })
158
+ }
159
+
160
+ return <div data-slot="dialog-overlay" ref={handleMount} />
161
+ }
162
+ ```
163
+
164
+ The overlay accesses `DialogContext` from the component tree but is moved to `document.body` to escape CSS containment.
165
+
166
+
167
+ ## Cleanup
168
+
169
+ Combine `portal.unmount()` with `onCleanup`:
170
+
171
+ ```tsx
172
+ "use client"
173
+ import { createPortal, onCleanup } from '@barefootjs/client'
174
+
175
+ const handleMount = (el: HTMLElement) => {
176
+ const portal = createPortal(el, document.body)
177
+
178
+ onCleanup(() => {
179
+ portal.unmount()
180
+ })
181
+ }
182
+ ```
183
+
184
+
185
+ ## Custom Container
186
+
187
+ Specify a different container instead of the default `document.body`:
188
+
189
+ ```tsx
190
+ const container = document.getElementById('modal-root')!
191
+ createPortal(el, container)
192
+ ```
@@ -0,0 +1,97 @@
1
+ ---
2
+ title: Props & Type Safety
3
+ description: Type component props with TypeScript interfaces and preserve type information through compilation.
4
+ ---
5
+
6
+ # Props & Type Safety
7
+
8
+ The compiler preserves TypeScript type information through compilation. Adapters use it to generate type-safe templates.
9
+
10
+ Props in client components are reactive — see [Props Reactivity](../reactivity/props-reactivity.md). This page covers typing patterns.
11
+
12
+
13
+ ## Defining Props
14
+
15
+ ```tsx
16
+ interface GreetingProps {
17
+ name: string
18
+ greeting?: string
19
+ }
20
+
21
+ export function Greeting(props: GreetingProps) {
22
+ return <h1>{props.greeting ?? 'Hello'}, {props.name}</h1>
23
+ }
24
+ ```
25
+
26
+
27
+ ## Default Values
28
+
29
+ Use nullish coalescing (`??`) on the props object:
30
+
31
+ ```tsx
32
+ function Button(props: { variant?: 'default' | 'primary'; children?: Child }) {
33
+ const variant = props.variant ?? 'default'
34
+ return <button className={variant}>{props.children}</button>
35
+ }
36
+ ```
37
+
38
+ For initial-value-only props, default parameter syntax works. Add `@bf-ignore` to suppress the `BF043` destructuring warning:
39
+
40
+ ```tsx
41
+ // @bf-ignore props-destructuring
42
+ function Counter({ initial = 0 }: { initial?: number }) {
43
+ const [count, setCount] = createSignal(initial)
44
+ return <button onClick={() => setCount(n => n + 1)}>{count()}</button>
45
+ }
46
+ ```
47
+
48
+
49
+ ## Extending HTML Attributes
50
+
51
+ Extend HTML attribute types for components wrapping native elements:
52
+
53
+ ```tsx
54
+ import type { ButtonHTMLAttributes } from '@barefootjs/jsx'
55
+
56
+ interface ButtonProps extends ButtonHTMLAttributes {
57
+ variant?: 'default' | 'primary' | 'destructive'
58
+ size?: 'sm' | 'md' | 'lg'
59
+ }
60
+
61
+ function Button(props: ButtonProps) {
62
+ const variant = props.variant ?? 'default'
63
+ const size = props.size ?? 'md'
64
+ const classes = `btn btn-${variant} btn-${size} ${props.className ?? ''}`
65
+
66
+ return <button className={classes} {...props}>{props.children}</button>
67
+ }
68
+ ```
69
+
70
+ Callers can pass standard button attributes (`type`, `disabled`, `aria-label`, etc.) alongside custom props.
71
+
72
+
73
+ ## Rest Spreading
74
+
75
+ Rest spreading captures values once, so use it for server components or non-reactive attributes:
76
+
77
+ ```tsx
78
+ function Card(props: { title: string; children?: Child } & HTMLAttributes) {
79
+ return (
80
+ <div className={props.className}>
81
+ <h2>{props.title}</h2>
82
+ {props.children}
83
+ </div>
84
+ )
85
+ }
86
+ ```
87
+
88
+ ```tsx
89
+ <Card title="Dashboard" className="shadow-lg" data-testid="dashboard-card">
90
+ <p>Content</p>
91
+ </Card>
92
+ ```
93
+
94
+
95
+ ## Type Preservation
96
+
97
+ The compiler carries TypeScript type information through the IR. Each adapter uses it for type-safe server output. See [Adapters](../adapters.md) for backend-specific type mapping.
@@ -0,0 +1,37 @@
1
+ ---
2
+ title: Style Overrides
3
+ description: How user-supplied classes override component base classes via CSS Cascade Layers
4
+ ---
5
+
6
+ # Style Overrides
7
+
8
+ User-supplied classes always override component base classes — guaranteed by CSS Cascade Layers. No runtime JS, no merge functions, no class-order concerns.
9
+
10
+ Styles in a named `@layer` lose to un-layered styles regardless of specificity. BarefootJS puts component base classes into `@layer components`:
11
+
12
+ ```css
13
+ @layer preflights, base, shortcuts, components, default;
14
+ ```
15
+
16
+ The compiler's `cssLayerPrefix` option prefixes base classes at compile time:
17
+
18
+ ```tsx
19
+ // Source
20
+ const baseClasses = 'inline-flex items-center bg-primary text-primary-foreground'
21
+
22
+ // Compiled (cssLayerPrefix: 'components')
23
+ const baseClasses = 'layer-components:inline-flex layer-components:items-center layer-components:bg-primary layer-components:text-primary-foreground'
24
+ ```
25
+
26
+ User classes remain un-layered and always win:
27
+
28
+ ```
29
+ <Button className="bg-red-500">
30
+
31
+ layer-components:bg-primary → @layer components (lower priority)
32
+ bg-red-500 → un-layered (higher priority)
33
+
34
+ Result: bg-red-500 wins.
35
+ ```
36
+
37
+ Zero runtime cost. Prefixing applies to the IR, so all adapters benefit.
@@ -0,0 +1,19 @@
1
+ ---
2
+ title: Components
3
+ description: How to author, compose, and type components in BarefootJS, including props, children, context, and portals.
4
+ ---
5
+
6
+ # Components
7
+
8
+ For the `"use client"` directive and the server/client boundary, see [Backend Freedom](./core-concepts/backend-freedom.md#the-use-client-directive).
9
+
10
+ ## Pages
11
+
12
+ | Topic | Description |
13
+ |-------|-------------|
14
+ | [Component Authoring](./components/component-authoring.md) | Server components, client components, and the compilation model |
15
+ | [Props & Type Safety](./components/props-type-safety.md) | Typing props, defaults, and rest spreading |
16
+ | [Children & Slots](./components/children-slots.md) | Children prop, the `Slot` component, and the `asChild` pattern |
17
+ | [Context API](./components/context-api.md) | Sharing state across compound components with `createContext` / `useContext` |
18
+ | [Portals](./components/portals.md) | Rendering elements outside their parent DOM hierarchy |
19
+ | [Style Overrides](./components/styling.md) | How user-supplied classes override component base classes via CSS Cascade Layers |
@@ -0,0 +1,83 @@
1
+ ---
2
+ title: AI-native Development
3
+ description: Millisecond IR tests and the bf CLI for human + agent workflows
4
+ ---
5
+
6
+ # AI-native Development
7
+
8
+ BarefootJS is designed so both humans and AI agents can build components without reading source files. Two pillars carry that:
9
+
10
+ 1. **IR tests** verify component structure in milliseconds, no browser required.
11
+ 2. **The `bf` CLI** drives discovery, scaffolding, testing, and debugging — every command supports `--json` for machine consumption.
12
+
13
+ ## IR Tests
14
+
15
+ `renderToTest()` verifies component structure, signals, events, and accessibility against the compiler's IR — in milliseconds, without a browser. Real interactions and visual behavior still need E2E tests, but structural issues are caught before you get there:
16
+
17
+ > **Test runner**: the snippet below uses `bun:test`. `bf init` picks the runner that matches your package manager — bun users get `bun:test`, everyone else gets [Vitest](https://vitest.dev) (`from 'vitest'`). The surfaces are API-compatible, so swap the import line if you're following along under npm / pnpm / yarn. `bf gen test` and `bf gen component` emit the right line for you.
18
+
19
+ ```tsx
20
+ import { describe, test, expect } from 'bun:test'
21
+ import { readFileSync } from 'fs'
22
+ import { resolve } from 'path'
23
+ import { renderToTest } from '@barefootjs/test'
24
+
25
+ const source = readFileSync(resolve(__dirname, 'Counter.tsx'), 'utf-8')
26
+
27
+ describe('Counter', () => {
28
+ const ir = renderToTest(source, 'Counter.tsx')
29
+
30
+ test('compiles cleanly with a `count` signal', () => {
31
+ expect(ir.errors).toEqual([])
32
+ expect(ir.signals).toContain('count')
33
+ })
34
+
35
+ test('has a button wired to a click handler', () => {
36
+ const button = ir.find({ tag: 'button' })
37
+ expect(button).not.toBeNull()
38
+ expect(button!.events).toContain('click')
39
+ })
40
+ })
41
+ ```
42
+
43
+ `renderToTest(source, filePath)` takes the component source as a string and returns a queryable `TestResult` (`root`, `signals`, `memos`, `errors`, plus `find`/`findAll`/`findByText` for traversal). The same call shape is what `bf gen component` and `bf gen test` write — see the auto-generated `index.test.tsx` next to any component for a richer worked example.
44
+
45
+ See [IR Schema Reference](../advanced/ir-schema.md) for the full specification.
46
+
47
+ ## CLI Workflow
48
+
49
+ Install via `npm create barefootjs@latest`. Run `bf --help` for the full command surface — this section shows the daily loop, not the manual page.
50
+
51
+ A typical component task is one straight line:
52
+
53
+ ```
54
+ search → docs → add → <pm> test → debug
55
+ ```
56
+
57
+ ```bash
58
+ bf search dialog # find a component in the registry + docs
59
+ bf docs dialog # read its API (props, examples, a11y)
60
+ bf add dialog # copy it into your project
61
+ <pm> test # verify the IR (bun test / npm test / pnpm test / yarn test)
62
+ bf debug graph dialog # inspect reactivity
63
+ ```
64
+
65
+ When nothing in the registry fits, `bf gen component <name> <comps...>` scaffolds a new component composed from existing ones, with an IR test stub. When a signal doesn't update what you expect, `bf debug trace <comp> <signal>` walks the propagation path.
66
+
67
+ **Visual preview**: hosted previews live at [ui.barefootjs.dev/components/&lt;name&gt;](https://ui.barefootjs.dev). Standalone `bf preview` for npm-installed projects is tracked in [#885](https://github.com/piconic-ai/barefootjs/issues/885).
68
+
69
+ ## Agent Loop
70
+
71
+ The same workflow driven by an AI agent — say, "add a settings form":
72
+
73
+ ```bash
74
+ bf search settings-form --json
75
+ bf docs field --json
76
+ bf docs switch --json
77
+ bf gen component settings-form field switch label
78
+ # edit components/ui/settings-form/index.tsx
79
+ bun test components/ui/settings-form/index.test.tsx # or `npm test -- <path>`, `pnpm test <path>`, etc.
80
+ bf debug graph settings-form
81
+ ```
82
+
83
+ No source files read — the CLI is the API.
@@ -0,0 +1,31 @@
1
+ ---
2
+ title: Backend Freedom
3
+ description: How adapters let the same JSX run on any server — Hono, Go, and beyond
4
+ ---
5
+
6
+ # Backend Freedom
7
+
8
+ JSX gives you components with props, composition, and type checking. But server rendering with JSX usually means running Node.js on the server.
9
+
10
+ BarefootJS compiles JSX at build time into your backend's native template format. Go renders `.tmpl` with `html/template`; Perl renders `.html.ep` with Mojolicious; Hono renders the generated `.tsx`. For non-Node backends, no Node.js runs at serving time. The compiler produces a backend-agnostic IR, then an adapter converts it:
11
+
12
+ ```
13
+ JSX → IR (backend-agnostic) → Adapter → Template
14
+ ```
15
+
16
+ | Language | Adapter | Notes |
17
+ |----------|---------|-------|
18
+ | TypeScript | [HonoAdapter](../adapters/hono-adapter.md) | Hono / JSX-based TS servers |
19
+ | TypeScript | [TestAdapter](https://github.com/piconic-ai/barefootjs/tree/main/packages/test) | IR-based component testing |
20
+ | Go | [GoTemplateAdapter](../adapters/go-template-adapter.md) | `html/template` |
21
+ | Perl | [MojoliciousAdapter](https://github.com/piconic-ai/barefootjs/tree/main/packages/adapter-mojolicious) | Mojolicious EP templates |
22
+
23
+ ### Planned
24
+
25
+ | Language | Adapter |
26
+ |----------|---------|
27
+ | Rust | (TBD) |
28
+ | Python | Jinja2Adapter |
29
+ | Ruby | ERBAdapter |
30
+
31
+ The IR contract is stable. You can [write a custom adapter](../adapters/custom-adapter.md) for any backend.