fiboui 2.8.3 → 2.8.5

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/README.md CHANGED
@@ -134,6 +134,95 @@ Return them. Nest them. **Skip `.build()`.**
134
134
 
135
135
  ---
136
136
 
137
+ ## Who owns a signal
138
+
139
+ **One rule:** each `.value` read has exactly **one** owner. Prefer the leaf node that displays or styles from it — not the page function, not a parent wrapper.
140
+
141
+ **Decision tree (short):**
142
+
143
+ 1. **Display** a number or string → `Text(\`${n.value} left\`)`.
144
+ 2. **Format** decimals / money → `Format.fixed(price)` / `Format.euro(price)` (not `n.value.toFixed(2)`).
145
+ 3. **CSS lengths from a signal** → `Box().width(Unit.px(width))`.
146
+ 4. **Count + word** → `Text(Plural.of(n, 'task', 'tasks'))`.
147
+ 5. **Style** from a raw number → `Box().width(n.value)` (or `n.value + 1` on **that** box).
148
+ 6. **Branch** that changes **shape** → a small `.observer()` around **that branch only**.
149
+ 7. **List / table rows** → `listSignal` + `List` / `Table` / `Tags`.
150
+
151
+ ### Print helpers (live)
152
+
153
+ | Helper | Example | Result |
154
+ | --- | --- | --- |
155
+ | `Format.fixed` | `Format.fixed(price)` | `12.50` |
156
+ | `Format.euro` | `Format.euro(price)` | `€ 12.50` |
157
+ | `Format.currency` | `Format.currency(price, 'USD')` | `$12.50` |
158
+ | `Format.percent` | `Format.percent(rate)` | `12%` |
159
+ | `Format.integer` | `Format.integer(n)` | `13` |
160
+ | `Format.compact` | `Format.compact(1200)` | `1.2K` |
161
+ | `Unit.px` / `rem` / `%` | `Unit.px(width)` | `100px` |
162
+ | `Plural.of` | `Plural.of(n, 'task', 'tasks')` | `3 tasks` |
163
+
164
+ `Transform` is an alias of `Format`.
165
+
166
+ In **development**, the console warns once with `[fiboui]` for **page roots** and **unsplit** string templates. Optional: `fiboDevStats()` returns `{ produces, replays, pageRoots, warns }` so you can inspect ownership in the console.
167
+
168
+ ### Good patterns
169
+
170
+ ```tsx
171
+ // Count in the label — the Text node owns the read
172
+ Text(`${left.value} left`)
173
+
174
+ // Decimals / money / units / plurals — helpers own the print
175
+ Text(Format.euro(price))
176
+ Box().width(Unit.px(width))
177
+ Text(Plural.of(count, 'task', 'tasks'))
178
+
179
+ // Switch owns checked; only the Live/Idle label observes shape
180
+ Row().children([
181
+ Switch().checked(isOnline),
182
+ Box()
183
+ .observer()
184
+ .children(() => [
185
+ isOnline.value ? Text('Live').color('lime') : Text('Idle').color('muted'),
186
+ ]),
187
+ ])
188
+
189
+ // listSignal: each row updates by _id without re-running the page
190
+ List().items(todos).renderItem((todo) => TodoRow(todo))
191
+ ```
192
+
193
+ ### Bad patterns
194
+
195
+ ```tsx
196
+ // ❌ Bare read in page() — the signal becomes a page root (dev warns; page re-runs)
197
+ page(() => {
198
+ if (online.value) return Column().children([/* … */])
199
+ return Column().children([/* … */])
200
+ })
201
+
202
+ // ❌ One observer for the whole sidebar — everything inside re-runs together
203
+ Column()
204
+ .observer()
205
+ .children(() => sidebarChildren(online.value, count.value))
206
+
207
+ // ❌ bare toFixed — use Format.fixed / Format.euro instead
208
+ Text(`€ ${n.value.toFixed(2)}`)
209
+ ```
210
+
211
+
212
+ Prefer a subpath when you only need a slice (smaller types): `fiboui/core`, `fiboui/layout`, `fiboui/structure`, …
213
+
214
+ Local pack for a sibling app (Turbopack-friendly tarball in-repo):
215
+
216
+ ```bash
217
+ npm run pack:local
218
+ # → .pack/fiboui-<version>.tgz (includes JS + split .d.ts)
219
+ # in the app (sibling repo):
220
+ # "fiboui": "file:../fiboui/.pack/fiboui-<version>.tgz"
221
+ # or an absolute path to that file
222
+ ```
223
+
224
+ ---
225
+
137
226
  ## Signal flow
138
227
 
139
228
  <img src="./docs/brand/flow-signals.png" width="960" alt="signal → bindSignal → function children → UI" />