@barefootjs/cli 0.1.3 → 0.2.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.
- package/dist/docs/core/README.mdx +2 -1
- package/dist/docs/core/components/context-api.md +7 -0
- package/dist/docs/core/core-concepts/ai-native.md +19 -0
- package/dist/docs/core/llms.txt +1 -0
- package/dist/docs/core/quick-start.mdx +1 -0
- package/dist/docs/core/reactivity/shared-state.md +233 -0
- package/dist/docs/core/reactivity.md +1 -0
- package/dist/index.js +8580 -7939
- package/package.json +2 -2
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
- [Backend Freedom](./core-concepts/backend-freedom.md) — How adapters let the same JSX run on any server
|
|
20
20
|
- [MPA-style Development](./core-concepts/mpa-style.md) — Server-rendering by default, JS only where marked
|
|
21
21
|
- [Fine-grained Reactivity](./core-concepts/reactivity.md) — Signals, effects, memos — no virtual DOM needed
|
|
22
|
-
- [AI-native Development](./core-concepts/ai-native.md) — Testable IR, CLI discovery, AI-assisted workflows
|
|
22
|
+
- [AI-native Development](./core-concepts/ai-native.md) — Testable IR, CLI discovery, agent skill (Claude Code / Codex), AI-assisted workflows
|
|
23
23
|
- [How It Works](./core-concepts/how-it-works.mdx) — Two-phase compilation, hydration markers, clean overrides
|
|
24
24
|
|
|
25
25
|
### 4. [Reactivity](./reactivity.md)
|
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
- [`onCleanup`](./reactivity/on-cleanup.md) — Register cleanup for effects and lifecycle
|
|
32
32
|
- [`untrack`](./reactivity/untrack.md) — Read signals without tracking dependencies
|
|
33
33
|
- [Props Reactivity](./reactivity/props-reactivity.md) — Gotchas with destructuring
|
|
34
|
+
- [Shared State Patterns](./reactivity/shared-state.md) — Cross-file state sharing: consolidation, custom events, server props
|
|
34
35
|
|
|
35
36
|
### 5. [Templates & Rendering](./rendering.md)
|
|
36
37
|
|
|
@@ -234,3 +234,10 @@ const theme = useContext(ThemeContext)
|
|
|
234
234
|
```
|
|
235
235
|
|
|
236
236
|
Use for optional contexts with a sensible fallback.
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
## Cross-File Context Does Not Work
|
|
240
|
+
|
|
241
|
+
`createContext()` and all components that use it must be in the **same file**. Importing a context from another file does not work — the compiler does not currently support cross-file context sharing.
|
|
242
|
+
|
|
243
|
+
This is why compound components (Accordion, Dialog, Select, Tabs) define all sub-components in a single file. For sharing state across components in separate files, see [Shared State Patterns](../reactivity/shared-state.md).
|
|
@@ -66,6 +66,25 @@ When nothing in the registry fits, `bf gen component <name> <comps...>` scaffold
|
|
|
66
66
|
|
|
67
67
|
**Visual preview**: hosted previews live at [ui.barefootjs.dev/components/<name>](https://ui.barefootjs.dev). Standalone `bf preview` for npm-installed projects is tracked in [#885](https://github.com/piconic-ai/barefootjs/issues/885).
|
|
68
68
|
|
|
69
|
+
## Agent Skill
|
|
70
|
+
|
|
71
|
+
BarefootJS publishes an agent skill that gives AI deep knowledge of the compiler, IR, CLI, and component system. Install it and the agent can build, test, and debug BarefootJS projects autonomously.
|
|
72
|
+
|
|
73
|
+
**Claude Code:**
|
|
74
|
+
|
|
75
|
+
```sh
|
|
76
|
+
/plugin marketplace add piconic-ai/barefootjs
|
|
77
|
+
/plugin install barefootjs@barefootjs
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
**Codex:**
|
|
81
|
+
|
|
82
|
+
```
|
|
83
|
+
install the barefootjs skill from piconic-ai/barefootjs
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
With the skill active, the agent understands `bf` CLI commands, IR test patterns, signal graphs, adapter output, and error codes — no manual prompting needed.
|
|
87
|
+
|
|
69
88
|
## Agent Loop
|
|
70
89
|
|
|
71
90
|
The same workflow driven by an AI agent — say, "add a settings form":
|
package/dist/docs/core/llms.txt
CHANGED
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
- [onCleanup](https://barefootjs.dev/docs/reactivity/on-cleanup.md): Registers a cleanup function that runs when the owning effect re-runs or the component is destroyed.
|
|
19
19
|
- [onMount](https://barefootjs.dev/docs/reactivity/on-mount.md): Runs a callback once when the component initializes, without tracking signal dependencies.
|
|
20
20
|
- [Props Reactivity](https://barefootjs.dev/docs/reactivity/props-reactivity.md): How prop access patterns determine whether reactive updates propagate in BarefootJS components.
|
|
21
|
+
- [Shared State Patterns](https://barefootjs.dev/docs/reactivity/shared-state.md): Patterns for sharing reactive state between components in separate files, and when to use each.
|
|
21
22
|
- [untrack](https://barefootjs.dev/docs/reactivity/untrack.md): Executes a function without tracking signal dependencies in the current reactive context.
|
|
22
23
|
|
|
23
24
|
## Rendering
|
|
@@ -147,6 +147,7 @@ This runs `bf build`, generates the final `uno.css`, and calls `wrangler deploy`
|
|
|
147
147
|
|
|
148
148
|
## Next steps
|
|
149
149
|
|
|
150
|
+
- **[Agent Skill](./core-concepts/ai-native.md#agent-skill)** — Install the BarefootJS skill for Claude Code or Codex and let the agent build components, write IR tests, and debug signals for you.
|
|
150
151
|
- **[Core Concepts](./core-concepts.md)** — the four design principles behind BarefootJS: backend freedom, MPA-style rendering, fine-grained reactivity, and AI-native workflows.
|
|
151
152
|
- **[`createSignal`](./reactivity/create-signal.md)** and **[`createMemo`](./reactivity/create-memo.md)** — the reactivity primitives you just used.
|
|
152
153
|
- **[Client Directive](./rendering/client-directive.md)** — exactly what `"use client"` does and when to reach for it.
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Shared State Patterns
|
|
3
|
+
description: Patterns for sharing reactive state between components in separate files, and when to use each.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Shared State Patterns
|
|
7
|
+
|
|
8
|
+
The [Context API](../components/context-api.md) shares state between components in the **same file** (compound components like Accordion, Dialog, Tabs). But the compilation model — one `.tsx` file produces one `.client.js` bundle — means context **cannot cross file boundaries**.
|
|
9
|
+
|
|
10
|
+
This guide covers patterns for sharing reactive state between components in **separate files**.
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
## Why Context Doesn't Work Across Files
|
|
14
|
+
|
|
15
|
+
Each `.client.js` bundle is independent. When the compiler processes two files that both reference the same `createContext()`, each bundle gets its own call — producing a unique `Symbol` id. The provider's context and the consumer's context are different objects:
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
PlaybackProvider.client.js → createContext() → Symbol(#1)
|
|
19
|
+
Player.client.js → createContext() → Symbol(#2)
|
|
20
|
+
↑ different identity
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
`useContext` in Player walks the DOM looking for `Symbol(#2)`, but PlaybackProvider set `Symbol(#1)`. No match — the value is never found.
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
## Pattern 1: Consolidate Into One File
|
|
27
|
+
|
|
28
|
+
The simplest solution. If components are tightly coupled, put them in the same file:
|
|
29
|
+
|
|
30
|
+
```tsx
|
|
31
|
+
"use client"
|
|
32
|
+
import { createContext, useContext, createSignal, createEffect } from '@barefootjs/client'
|
|
33
|
+
|
|
34
|
+
interface PlaybackContextValue {
|
|
35
|
+
elapsedMs: () => number
|
|
36
|
+
playing: () => boolean
|
|
37
|
+
seek: (ms: number) => void
|
|
38
|
+
toggle: () => void
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const PlaybackContext = createContext<PlaybackContextValue>()
|
|
42
|
+
|
|
43
|
+
export function PlaybackProvider(props: { children?: unknown }) {
|
|
44
|
+
const [elapsedMs, setElapsedMs] = createSignal(0)
|
|
45
|
+
const [playing, setPlaying] = createSignal(false)
|
|
46
|
+
|
|
47
|
+
const seek = (ms: number) => setElapsedMs(ms)
|
|
48
|
+
const toggle = () => setPlaying(p => !p)
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<PlaybackContext.Provider value={{ elapsedMs, playing, seek, toggle }}>
|
|
52
|
+
{props.children}
|
|
53
|
+
</PlaybackContext.Provider>
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function Player(props: { children?: unknown }) {
|
|
58
|
+
const handleMount = (el: HTMLElement) => {
|
|
59
|
+
const ctx = useContext(PlaybackContext)
|
|
60
|
+
createEffect(() => {
|
|
61
|
+
el.textContent = `${Math.floor(ctx.elapsedMs() / 1000)}s`
|
|
62
|
+
})
|
|
63
|
+
}
|
|
64
|
+
return <div ref={handleMount}>{props.children}</div>
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function TimelineBar(props: { duration: number }) {
|
|
68
|
+
const handleMount = (el: HTMLElement) => {
|
|
69
|
+
const ctx = useContext(PlaybackContext)
|
|
70
|
+
el.addEventListener('click', (e) => {
|
|
71
|
+
const rect = el.getBoundingClientRect()
|
|
72
|
+
const ratio = (e.clientX - rect.left) / rect.width
|
|
73
|
+
ctx.seek(ratio * props.duration)
|
|
74
|
+
})
|
|
75
|
+
}
|
|
76
|
+
return <div ref={handleMount} className="timeline" />
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
**When to use:** Components share a tight contract and are always used together (like Select + SelectTrigger + SelectContent).
|
|
81
|
+
|
|
82
|
+
| Pros | Cons |
|
|
83
|
+
|------|------|
|
|
84
|
+
| Full reactivity via signals | All components in one file |
|
|
85
|
+
| Type-safe | File grows with component count |
|
|
86
|
+
| Works with Context API | Not suitable for loosely coupled components |
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
## Pattern 2: Custom DOM Events
|
|
90
|
+
|
|
91
|
+
Use the browser's native event system. One component dispatches events, others listen. No shared module state needed.
|
|
92
|
+
|
|
93
|
+
Player (`components/Player.tsx`):
|
|
94
|
+
|
|
95
|
+
```tsx
|
|
96
|
+
"use client"
|
|
97
|
+
import { createSignal, createEffect, onCleanup } from '@barefootjs/client'
|
|
98
|
+
|
|
99
|
+
export function Player() {
|
|
100
|
+
const [elapsedMs, setElapsedMs] = createSignal(0)
|
|
101
|
+
|
|
102
|
+
const handleMount = (el: HTMLElement) => {
|
|
103
|
+
createEffect(() => {
|
|
104
|
+
el.dispatchEvent(new CustomEvent('playback:timeupdate', {
|
|
105
|
+
bubbles: true,
|
|
106
|
+
detail: { elapsedMs: elapsedMs() },
|
|
107
|
+
}))
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
const onSeek = ((e: CustomEvent) => {
|
|
111
|
+
setElapsedMs(e.detail.ms)
|
|
112
|
+
}) as EventListener
|
|
113
|
+
|
|
114
|
+
document.addEventListener('playback:seek', onSeek)
|
|
115
|
+
onCleanup(() => document.removeEventListener('playback:seek', onSeek))
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return <div ref={handleMount} />
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
TimelineBar (`components/TimelineBar.tsx`):
|
|
123
|
+
|
|
124
|
+
```tsx
|
|
125
|
+
"use client"
|
|
126
|
+
import { onCleanup } from '@barefootjs/client'
|
|
127
|
+
|
|
128
|
+
export function TimelineBar(props: { duration: number }) {
|
|
129
|
+
const handleMount = (el: HTMLElement) => {
|
|
130
|
+
const onTimeUpdate = ((e: CustomEvent) => {
|
|
131
|
+
const ratio = e.detail.elapsedMs / props.duration
|
|
132
|
+
el.style.setProperty('--progress', String(ratio))
|
|
133
|
+
}) as EventListener
|
|
134
|
+
|
|
135
|
+
document.addEventListener('playback:timeupdate', onTimeUpdate)
|
|
136
|
+
onCleanup(() => document.removeEventListener('playback:timeupdate', onTimeUpdate))
|
|
137
|
+
|
|
138
|
+
el.addEventListener('click', (e) => {
|
|
139
|
+
const rect = el.getBoundingClientRect()
|
|
140
|
+
const ratio = (e.clientX - rect.left) / rect.width
|
|
141
|
+
el.dispatchEvent(new CustomEvent('playback:seek', {
|
|
142
|
+
bubbles: true,
|
|
143
|
+
detail: { ms: ratio * props.duration },
|
|
144
|
+
}))
|
|
145
|
+
})
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return <div ref={handleMount} className="timeline" />
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
**When to use:** Components are in separate files and communicate through a shared parent in the DOM.
|
|
153
|
+
|
|
154
|
+
| Pros | Cons |
|
|
155
|
+
|------|------|
|
|
156
|
+
| Works across any file boundary | Not reactive — imperative dispatch/listen |
|
|
157
|
+
| No shared imports needed | No type safety on event payloads (use a shared type file to mitigate) |
|
|
158
|
+
| Familiar browser API | Requires manual cleanup |
|
|
159
|
+
| SSR-safe (listeners only run on client) | Event naming conventions needed |
|
|
160
|
+
|
|
161
|
+
### Type-safe event helpers
|
|
162
|
+
|
|
163
|
+
Define event types in a shared `src/` file to get type safety without sharing runtime state:
|
|
164
|
+
|
|
165
|
+
```ts
|
|
166
|
+
export interface PlaybackTimeUpdateDetail {
|
|
167
|
+
elapsedMs: number
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export interface PlaybackSeekDetail {
|
|
171
|
+
ms: number
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export function dispatchTimeUpdate(el: Element, detail: PlaybackTimeUpdateDetail) {
|
|
175
|
+
el.dispatchEvent(new CustomEvent('playback:timeupdate', { bubbles: true, detail }))
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export function dispatchSeek(el: Element, detail: PlaybackSeekDetail) {
|
|
179
|
+
el.dispatchEvent(new CustomEvent('playback:seek', { bubbles: true, detail }))
|
|
180
|
+
}
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
`src/` utility files are inlined at compile time. Type definitions and `CustomEvent` constructors don't carry mutable state, so inlining is safe — each bundle gets its own copy of the helper functions, but they produce identical events.
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
## Pattern 3: Server-Mediated Props
|
|
187
|
+
|
|
188
|
+
For state that originates on the server (database, session, URL params), pass it as props from the server route. No client-side sharing needed:
|
|
189
|
+
|
|
190
|
+
```tsx
|
|
191
|
+
import { Player } from '@/components/Player'
|
|
192
|
+
import { TimelineBar } from '@/components/TimelineBar'
|
|
193
|
+
|
|
194
|
+
app.get('/player/:id', async (c) => {
|
|
195
|
+
const track = await db.getTrack(c.req.param('id'))
|
|
196
|
+
return c.render(
|
|
197
|
+
<main>
|
|
198
|
+
<Player trackId={track.id} initialPosition={track.lastPosition} />
|
|
199
|
+
<TimelineBar duration={track.durationMs} />
|
|
200
|
+
</main>
|
|
201
|
+
)
|
|
202
|
+
})
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
Each component hydrates independently with its own signals. User interactions that need to cross components use Pattern 2 (custom events).
|
|
206
|
+
|
|
207
|
+
**When to use:** Initial state comes from the server; components only need to sync on user-driven actions.
|
|
208
|
+
|
|
209
|
+
| Pros | Cons |
|
|
210
|
+
|------|------|
|
|
211
|
+
| SSR-friendly — state is in HTML | Real-time sync still needs custom events |
|
|
212
|
+
| No shared client state | Server round-trip for state changes |
|
|
213
|
+
| Each component is independently testable | |
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
## Choosing a Pattern
|
|
217
|
+
|
|
218
|
+
```
|
|
219
|
+
Is all state server-derived?
|
|
220
|
+
└─ Yes → Pattern 3 (Server-Mediated Props)
|
|
221
|
+
└─ No
|
|
222
|
+
├─ Are components tightly coupled (always used together)?
|
|
223
|
+
│ └─ Yes → Pattern 1 (Consolidate Into One File)
|
|
224
|
+
└─ No → Pattern 2 (Custom DOM Events)
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
| | Context (same file) | Consolidate | Custom Events | Server Props |
|
|
228
|
+
|---|---|---|---|---|
|
|
229
|
+
| Cross-file | No | No (one file) | Yes | Yes |
|
|
230
|
+
| Reactive | Yes | Yes | No | No (initial only) |
|
|
231
|
+
| Type-safe | Yes | Yes | With helpers | Yes |
|
|
232
|
+
| SSR-safe | Yes | Yes | Yes | Yes |
|
|
233
|
+
| Testable | IR test | IR test | E2E | IR test + E2E |
|
|
@@ -26,3 +26,4 @@ import { createSignal, createEffect, createMemo, onMount, onCleanup, untrack } f
|
|
|
26
26
|
## Guides
|
|
27
27
|
|
|
28
28
|
- [Props Reactivity](./reactivity/props-reactivity.md) — How props stay reactive, and when destructuring breaks it
|
|
29
|
+
- [Shared State Patterns](./reactivity/shared-state.md) — Sharing state across components in separate files
|