@polderlabs/bizar 4.4.6 → 4.4.8

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.
@@ -1,12 +1,12 @@
1
1
  ---
2
2
  name: glyph
3
3
  description: Create and consume Bizar glyphs — visual plan/recap artifacts in `artifacts/<slug>/`. Glyphs are MDX files with frontmatter and a block vocabulary. Use for design proposals, decision recaps, and postmortems.
4
- version: 1
4
+ version: 2
5
5
  ---
6
6
 
7
7
  # Glyphs — Visual Plan Artifacts
8
8
 
9
- A **glyph** is a visual artifact used for plans, recaps, and design proposals. It's an MDX file in `artifacts/<slug>/artifact.mdx` that the dashboard renders with comment pin overlay and right-click context menu.
9
+ A **glyph** is a visual artifact used for plans, recaps, design proposals, and postmortems. It's an MDX file at `artifacts/<slug>/artifact.mdx` (or `~/.config/opencode/artifacts/<slug>/` for global glyphs) that the dashboard renders with comment-pin overlay and right-click context menu.
10
10
 
11
11
  ## When to create a glyph
12
12
 
@@ -22,12 +22,16 @@ If it's a one-line fix, don't make a glyph. If it's a 5-minute edit, don't make
22
22
 
23
23
  ```
24
24
  artifacts/<slug>/
25
- ├── meta.json # title, slug, status, author, created
25
+ ├── meta.json # title, slug, status, author, created, lastEdited
26
26
  ├── artifact.mdx # ← source of truth (git-tracked, diff-friendly)
27
- └── comments.json # ← free-placed pins (mutable)
27
+ └── comments.json # ← free-placed pins (mutable, gitignored usually)
28
28
  ```
29
29
 
30
- The `meta.json` is metadata, `artifact.mdx` is the content, `comments.json` is the only mutable side-channel.
30
+ Two locations are scanned:
31
+ 1. **Per-project:** `<projectRoot>/.bizar/artifacts/<slug>/`
32
+ 2. **Global:** `~/.config/opencode/artifacts/<slug>/`
33
+
34
+ The dashboard uses whichever exists. The project's `artifacts/<slug>/` is preferred when running inside a worktree.
31
35
 
32
36
  ## Frontmatter
33
37
 
@@ -40,6 +44,8 @@ kind: plan | postmortem | recap | design
40
44
  ---
41
45
  ```
42
46
 
47
+ The frontmatter is parsed once and exposed to the React renderer. Unknown keys are passed through. Status controls the badge color + whether the glyph is read-only.
48
+
43
49
  ## Block vocabulary
44
50
 
45
51
  ```mdx
@@ -119,31 +125,109 @@ kind: plan | postmortem | recap | design
119
125
  />
120
126
  ```
121
127
 
128
+ ## What data shape each block expects
129
+
130
+ The dashboard validates every block's `data` shape BEFORE rendering. If a field is missing or the wrong type, the block renders an inline error card instead of crashing the whole canvas (v4.4.8+). The same thing happens if a block throws during render — a per-block `ErrorBoundary` catches it.
131
+
132
+ | Block | Required `data` fields |
133
+ | --- | --- |
134
+ | `RichText` | none (children are markdown) |
135
+ | `Callout` | none; optional `tone: 'info' \| 'warn' \| 'success' \| 'danger'` (default: info) |
136
+ | `Stat` | `label: string`, `value: string \| number`; optional `trend`, `hint` |
137
+ | `Checklist` | `items: Array<{ id, label, checked }>` |
138
+ | `Table` | `columns: string[]`, `rows: string[][]` (each row is a cell array) |
139
+ | `CodeTabs` | `tabs: Array<{ id, label, language, code, caption? }>` |
140
+ | `Decision` | `options: Array<{ id, label, detail, recommended? }>`; optional `title`, `question` |
141
+ | `OpenQuestions` | `questions: Array<{ id, label, kind, options? }>` |
142
+ | `FileTree` | `entries: Array<{ path, change, note? }>` where `change ∈ {added, modified, removed, renamed}` |
143
+ | `Diff` | `before: string`, `after: string`; optional `filename`, `language`, `mode` |
144
+ | `Workflow` | `steps: Array<{ id, label, type }>`; optional `connections` |
145
+ | `Mockup` | `html: string`; optional `title`, `x`, `y`, `w`, `h` |
146
+ | `Diagram` | `dataHtml: string`; optional `title`, `dataCss` |
147
+
122
148
  ## Common pitfalls
123
149
 
124
- ### FileTree `change` field
150
+ These are the bugs we've actually hit. Read this section before writing a glyph.
125
151
 
126
- `change` must be exactly one of: `added`, `modified`, `removed`, `renamed`. Use the `note` field for nuance ("rewritten from scratch", "kept as-is", "deprecated").
152
+ ### 1. Backticks inside attribute strings break the parser
153
+
154
+ The MDX parser scans for `` ` `` to delimit template literals in JSX. A literal backtick inside an attribute value — even inside a double-quoted string — will close the surrounding context and produce a parse error.
127
155
 
128
156
  ```mdx
129
- <FileTree
130
- entries={[
131
- { path: "src/chat/Composer.tsx", change: "modified", note: "rewritten as pill composer" }
132
- ]}
133
- />
157
+ <!-- WRONG: \` inside a string value kills the parser -->
158
+ <RichText id="x">Run \`bizar install\` to bootstrap.</RichText>
159
+
160
+ <!-- RIGHT: use straight quotes or rephrase -->
161
+ <RichText id="x">Run 'bizar install' to bootstrap.</RichText>
162
+ <RichText id="x">Run the installer to bootstrap.</RichText>
163
+ ```
164
+
165
+ The parser's JSX-attribute walker does NOT track string literals when counting braces — so any `{` or `}` inside a string value also throws the parser off. Quote all strings that contain braces.
166
+
167
+ ### 2. The parser counts braces naively (does NOT track strings)
168
+
169
+ The `parseJsxValue` function uses a brace-depth counter that doesn't know about string literals. If you have something like:
170
+
171
+ ```mdx
172
+ <FileTree entries={[{ path: "x", change: "added", note: "this {weird} note" }]} />
173
+ ```
174
+
175
+ …the inner `{` and `}` inside the string still increment the brace depth. With deeply-nested or brace-laden strings the walker terminates at the wrong closing brace, leaving a truncated value for `parseJsxValue`.
176
+
177
+ **Rule of thumb:** keep string values brace-free. If you must use `{...}` in a note, escape or rephrase.
178
+
179
+ ### 3. Trailing commas in arrays used to be a phantom-element bug
180
+
181
+ Before v4.4.8, an array ending with a trailing comma — e.g. `[a, b,]` — caused the parser to call `parseValue` on the closing `]`, which fell through to the bareword-identifier fallback and returned `{__ident: ''}` as a phantom element. The FileTree then had one extra broken entry that crashed the React renderer (`t.bg` on undefined).
182
+
183
+ v4.4.8 fixed this in the parser. But: don't rely on trailing commas, even where supported — keep the array clean.
184
+
185
+ ### 4. `FileTree` `change` field must be exact
186
+
187
+ `change` must be exactly one of: `added`, `modified`, `removed`, `renamed`. Anything else (including the empty string or `null`) makes `FILE_CHANGE[e.change]` return `undefined` and the renderer crashes with "can't access property 'bg' of undefined".
188
+
189
+ ```mdx
190
+ <!-- WRONG -->
191
+ { path: "src/foo.ts", change: null, note: "..." }
192
+ { path: "src/foo.ts", change: "", note: "..." }
193
+
194
+ <!-- RIGHT -->
195
+ { path: "src/foo.ts", change: "modified", note: "..." }
196
+ { path: "src/foo.ts", change: "added", note: "..." }
134
197
  ```
135
198
 
136
- ### MDX heading with `<N`
199
+ If a file's status is genuinely unclear, use `modified` with a `note` that explains why.
137
200
 
138
- A heading like `## Mobile (under 768px)` is fine. But `## Mobile <768px` breaks the MDX validator because `<7` looks like a malformed JSX tag. Always escape or rephrase.
201
+ ### 5. MDX heading with `<N`
139
202
 
140
- ### Block IDs must be unique within a glyph
203
+ A heading like `## Mobile (under 768px)` is fine. But `## Mobile <768px` breaks the MDX validator because `<7` looks like a malformed JSX tag. Always escape or rephrase:
141
204
 
142
- If you copy a `<RichText>` block, change its `id`. The compiler may warn but won't fail.
205
+ ```mdx
206
+ ## Mobile (under 768px) <!-- fine -->
207
+ ## Mobile: under 768px <!-- fine -->
208
+ ## Mobile &lt; 768px <!-- fine but ugly -->
209
+ ```
210
+
211
+ ### 6. Block IDs must be unique within a glyph
212
+
213
+ If you copy a `<RichText>` block, change its `id`. The compiler may warn but won't fail. Two blocks with the same id will collide on the comment-pin overlay.
143
214
 
144
- ### Children vs self-closing
215
+ ### 7. Children vs self-closing
145
216
 
146
- Blocks with content (RichText, Callout) use open/close tags with children. Blocks with only data (Stat, Table, FileTree, Decision, etc.) are self-closing.
217
+ Blocks with content (`RichText`, `Callout`) use open/close tags with children. Blocks with only data (`Stat`, `Table`, `FileTree`, `Decision`, etc.) are self-closing — no children, no closing tag.
218
+
219
+ ```mdx
220
+ <!-- self-closing -->
221
+ <Stat id="x" label="..." value="..." />
222
+
223
+ <!-- with children -->
224
+ <RichText id="x">markdown content here</RichText>
225
+ <Callout id="x" tone="warn">important message</Callout>
226
+ ```
227
+
228
+ ### 8. Em-dashes and special characters
229
+
230
+ The parser's JSON-like value reader handles `\\`, `\"`, `\'`, `\n`, `\t`, `\r` inside string literals. Em-dashes (`—`), curly quotes (`""`), and other Unicode characters are fine. Just avoid `\``.
147
231
 
148
232
  ## meta.json example
149
233
 
@@ -158,17 +242,34 @@ Blocks with content (RichText, Callout) use open/close tags with children. Block
158
242
  }
159
243
  ```
160
244
 
245
+ `lastEdited` is updated whenever the dashboard re-saves the glyph via the toolbar.
246
+
247
+ ## Validation workflow
248
+
249
+ Before committing a glyph:
250
+
251
+ 1. Run the compiler against your MDX:
252
+ ```bash
253
+ node -e "import('./bizar-dash/src/server/glyphs/mdx-compiler.mjs').then(m => { const fs = require('node:fs'); const src = fs.readFileSync('artifacts/<slug>/artifact.mdx', 'utf8'); const out = m.compileGlyphMdxSync(src); console.log('blocks:', out.blocks.length, 'errors:', out.errors); out.errors.forEach(e => console.error(' line', e.line, ':', e.message)); })"
254
+ ```
255
+ This catches parser bugs (missing tags, malformed attrs, etc.) before they hit the dashboard.
256
+
257
+ 2. Visually inspect the rendered output by hitting `GET /api/artifacts/<slug>/render`. The response is the compiled JSON the React renderer consumes. Look for blocks with missing data fields — those will show up as `data: {}` or `data: []`.
258
+
259
+ 3. Open the glyph in the dashboard. v4.4.8+ shows a red error banner at the top of the canvas if any block failed to render. The banner lists every block error AND every compiler warning.
260
+
161
261
  ## How to view in the dashboard
162
262
 
163
263
  Glyphs are rendered at `/artifacts/<slug>`. The dashboard shows them with:
164
- - Section grouping (by block id prefix: `overview`, `implementation`, `questions`)
165
- - Comment pin overlay (right-click to add)
166
- - Floating toolbar (Send to agent, share)
167
- - Read-only by default; `status: shipped` locks editing
264
+ - **Section grouping** by block id prefix (e.g. all blocks with ids starting `overview_` go into the "Overview" section)
265
+ - **Comment pin overlay** right-click anywhere to add a comment
266
+ - **Floating toolbar** Send to agent, share, fullscreen
267
+ - **Error banner** v4.4.8+: red banner at the top with block render errors and compiler warnings. The page is no longer blank when something fails — you'll see exactly which block crashed and why.
268
+ - **Status badge** — `draft | review | shipped | archived`. `shipped` locks editing.
168
269
 
169
270
  ## See also
170
271
 
171
272
  - `bizar-dash/src/web/views/glyphs/components.tsx` — block component implementations
172
- - `bizar-dash/src/server/glyphs/mdx-compiler.mjs` — MDX parser
173
- - `artifacts/sample-plan-login/`a complete worked example
174
- - The user's Obsidian vault for the project's design notes
273
+ - `bizar-dash/src/server/glyphs/mdx-compiler.mjs` — MDX parser (custom, no @mdx-js dependency at runtime)
274
+ - `bizar-dash/src/web/views/glyphs/GlyphRenderer.tsx`React renderer (v4.4.8+ has the error banner + ErrorBoundary)
275
+ - `artifacts/sample-plan-login/` a complete worked example