@polderlabs/bizar 4.4.7 → 4.4.9
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/bizar-dash/dist/assets/main-CEazNxxy.js +347 -0
- package/bizar-dash/dist/assets/main-CEazNxxy.js.map +1 -0
- package/bizar-dash/dist/assets/{mobile-CQTXbuHq.js → mobile-DSb-t42Y.js} +2 -2
- package/bizar-dash/dist/assets/{mobile-CQTXbuHq.js.map → mobile-DSb-t42Y.js.map} +1 -1
- package/bizar-dash/dist/assets/{mobile-CaZ0gEeJ.js → mobile-Dl1q7Cyq.js} +2 -2
- package/bizar-dash/dist/assets/{mobile-CaZ0gEeJ.js.map → mobile-Dl1q7Cyq.js.map} +1 -1
- package/bizar-dash/dist/index.html +2 -2
- package/bizar-dash/dist/mobile.html +2 -2
- package/bizar-dash/src/server/glyphs/mdx-compiler.mjs +13 -0
- package/bizar-dash/src/web/views/glyphs/GlyphRenderer.tsx +240 -9
- package/config/skills/glyph/SKILL.md +87 -114
- package/package.json +1 -1
- package/bizar-dash/dist/assets/main-C4Dq4wTz.js +0 -347
- package/bizar-dash/dist/assets/main-C4Dq4wTz.js.map +0 -1
|
@@ -29,8 +29,8 @@
|
|
|
29
29
|
}
|
|
30
30
|
})();
|
|
31
31
|
</script>
|
|
32
|
-
<script type="module" crossorigin src="/assets/main-
|
|
33
|
-
<link rel="modulepreload" crossorigin href="/assets/mobile-
|
|
32
|
+
<script type="module" crossorigin src="/assets/main-CEazNxxy.js"></script>
|
|
33
|
+
<link rel="modulepreload" crossorigin href="/assets/mobile-Dl1q7Cyq.js">
|
|
34
34
|
<link rel="stylesheet" crossorigin href="/assets/mobile-CsZQAswA.css">
|
|
35
35
|
<link rel="stylesheet" crossorigin href="/assets/main-Nq8Dq3VR.css">
|
|
36
36
|
</head>
|
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
13
13
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
14
14
|
<link rel="stylesheet" href="https://rsms.me/inter/inter.css" />
|
|
15
|
-
<script type="module" crossorigin src="/assets/mobile-
|
|
16
|
-
<link rel="modulepreload" crossorigin href="/assets/mobile-
|
|
15
|
+
<script type="module" crossorigin src="/assets/mobile-DSb-t42Y.js"></script>
|
|
16
|
+
<link rel="modulepreload" crossorigin href="/assets/mobile-Dl1q7Cyq.js">
|
|
17
17
|
<link rel="stylesheet" crossorigin href="/assets/mobile-CsZQAswA.css">
|
|
18
18
|
</head>
|
|
19
19
|
<body>
|
|
@@ -199,6 +199,19 @@ function parseJsxValue(src) {
|
|
|
199
199
|
}
|
|
200
200
|
while (i < len) {
|
|
201
201
|
skipWs();
|
|
202
|
+
// v4.4.8 — After consuming a comma + whitespace, check for the
|
|
203
|
+
// closer before recursing. Without this, an array like `[a, b, ]`
|
|
204
|
+
// would call parseValue on `]`, which falls through to the
|
|
205
|
+
// bareword-identifier branch and returns `{__ident: ''}` as a
|
|
206
|
+
// phantom element. The trailing-comma-then-closer case was the
|
|
207
|
+
// user-visible bug in the v3-to-v4-consolidation glyph: the
|
|
208
|
+
// FileTree's last entry was the bareword phantom, which then
|
|
209
|
+
// crashed the React renderer (components.tsx:609 tried to read
|
|
210
|
+
// `t.bg` on undefined).
|
|
211
|
+
if (arr && src[i] === closer) {
|
|
212
|
+
i += 1;
|
|
213
|
+
return items;
|
|
214
|
+
}
|
|
202
215
|
if (arr) {
|
|
203
216
|
items.push(parseValue());
|
|
204
217
|
} else {
|
|
@@ -49,6 +49,142 @@ import {
|
|
|
49
49
|
Mockup,
|
|
50
50
|
} from './components';
|
|
51
51
|
|
|
52
|
+
// ---------------------------------------------------------------------------
|
|
53
|
+
// BlockErrorBoundary — v4.4.8
|
|
54
|
+
//
|
|
55
|
+
// A single broken block (e.g. a FileTree whose `data.entries` ended up
|
|
56
|
+
// not being an array because the parser silently produced a phantom
|
|
57
|
+
// bareword entry) used to throw inside React, which propagated up and
|
|
58
|
+
// blanked the whole canvas. The boundary catches throws per-block and
|
|
59
|
+
// renders an inline error card instead. The boundary also exposes its
|
|
60
|
+
// errors via a callback so we can surface them in the top-of-page
|
|
61
|
+
// "render errors" banner.
|
|
62
|
+
// ---------------------------------------------------------------------------
|
|
63
|
+
|
|
64
|
+
interface BlockErrorBoundaryProps {
|
|
65
|
+
blockId: string;
|
|
66
|
+
blockType: string;
|
|
67
|
+
children: React.ReactNode;
|
|
68
|
+
onError: (blockId: string, blockType: string, err: Error, info: React.ErrorInfo) => void;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
interface BlockErrorBoundaryState {
|
|
72
|
+
err: Error | null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
class BlockErrorBoundary extends React.Component<BlockErrorBoundaryProps, BlockErrorBoundaryState> {
|
|
76
|
+
state: BlockErrorBoundaryState = { err: null };
|
|
77
|
+
static getDerivedStateFromError(err: Error): BlockErrorBoundaryState {
|
|
78
|
+
return { err };
|
|
79
|
+
}
|
|
80
|
+
componentDidCatch(err: Error, info: React.ErrorInfo): void {
|
|
81
|
+
try {
|
|
82
|
+
this.props.onError(this.props.blockId, this.props.blockType, err, info);
|
|
83
|
+
} catch {
|
|
84
|
+
/* ignore — the boundary itself must not throw */
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
render(): React.ReactNode {
|
|
88
|
+
if (this.state.err) {
|
|
89
|
+
return (
|
|
90
|
+
<div
|
|
91
|
+
className="glyph-block-error"
|
|
92
|
+
role="alert"
|
|
93
|
+
style={{
|
|
94
|
+
border: '1px solid var(--error, #f85149)',
|
|
95
|
+
background: 'rgba(248, 81, 73, 0.06)',
|
|
96
|
+
borderRadius: 8,
|
|
97
|
+
padding: '12px 14px',
|
|
98
|
+
margin: '12px 0',
|
|
99
|
+
color: 'var(--text)',
|
|
100
|
+
fontFamily: 'var(--font-mono, ui-monospace, monospace)',
|
|
101
|
+
fontSize: 12,
|
|
102
|
+
lineHeight: 1.55,
|
|
103
|
+
}}
|
|
104
|
+
>
|
|
105
|
+
<strong style={{ color: 'var(--error, #f85149)', fontFamily: 'var(--font-sans, system-ui, sans-serif)', display: 'block', marginBottom: 4 }}>
|
|
106
|
+
{this.props.blockType} block crashed
|
|
107
|
+
</strong>
|
|
108
|
+
<div style={{ color: 'var(--text-muted)', marginBottom: 6 }}>
|
|
109
|
+
block id: <code>{this.props.blockId}</code>
|
|
110
|
+
</div>
|
|
111
|
+
<pre style={{ margin: 0, whiteSpace: 'pre-wrap', wordBreak: 'break-word', color: 'var(--text)' }}>
|
|
112
|
+
{this.state.err.message}
|
|
113
|
+
</pre>
|
|
114
|
+
</div>
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
return this.props.children;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// ---------------------------------------------------------------------------
|
|
122
|
+
// Block validation — v4.4.8
|
|
123
|
+
//
|
|
124
|
+
// Before handing block data to the component switch, verify that the
|
|
125
|
+
// shape matches the prop contract. Returns `{ ok: true }` when the
|
|
126
|
+
// block can be rendered, or `{ ok: false, error }` when a field is
|
|
127
|
+
// missing or wrong type. The renderer uses the error to skip the
|
|
128
|
+
// component and surface an inline message instead of throwing.
|
|
129
|
+
// ---------------------------------------------------------------------------
|
|
130
|
+
|
|
131
|
+
function validateBlock(b: Block): { ok: true } | { ok: false; error: string } {
|
|
132
|
+
const data = (b.data ?? {}) as Record<string, unknown>;
|
|
133
|
+
switch (b.type) {
|
|
134
|
+
case 'RichText':
|
|
135
|
+
return { ok: true };
|
|
136
|
+
case 'Callout':
|
|
137
|
+
return { ok: true };
|
|
138
|
+
case 'Checklist':
|
|
139
|
+
if (!Array.isArray(data.items)) return { ok: false, error: 'Checklist requires data.items to be an array' };
|
|
140
|
+
return { ok: true };
|
|
141
|
+
case 'Table':
|
|
142
|
+
if (!Array.isArray(data.columns)) return { ok: false, error: 'Table requires data.columns to be an array' };
|
|
143
|
+
if (!Array.isArray(data.rows)) return { ok: false, error: 'Table requires data.rows to be an array' };
|
|
144
|
+
if (data.rows.length > 0 && !Array.isArray(data.rows[0])) return { ok: false, error: 'Table.data.rows[0] must be an array (cell array)' };
|
|
145
|
+
return { ok: true };
|
|
146
|
+
case 'CodeTabs':
|
|
147
|
+
if (!Array.isArray(data.tabs)) return { ok: false, error: 'CodeTabs requires data.tabs to be an array' };
|
|
148
|
+
return { ok: true };
|
|
149
|
+
case 'Decision':
|
|
150
|
+
if (!Array.isArray(data.options)) return { ok: false, error: 'Decision requires data.options to be an array' };
|
|
151
|
+
return { ok: true };
|
|
152
|
+
case 'OpenQuestions':
|
|
153
|
+
if (!Array.isArray(data.questions)) return { ok: false, error: 'OpenQuestions requires data.questions to be an array' };
|
|
154
|
+
return { ok: true };
|
|
155
|
+
case 'FileTree':
|
|
156
|
+
if (!Array.isArray(data.entries)) return { ok: false, error: 'FileTree requires data.entries to be an array' };
|
|
157
|
+
// v4.4.8 — also catch the phantom bareword entry the parser used
|
|
158
|
+
// to emit (the v3-to-v4-consolidation glyph crash). An entry that
|
|
159
|
+
// is not an object means the parser miscounted braces.
|
|
160
|
+
for (const e of data.entries) {
|
|
161
|
+
if (e === null || typeof e !== 'object' || Array.isArray(e)) {
|
|
162
|
+
return { ok: false, error: `FileTree contains a malformed entry: ${JSON.stringify(e)}` };
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return { ok: true };
|
|
166
|
+
case 'Diff':
|
|
167
|
+
if (typeof data.before !== 'string') return { ok: false, error: 'Diff requires data.before to be a string' };
|
|
168
|
+
if (typeof data.after !== 'string') return { ok: false, error: 'Diff requires data.after to be a string' };
|
|
169
|
+
return { ok: true };
|
|
170
|
+
case 'Stat':
|
|
171
|
+
if (data.label === undefined || data.label === null) return { ok: false, error: 'Stat requires data.label' };
|
|
172
|
+
if (data.value === undefined || data.value === null) return { ok: false, error: 'Stat requires data.value' };
|
|
173
|
+
return { ok: true };
|
|
174
|
+
case 'Workflow':
|
|
175
|
+
if (!Array.isArray(data.steps)) return { ok: false, error: 'Workflow requires data.steps to be an array' };
|
|
176
|
+
return { ok: true };
|
|
177
|
+
case 'Mockup':
|
|
178
|
+
if (typeof data.html !== 'string') return { ok: false, error: 'Mockup requires data.html to be a string' };
|
|
179
|
+
return { ok: true };
|
|
180
|
+
case 'Diagram':
|
|
181
|
+
if (typeof data.dataHtml !== 'string') return { ok: false, error: 'Diagram requires data.dataHtml to be a string' };
|
|
182
|
+
return { ok: true };
|
|
183
|
+
default:
|
|
184
|
+
return { ok: false, error: `Unknown block type: ${(b as { type: string }).type}` };
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
52
188
|
// ---------------------------------------------------------------------------
|
|
53
189
|
// Types — match the server compiler output
|
|
54
190
|
// ---------------------------------------------------------------------------
|
|
@@ -72,7 +208,7 @@ interface CompiledGlyph {
|
|
|
72
208
|
slug: string;
|
|
73
209
|
frontmatter: Record<string, unknown>;
|
|
74
210
|
blocks: Block[];
|
|
75
|
-
errors: string
|
|
211
|
+
errors: Array<{ line?: number; message: string }>;
|
|
76
212
|
compiledAt: string;
|
|
77
213
|
}
|
|
78
214
|
|
|
@@ -162,6 +298,25 @@ export function GlyphRenderer({ slug, onClose, onCommentAdded }: Props) {
|
|
|
162
298
|
// Active pin (expanded thread view)
|
|
163
299
|
const [activePin, setActivePin] = useState<string | null>(null);
|
|
164
300
|
|
|
301
|
+
// v4.4.8 — Per-block render errors caught by BlockErrorBoundary. The
|
|
302
|
+
// boundary calls onError(err) which pushes into this array. We render
|
|
303
|
+
// the array at the top of the canvas in a prominent red banner so the
|
|
304
|
+
// user isn't left staring at a blank screen wondering what happened.
|
|
305
|
+
const [blockErrors, setBlockErrors] = useState<
|
|
306
|
+
Array<{ blockId: string; blockType: string; message: string }>
|
|
307
|
+
>([]);
|
|
308
|
+
const reportBlockError = React.useCallback(
|
|
309
|
+
(blockId: string, blockType: string, err: Error) => {
|
|
310
|
+
setBlockErrors((prev) => {
|
|
311
|
+
// dedupe by blockId so the same block crashing twice doesn't
|
|
312
|
+
// duplicate the entry
|
|
313
|
+
if (prev.some((e) => e.blockId === blockId)) return prev;
|
|
314
|
+
return [...prev, { blockId, blockType, message: err.message || String(err) }];
|
|
315
|
+
});
|
|
316
|
+
},
|
|
317
|
+
[],
|
|
318
|
+
);
|
|
319
|
+
|
|
165
320
|
const canvasRef = useRef<HTMLDivElement>(null);
|
|
166
321
|
|
|
167
322
|
// Section grouping — memoized so we don't re-walk the block array on every render
|
|
@@ -289,10 +444,58 @@ export function GlyphRenderer({ slug, onClose, onCommentAdded }: Props) {
|
|
|
289
444
|
setFullscreen((v) => !v);
|
|
290
445
|
}
|
|
291
446
|
|
|
292
|
-
// Render a single block
|
|
447
|
+
// Render a single block — wrapped in a BlockErrorBoundary so a single
|
|
448
|
+
// broken block (e.g. wrong data shape from a parser bug) shows an inline
|
|
449
|
+
// error card instead of blanking the whole canvas.
|
|
293
450
|
const renderBlock = (b: Block) => {
|
|
294
451
|
const id = b.id;
|
|
295
452
|
const data = (b.data ?? {}) as Record<string, unknown>;
|
|
453
|
+
// Validate data shape BEFORE handing to the component switch. A
|
|
454
|
+
// validation failure shows an inline error card immediately, no
|
|
455
|
+
// need for the boundary to catch it.
|
|
456
|
+
const validation = validateBlock(b);
|
|
457
|
+
if (!validation.ok) {
|
|
458
|
+
return (
|
|
459
|
+
<div
|
|
460
|
+
key={id}
|
|
461
|
+
id={id}
|
|
462
|
+
className="glyph-block-error"
|
|
463
|
+
role="alert"
|
|
464
|
+
style={{
|
|
465
|
+
border: '1px solid var(--error, #f85149)',
|
|
466
|
+
background: 'rgba(248, 81, 73, 0.06)',
|
|
467
|
+
borderRadius: 8,
|
|
468
|
+
padding: '12px 14px',
|
|
469
|
+
margin: '12px 0',
|
|
470
|
+
color: 'var(--text)',
|
|
471
|
+
fontFamily: 'var(--font-mono, ui-monospace, monospace)',
|
|
472
|
+
fontSize: 12,
|
|
473
|
+
lineHeight: 1.55,
|
|
474
|
+
}}
|
|
475
|
+
>
|
|
476
|
+
<strong style={{ color: 'var(--error, #f85149)', fontFamily: 'var(--font-sans, system-ui, sans-serif)', display: 'block', marginBottom: 4 }}>
|
|
477
|
+
{b.type} block invalid
|
|
478
|
+
</strong>
|
|
479
|
+
<div style={{ color: 'var(--text-muted)', marginBottom: 6 }}>
|
|
480
|
+
block id: <code>{id}</code>
|
|
481
|
+
</div>
|
|
482
|
+
<pre style={{ margin: 0, whiteSpace: 'pre-wrap', wordBreak: 'break-word' }}>{validation.error}</pre>
|
|
483
|
+
</div>
|
|
484
|
+
);
|
|
485
|
+
}
|
|
486
|
+
// Wrap the actual render in an error boundary so even runtime
|
|
487
|
+
// errors (bad component props, undefined data, etc.) don't crash
|
|
488
|
+
// the whole canvas.
|
|
489
|
+
return (
|
|
490
|
+
<BlockErrorBoundary key={id} blockId={id} blockType={b.type} onError={reportBlockError}>
|
|
491
|
+
{renderBlockInner(b, id, data)}
|
|
492
|
+
</BlockErrorBoundary>
|
|
493
|
+
);
|
|
494
|
+
};
|
|
495
|
+
|
|
496
|
+
// Inner renderer — split out so the outer renderBlock can validate +
|
|
497
|
+
// wrap in an error boundary without nesting the switch in the JSX tree.
|
|
498
|
+
const renderBlockInner = (b: Block, id: string, data: Record<string, unknown>) => {
|
|
296
499
|
switch (b.type) {
|
|
297
500
|
case 'RichText':
|
|
298
501
|
return <RichText key={id} id={id}>{b.childrenMarkdown ?? ''}</RichText>;
|
|
@@ -506,13 +709,41 @@ export function GlyphRenderer({ slug, onClose, onCommentAdded }: Props) {
|
|
|
506
709
|
</div>
|
|
507
710
|
</header>
|
|
508
711
|
|
|
509
|
-
{/*
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
712
|
+
{/* v4.4.8 — Render errors banner. Two layers:
|
|
713
|
+
1. Compiler warnings from the MDX parser (e.g. unknown
|
|
714
|
+
tag, unclosed block). Lightweight, yellow border.
|
|
715
|
+
2. Block render errors caught by BlockErrorBoundary. These
|
|
716
|
+
are the "one block crashed but the rest still rendered"
|
|
717
|
+
cases. Red border, prominent, listed first so the user
|
|
718
|
+
sees them before scrolling. */}
|
|
719
|
+
{(blockErrors.length > 0 || compiled.errors?.length > 0) && (
|
|
720
|
+
<div
|
|
721
|
+
className="glyph-render-errors"
|
|
722
|
+
role="alert"
|
|
723
|
+
style={{
|
|
724
|
+
border: '1px solid var(--error, #f85149)',
|
|
725
|
+
background: 'rgba(248, 81, 73, 0.08)',
|
|
726
|
+
borderRadius: 8,
|
|
727
|
+
padding: '12px 16px',
|
|
728
|
+
margin: '0 0 16px 0',
|
|
729
|
+
color: 'var(--text)',
|
|
730
|
+
}}
|
|
731
|
+
>
|
|
732
|
+
<strong style={{ color: 'var(--error, #f85149)', display: 'block', marginBottom: 8 }}>
|
|
733
|
+
{blockErrors.length > 0
|
|
734
|
+
? `${blockErrors.length} block${blockErrors.length === 1 ? '' : 's'} failed to render`
|
|
735
|
+
: 'Compiler warnings'}
|
|
736
|
+
</strong>
|
|
737
|
+
<ul style={{ margin: 0, paddingLeft: 20, fontFamily: 'var(--font-mono, ui-monospace, monospace)', fontSize: 12, lineHeight: 1.6 }}>
|
|
738
|
+
{blockErrors.map((e, i) => (
|
|
739
|
+
<li key={`be-${i}`}>
|
|
740
|
+
<strong>{e.blockType}</strong> (<code>{e.blockId}</code>): {e.message}
|
|
741
|
+
</li>
|
|
742
|
+
))}
|
|
743
|
+
{compiled.errors?.map((e, i) => (
|
|
744
|
+
<li key={`ce-${i}`}>
|
|
745
|
+
{e.line ? `line ${e.line}: ` : ''}{e.message}
|
|
746
|
+
</li>
|
|
516
747
|
))}
|
|
517
748
|
</ul>
|
|
518
749
|
</div>
|
|
@@ -1,174 +1,147 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: glyph
|
|
3
|
-
description: Create and consume Bizar glyphs — visual
|
|
4
|
-
version:
|
|
3
|
+
description: Create and consume Bizar glyphs — visual artifacts at `artifacts/<slug>/`. Use for plans, recaps, design proposals, postmortems, handoffs. Quick reference for the block vocabulary and the 5 things that actually break a glyph.
|
|
4
|
+
version: 3
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
# Glyphs
|
|
7
|
+
# Glyphs
|
|
8
8
|
|
|
9
|
-
A **glyph** is
|
|
10
|
-
|
|
11
|
-
## When to create a glyph
|
|
12
|
-
|
|
13
|
-
- **Design proposal** — you're proposing a new component, system, or workflow
|
|
14
|
-
- **Decision recap** — multiple options were considered; one was chosen; the reasoning matters
|
|
15
|
-
- **Postmortem** — something broke; here's the timeline and root cause
|
|
16
|
-
- **Implementation plan** — breaking down a non-trivial feature into phases
|
|
17
|
-
- **Handoff** — work moving from one agent to another
|
|
18
|
-
|
|
19
|
-
If it's a one-line fix, don't make a glyph. If it's a 5-minute edit, don't make a glyph. Glyphs are for work that needs visible structure.
|
|
20
|
-
|
|
21
|
-
## File structure
|
|
9
|
+
A **glyph** is an MDX file the dashboard renders with a comment-pin overlay and right-click menu. Three files per glyph:
|
|
22
10
|
|
|
23
11
|
```
|
|
24
12
|
artifacts/<slug>/
|
|
25
|
-
├── meta.json
|
|
26
|
-
├── artifact.mdx
|
|
27
|
-
└── comments.json
|
|
13
|
+
├── meta.json ← title, status, author, created
|
|
14
|
+
├── artifact.mdx ← source of truth
|
|
15
|
+
└── comments.json ← free-placed pins (mutable)
|
|
28
16
|
```
|
|
29
17
|
|
|
30
|
-
|
|
18
|
+
Two locations are scanned: `<projectRoot>/.bizar/artifacts/` (preferred) and `~/.config/opencode/artifacts/` (global).
|
|
31
19
|
|
|
32
20
|
## Frontmatter
|
|
33
21
|
|
|
34
22
|
```yaml
|
|
35
23
|
---
|
|
36
24
|
title: "Short, action-oriented title"
|
|
37
|
-
brief: "One sentence
|
|
25
|
+
brief: "One sentence."
|
|
38
26
|
status: draft | review | shipped | archived
|
|
39
27
|
kind: plan | postmortem | recap | design
|
|
40
28
|
---
|
|
41
29
|
```
|
|
42
30
|
|
|
43
|
-
##
|
|
31
|
+
## The blocks at a glance
|
|
44
32
|
|
|
45
|
-
|
|
46
|
-
|
|
33
|
+
| Block | What it shows | Required `data` |
|
|
34
|
+
| --- | --- | --- |
|
|
35
|
+
| `<RichText>` | Prose, lists, code blocks | none — body is markdown |
|
|
36
|
+
| `<Callout tone="info\|warn\|success\|danger">` | Boxed callout | none (tone defaults to info) |
|
|
37
|
+
| `<Stat label value trend hint />` | Big number with trend | `label`, `value` |
|
|
38
|
+
| `<Checklist items={[...]} />` | Checkbox list | `items: [{id,label,checked}]` |
|
|
39
|
+
| `<Table columns rows />` | Tabular data | `columns: []`, `rows: [[]]` |
|
|
40
|
+
| `<CodeTabs tabs />` | Tabbed code blocks | `tabs: [{id,label,language,code}]` |
|
|
41
|
+
| `<Decision title question options />` | Multi-option choice | `options: [{id,label,detail,recommended?}]` |
|
|
42
|
+
| `<OpenQuestions questions />` | Unanswered questions | `questions: [{id,label,kind,options?}]` |
|
|
43
|
+
| `<FileTree title entries />` | File-by-file change list | `entries: [{path,change,note?}]` |
|
|
44
|
+
| `<Diff before after filename language mode />` | Before/after diff | `before`, `after` |
|
|
45
|
+
| `<Workflow steps connections />` | Node graph | `steps: [{id,label,type}]` |
|
|
46
|
+
| `<Mockup title x y w h html />` | Inline UI mockup | `html` |
|
|
47
|
+
| `<Diagram title dataHtml dataCss />` | Inline SVG | `dataHtml` |
|
|
47
48
|
|
|
48
|
-
|
|
49
|
+
Every block needs a unique `id`. `RichText` and `Callout` use open/close tags with markdown children; everything else is self-closing.
|
|
49
50
|
|
|
50
|
-
|
|
51
|
+
## Skeleton
|
|
51
52
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
{ id: "i1", label: "...", checked: true },
|
|
56
|
-
{ id: "i2", label: "...", checked: false }
|
|
57
|
-
]}
|
|
58
|
-
/>
|
|
59
|
-
|
|
60
|
-
<Table
|
|
61
|
-
id="..."
|
|
62
|
-
columns={["A", "B"]}
|
|
63
|
-
rows={[["x", "y"], ["p", "q"]]}
|
|
64
|
-
/>
|
|
53
|
+
```mdx
|
|
54
|
+
<RichText id="overview">
|
|
55
|
+
## What this covers
|
|
65
56
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
tabs={[
|
|
69
|
-
{ id: "t1", label: "file.ts", language: "typescript", code: "...", caption: "..." }
|
|
70
|
-
]}
|
|
71
|
-
/>
|
|
57
|
+
A one-paragraph summary.
|
|
58
|
+
</RichText>
|
|
72
59
|
|
|
73
|
-
<
|
|
74
|
-
id="..."
|
|
75
|
-
title="..."
|
|
76
|
-
question="..."
|
|
77
|
-
options={[
|
|
78
|
-
{ id: "a", label: "...", detail: "...", recommended: true }
|
|
79
|
-
]}
|
|
80
|
-
/>
|
|
60
|
+
<Stat id="headline" label="X" value="7" trend="flat" />
|
|
81
61
|
|
|
82
|
-
<
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
{ id: "q1", label: "...", kind: "choice|text|multi", options: ["A", "B"] }
|
|
86
|
-
]}
|
|
87
|
-
/>
|
|
62
|
+
<Callout id="warning" tone="warn">
|
|
63
|
+
One important thing.
|
|
64
|
+
</Callout>
|
|
88
65
|
|
|
89
66
|
<FileTree
|
|
90
|
-
id="
|
|
91
|
-
title="
|
|
67
|
+
id="files"
|
|
68
|
+
title="Files changed"
|
|
92
69
|
entries={[
|
|
93
|
-
{ path: "src/foo.ts", change: "
|
|
70
|
+
{ path: "src/foo.ts", change: "modified", note: "why" },
|
|
71
|
+
{ path: "src/bar.ts", change: "added" },
|
|
94
72
|
]}
|
|
95
73
|
/>
|
|
96
74
|
|
|
97
75
|
<Workflow
|
|
98
|
-
id="
|
|
76
|
+
id="flow"
|
|
99
77
|
steps={[
|
|
100
|
-
{ id: "s1", label: "
|
|
78
|
+
{ id: "s1", label: "Step 1", type: "task" },
|
|
79
|
+
{ id: "s2", label: "Step 2", type: "task" },
|
|
101
80
|
]}
|
|
102
81
|
connections={[
|
|
103
|
-
{ from: "s1", to: "s2"
|
|
82
|
+
{ from: "s1", to: "s2" },
|
|
104
83
|
]}
|
|
105
84
|
/>
|
|
106
|
-
|
|
107
|
-
<Mockup
|
|
108
|
-
id="..."
|
|
109
|
-
title="..."
|
|
110
|
-
x={40} y={120} w={280} h={180}
|
|
111
|
-
html="<div class='mockup-card'>...</div>"
|
|
112
|
-
/>
|
|
113
|
-
|
|
114
|
-
<Diagram
|
|
115
|
-
id="..."
|
|
116
|
-
title="..."
|
|
117
|
-
dataHtml="<svg>...</svg>"
|
|
118
|
-
dataCss=".diagram-node { fill: var(--bg-1); }"
|
|
119
|
-
/>
|
|
120
85
|
```
|
|
121
86
|
|
|
122
|
-
##
|
|
87
|
+
## The 5 things that break a glyph
|
|
123
88
|
|
|
124
|
-
|
|
89
|
+
Read this BEFORE writing. Each one is a real bug we've shipped.
|
|
125
90
|
|
|
126
|
-
|
|
91
|
+
### 1. Backticks inside attribute strings
|
|
127
92
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
93
|
+
The parser scans for `` ` `` to delimit template literals. A literal backtick inside a string — even inside double-quotes — closes the surrounding context and breaks parsing.
|
|
94
|
+
|
|
95
|
+
```
|
|
96
|
+
WRONG: Run `bizar install` to bootstrap.
|
|
97
|
+
RIGHT: Run 'bizar install' to bootstrap.
|
|
98
|
+
RIGHT: Run the installer to bootstrap.
|
|
134
99
|
```
|
|
135
100
|
|
|
136
|
-
###
|
|
101
|
+
### 2. The parser doesn't track strings when counting braces
|
|
137
102
|
|
|
138
|
-
|
|
103
|
+
`{` and `}` inside string values still increment the brace depth walker. With brace-laden strings the walker terminates at the wrong closing brace and the value gets truncated.
|
|
139
104
|
|
|
140
|
-
|
|
105
|
+
**Rule:** keep string values brace-free. If you must use `{...}` in a note, escape or rephrase.
|
|
141
106
|
|
|
142
|
-
|
|
107
|
+
### 3. `FileTree` `change` field must be exact
|
|
143
108
|
|
|
144
|
-
|
|
109
|
+
`change` must be `added` | `modified` | `removed` | `renamed`. Anything else (empty string, `null`, typo) makes the renderer crash on `t.bg`.
|
|
145
110
|
|
|
146
|
-
|
|
111
|
+
```
|
|
112
|
+
WRONG: change: null, change: "", change: "Modified"
|
|
113
|
+
RIGHT: change: "modified"
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
When in doubt, use `modified` with a `note` that explains why.
|
|
117
|
+
|
|
118
|
+
### 4. Two blocks with the same id
|
|
147
119
|
|
|
148
|
-
|
|
120
|
+
Comment pins and error reporting are keyed by block id. Duplicates collide. Each `id` must be unique within a glyph.
|
|
149
121
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
"created": "2026-06-26T20:30:00.000Z",
|
|
157
|
-
"lastEdited": "2026-06-26T20:30:00.000Z"
|
|
158
|
-
}
|
|
122
|
+
### 5. MDX heading with `<N`
|
|
123
|
+
|
|
124
|
+
```
|
|
125
|
+
WRONG: ## Mobile <768px ← <7 looks like a malformed tag
|
|
126
|
+
RIGHT: ## Mobile (under 768px)
|
|
127
|
+
RIGHT: ## Mobile: under 768px
|
|
159
128
|
```
|
|
160
129
|
|
|
161
|
-
##
|
|
130
|
+
## Validate before shipping
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
node -e "import('./bizar-dash/src/server/glyphs/mdx-compiler.mjs').then(m => { \
|
|
134
|
+
const fs = require('node:fs'); \
|
|
135
|
+
const out = m.compileGlyphMdxSync(fs.readFileSync('artifacts/<slug>/artifact.mdx','utf8')); \
|
|
136
|
+
console.log('blocks:', out.blocks.length, 'errors:', out.errors.length); \
|
|
137
|
+
out.errors.forEach(e => console.error(' line', e.line, ':', e.message)); \
|
|
138
|
+
})"
|
|
139
|
+
```
|
|
162
140
|
|
|
163
|
-
|
|
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
|
|
141
|
+
If errors > 0, fix the MDX before opening the glyph in the browser. When a block DOES fail in the browser, v4.4.8+ shows a red banner with block id + error message — no more blank screen.
|
|
168
142
|
|
|
169
143
|
## See also
|
|
170
144
|
|
|
171
|
-
- `bizar-dash/src/
|
|
172
|
-
- `bizar-dash/src/
|
|
173
|
-
- `
|
|
174
|
-
- The user's Obsidian vault for the project's design notes
|
|
145
|
+
- `bizar-dash/src/server/glyphs/mdx-compiler.mjs` — parser
|
|
146
|
+
- `bizar-dash/src/web/views/glyphs/GlyphRenderer.tsx` — React renderer (has the error banner)
|
|
147
|
+
- `bizar-dash/src/web/views/glyphs/components.tsx` — block component implementations
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@polderlabs/bizar",
|
|
3
|
-
"version": "4.4.
|
|
3
|
+
"version": "4.4.9",
|
|
4
4
|
"description": "Norse-pantheon multi-agent system for opencode — 13 agents across 4 cost tiers with cost-aware routing, plans, and a configurable agent harness. v4 ships as a single npm package bundling the dashboard server, opencode plugin, and typed SDK.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|