plotleaf 0.0.0-stage → 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.
@@ -0,0 +1,24 @@
1
+ [
2
+ { "date": "2026-02-01", "teams": 1113 },
3
+ { "date": "2026-02-08", "teams": 1118 },
4
+ { "date": "2026-02-15", "teams": 1145 },
5
+ { "date": "2026-02-22", "teams": 1116 },
6
+ { "date": "2026-03-01", "teams": 1121 },
7
+ { "date": "2026-03-08", "teams": 1140 },
8
+ { "date": "2026-03-15", "teams": 1189 },
9
+ { "date": "2026-03-22", "teams": 1166 },
10
+ { "date": "2026-03-29", "teams": 1190 },
11
+ { "date": "2026-04-05", "teams": 1194 },
12
+ { "date": "2026-04-12", "teams": 1160 },
13
+ { "date": "2026-04-19", "teams": 1182 },
14
+ { "date": "2026-04-26", "teams": 1244 },
15
+ { "date": "2026-05-03", "teams": 1218 },
16
+ { "date": "2026-05-10", "teams": 1226 },
17
+ { "date": "2026-05-17", "teams": 1263 },
18
+ { "date": "2026-05-24", "teams": 1289 },
19
+ { "date": "2026-05-31", "teams": 1340 },
20
+ { "date": "2026-06-07", "teams": 1307 },
21
+ { "date": "2026-06-14", "teams": 1352 },
22
+ { "date": "2026-06-21", "teams": 1404 },
23
+ { "date": "2026-06-28", "teams": 1492 }
24
+ ]
@@ -0,0 +1,5 @@
1
+ [
2
+ { "plan": "Pro", "teams": 884, "change": 0.41, "alerts": 2140 },
3
+ { "plan": "Team", "teams": 432, "change": 0.22, "alerts": 861 },
4
+ { "plan": "Free", "teams": 176, "change": 0.09, "alerts": 291 }
5
+ ]
@@ -0,0 +1,31 @@
1
+ ---
2
+ title: Feature Adoption — User Alerts
3
+ description: Adoption, team mix and the next growth milestone.
4
+ ---
5
+
6
+ # Feature Adoption: User Alerts
7
+
8
+ This fictional product report uses synthetic data for demonstration.
9
+
10
+ ## Summary
11
+
12
+ In this fictional launch scenario, User Alerts is leaving beta. **1,492** teams had configured at least one alert by the end of June, up **34%** over the quarter from **1,113**. Pro-plan teams account for most of the total.
13
+
14
+ ::area{data="./adoption.json" x="date" y="teams" xType="date" title="Teams with an alert configured" target="1600" targetLabel="Target"}
15
+
16
+ ## Adoption
17
+
18
+ Every plan picked it up through the beta, but the mix shifted toward Pro. Teams that set an alert in their first week stayed active at **61%**, the same rate as the existing base.
19
+
20
+ :::table{data="./plans.json" title="By plan"}
21
+ ::column{field="plan" label="Plan"}
22
+ ::column{field="teams" label="Teams" format="number"}
23
+ ::column{field="change" label="Δ QoQ" format="percent" sign="always" semantics="higher-is-better"}
24
+ ::column{field="alerts" label="Alerts set" format="number"}
25
+ :::
26
+
27
+ ### Key findings
28
+
29
+ Pro-plan teams led the expansion. Activation held steady at **61%** week over week. Churn fell to **2.1%**, the lowest this year.
30
+
31
+ All data, comparisons and findings are fictional and supplied by the example author.
@@ -0,0 +1,26 @@
1
+ [
2
+ {
3
+ "category": "Apparel",
4
+ "orders": 5680,
5
+ "revenue": 323760,
6
+ "change": 0.184
7
+ },
8
+ {
9
+ "category": "Home",
10
+ "orders": 3240,
11
+ "revenue": 252720,
12
+ "change": 0.123
13
+ },
14
+ {
15
+ "category": "Accessories",
16
+ "orders": 2360,
17
+ "revenue": 165200,
18
+ "change": 0.328
19
+ },
20
+ {
21
+ "category": "Outdoors",
22
+ "orders": 1200,
23
+ "revenue": 82480,
24
+ "change": 0.176
25
+ }
26
+ ]
@@ -0,0 +1,30 @@
1
+ [
2
+ {
3
+ "date": "2026-11-01",
4
+ "orders": 1420
5
+ },
6
+ {
7
+ "date": "2026-11-02",
8
+ "orders": 1630
9
+ },
10
+ {
11
+ "date": "2026-11-03",
12
+ "orders": 1740
13
+ },
14
+ {
15
+ "date": "2026-11-04",
16
+ "orders": 1890
17
+ },
18
+ {
19
+ "date": "2026-11-05",
20
+ "orders": 1610
21
+ },
22
+ {
23
+ "date": "2026-11-06",
24
+ "orders": 1950
25
+ },
26
+ {
27
+ "date": "2026-11-07",
28
+ "orders": 2240
29
+ }
30
+ ]
@@ -0,0 +1,45 @@
1
+ ---
2
+ title: Storefront Overview
3
+ description: A fictional weekly retail report demonstrating plotleaf components.
4
+ ---
5
+
6
+ # Storefront Overview
7
+
8
+ A fictional store's weekly performance, using synthetic data for demonstration. Orders grew **18.2%**, while returns fell **12%** from the previous period.
9
+
10
+ :::grid{columns="4"}
11
+ ::metric{label="Orders" value="12480" comparison="18.2% vs previous week" direction="up" sentiment="positive"}
12
+ ::metric{label="Revenue" value="824160" format="currency:USD" comparison="24.1% vs previous week" direction="up" sentiment="positive"}
13
+ ::metric{label="Returns" value="384" comparison="12% vs previous week" direction="down" sentiment="positive"}
14
+ ::metric{label="Return rate" value="0.03076923076923077" format="percent" comparison="0.4 pt vs previous week" direction="down" sentiment="positive"}
15
+ :::
16
+
17
+ ## Sales and returns
18
+
19
+ The store received **12,480 orders** over seven days. Size-related returns made up most of the **384 returns**, suggesting a useful area for clearer product guidance.
20
+
21
+ :::grid{columns="2"}
22
+ ::area{data="./orders.json" x="date" y="orders" xType="date" title="Orders by day" target="2000" targetLabel="Daily target"}
23
+ ::bar{data="./returns.json" x="reason" y="count" title="Returns by reason"}
24
+ :::
25
+
26
+ ## Sales by category
27
+
28
+ Apparel accounts for the largest share of orders. Accessories showed the strongest relative growth.
29
+
30
+ :::table{data="./categories.json" title="Category performance"}
31
+ ::column{field="category" label="Category"}
32
+ ::column{field="orders" label="Orders" format="number"}
33
+ ::column{field="revenue" label="Revenue" format="currency:USD"}
34
+ ::column{field="change" label="Δ week" format="percent" sign="always" semantics="higher-is-better"}
35
+ :::
36
+
37
+ ### Key findings
38
+
39
+ - **Growth spans every category.** All four categories increased orders over the previous week.
40
+ - **Returns declined.** The return count and rate both improved.
41
+ - **Size guidance is the next opportunity.** Review fit descriptions before changing the return policy.
42
+
43
+ :::callout{title="About this example"}
44
+ All data, comparisons and findings are fictional. This example covers 1–7 November 2026 and contains no customer or organization data. An author prepares the analysis; plotleaf renders it.
45
+ :::
@@ -0,0 +1,10 @@
1
+ [
2
+ {
3
+ "reason": "Wrong size",
4
+ "count": 228
5
+ },
6
+ {
7
+ "reason": "Changed mind",
8
+ "count": 156
9
+ }
10
+ ]
package/package.json CHANGED
@@ -1,6 +1,72 @@
1
1
  {
2
2
  "name": "plotleaf",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0",
4
+ "description": "Beautiful, interactive reports from Markdown and JSON. Built for agents.",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "bin": {
8
+ "plotleaf": "./dist/cli.js"
9
+ },
10
+ "engines": {
11
+ "node": ">=22"
12
+ },
13
+ "packageManager": "bun@1.4.4",
14
+ "files": [
15
+ "dist/cli.js",
16
+ "dist/assets/base.js",
17
+ "dist/assets/charts.js",
18
+ "dist/assets/styles.css",
19
+ "dist/assets/notices.txt",
20
+ "schema/components.json",
21
+ "examples/storefront/*.md",
22
+ "examples/storefront/*.json",
23
+ "examples/adoption/*.md",
24
+ "examples/adoption/*.json",
25
+ "skills/plotleaf/SKILL.md",
26
+ "README.md",
27
+ "LICENSE",
28
+ "THIRD_PARTY_NOTICES.txt"
29
+ ],
30
+ "scripts": {
31
+ "format": "bun fmt src scripts tests playwright.config.ts package.json tsconfig.json tsconfig.browser.json tsconfig.dev.json bunfig.toml .github/workflows/ci.yml",
32
+ "build": "bun scripts/build.ts",
33
+ "typecheck": "bun node_modules/typescript/bin/tsc --noEmit -p tsconfig.json && bun node_modules/typescript/bin/tsc --noEmit -p tsconfig.browser.json && bun node_modules/typescript/bin/tsc --noEmit -p tsconfig.dev.json",
34
+ "typecheck:tsrs": "bun node_modules/tsrs/bin/tsrs --noEmit --fast false -p tsconfig.json && bun node_modules/tsrs/bin/tsrs --noEmit --fast false -p tsconfig.browser.json && bun node_modules/tsrs/bin/tsrs --noEmit --fast false -p tsconfig.dev.json",
35
+ "test": "bun test",
36
+ "test:browser": "node node_modules/@playwright/test/cli.js test",
37
+ "test:consumer": "node tests/consumer/smoke.mjs",
38
+ "check": "bun run typecheck && bun run build && bun run test && bun run test:consumer",
39
+ "audit:package": "node scripts/audit-package.mjs"
40
+ },
41
+ "dependencies": {
42
+ "chokidar": "6.0.0",
43
+ "open": "11.0.4",
44
+ "rehype-sanitize": "6.0.0",
45
+ "rehype-stringify": "10.0.1",
46
+ "remark-directive": "4.0.0",
47
+ "remark-frontmatter": "5.0.0",
48
+ "remark-parse": "11.0.0",
49
+ "remark-rehype": "11.1.2",
50
+ "unified": "11.0.5",
51
+ "yaml": "2.9.1",
52
+ "zod": "4.6.7"
53
+ },
54
+ "devDependencies": {
55
+ "@playwright/test": "1.65.0",
56
+ "@types/bun": "1.4.4",
57
+ "@types/hast": "3.0.5",
58
+ "@types/mdast": "4.0.4",
59
+ "@types/node": "22.20.5",
60
+ "echarts": "6.1.0",
61
+ "tsrs": "0.23.0-ts7.1.0-beta",
62
+ "typescript": "7.0.2"
63
+ },
64
+ "keywords": [
65
+ "markdown",
66
+ "reports",
67
+ "data-visualization",
68
+ "charts",
69
+ "cli",
70
+ "agents"
71
+ ]
72
+ }
@@ -0,0 +1,416 @@
1
+ {
2
+ "version": 1,
3
+ "syntax": "markdown-directives",
4
+ "attributes": "Literal strings decoded according to each property schema. No JavaScript expressions.",
5
+ "limits": {
6
+ "markdownBytes": 1048576,
7
+ "jsonBytes": 10485760,
8
+ "totalBytes": 26214400,
9
+ "datasets": 64,
10
+ "rows": 100000,
11
+ "chartRows": 10000,
12
+ "tableRows": 1000,
13
+ "tableColumns": 40
14
+ },
15
+ "data": {
16
+ "shape": "array of flat JSON records",
17
+ "resolution": "relative to the report directory; local .json files only",
18
+ "percent": "ratios: 0.12 displays as 12%",
19
+ "percentagePoints": "point values: 0.8 displays as 0.8 pt",
20
+ "currency": "major units; omit .00 on whole amounts, preserve nonzero cents",
21
+ "locale": "en-GB",
22
+ "timeZone": "UTC"
23
+ },
24
+ "components": {
25
+ "grid": {
26
+ "description": "A responsive row of one to four components.",
27
+ "forms": [
28
+ "container"
29
+ ],
30
+ "children": [
31
+ "metric",
32
+ "line",
33
+ "area",
34
+ "bar",
35
+ "table",
36
+ "callout"
37
+ ],
38
+ "props": {
39
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
40
+ "type": "object",
41
+ "properties": {
42
+ "columns": {
43
+ "default": 2,
44
+ "type": "integer",
45
+ "minimum": 1,
46
+ "maximum": 4
47
+ }
48
+ },
49
+ "required": [
50
+ "columns"
51
+ ],
52
+ "additionalProperties": false
53
+ },
54
+ "example": ":::grid{columns=\"4\"}\n::metric{label=\"Teams\" value=\"1492\"}\n:::"
55
+ },
56
+ "metric": {
57
+ "description": "A prepared numeric value with optional comparison, direction and sentiment.",
58
+ "forms": [
59
+ "leaf"
60
+ ],
61
+ "children": [],
62
+ "props": {
63
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
64
+ "type": "object",
65
+ "properties": {
66
+ "label": {
67
+ "type": "string",
68
+ "minLength": 1,
69
+ "maxLength": 1000
70
+ },
71
+ "value": {
72
+ "type": "number"
73
+ },
74
+ "format": {
75
+ "default": "number",
76
+ "type": "string",
77
+ "pattern": "^(number|percent|percentage-points|currency:[A-Z]{3})$"
78
+ },
79
+ "comparison": {
80
+ "type": "string",
81
+ "minLength": 1,
82
+ "maxLength": 1000
83
+ },
84
+ "direction": {
85
+ "type": "string",
86
+ "enum": [
87
+ "up",
88
+ "down",
89
+ "flat"
90
+ ]
91
+ },
92
+ "sentiment": {
93
+ "default": "neutral",
94
+ "type": "string",
95
+ "enum": [
96
+ "positive",
97
+ "negative",
98
+ "neutral"
99
+ ]
100
+ }
101
+ },
102
+ "required": [
103
+ "label",
104
+ "value",
105
+ "format",
106
+ "sentiment"
107
+ ],
108
+ "additionalProperties": false
109
+ },
110
+ "example": "::metric{label=\"Conversion\" value=\"0.103\" format=\"percent\"}"
111
+ },
112
+ "line": {
113
+ "description": "An interactive single-series line chart from local JSON.",
114
+ "forms": [
115
+ "leaf"
116
+ ],
117
+ "children": [],
118
+ "props": {
119
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
120
+ "type": "object",
121
+ "properties": {
122
+ "data": {
123
+ "type": "string",
124
+ "minLength": 1,
125
+ "maxLength": 1000
126
+ },
127
+ "x": {
128
+ "type": "string",
129
+ "minLength": 1,
130
+ "maxLength": 1000
131
+ },
132
+ "y": {
133
+ "type": "string",
134
+ "minLength": 1,
135
+ "maxLength": 1000
136
+ },
137
+ "title": {
138
+ "type": "string",
139
+ "minLength": 1,
140
+ "maxLength": 1000
141
+ },
142
+ "format": {
143
+ "default": "number",
144
+ "type": "string",
145
+ "pattern": "^(number|percent|percentage-points|currency:[A-Z]{3})$"
146
+ },
147
+ "xType": {
148
+ "default": "category",
149
+ "type": "string",
150
+ "enum": [
151
+ "category",
152
+ "date",
153
+ "number"
154
+ ]
155
+ },
156
+ "target": {
157
+ "type": "number"
158
+ },
159
+ "targetLabel": {
160
+ "type": "string",
161
+ "minLength": 1,
162
+ "maxLength": 1000
163
+ }
164
+ },
165
+ "required": [
166
+ "data",
167
+ "x",
168
+ "y",
169
+ "title",
170
+ "format",
171
+ "xType"
172
+ ],
173
+ "additionalProperties": false
174
+ },
175
+ "example": "::line{data=\"./usage.json\" x=\"date\" y=\"teams\" xType=\"date\" title=\"Active teams\"}"
176
+ },
177
+ "area": {
178
+ "description": "A line chart with a restrained area fill.",
179
+ "forms": [
180
+ "leaf"
181
+ ],
182
+ "children": [],
183
+ "props": {
184
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
185
+ "type": "object",
186
+ "properties": {
187
+ "data": {
188
+ "type": "string",
189
+ "minLength": 1,
190
+ "maxLength": 1000
191
+ },
192
+ "x": {
193
+ "type": "string",
194
+ "minLength": 1,
195
+ "maxLength": 1000
196
+ },
197
+ "y": {
198
+ "type": "string",
199
+ "minLength": 1,
200
+ "maxLength": 1000
201
+ },
202
+ "title": {
203
+ "type": "string",
204
+ "minLength": 1,
205
+ "maxLength": 1000
206
+ },
207
+ "format": {
208
+ "default": "number",
209
+ "type": "string",
210
+ "pattern": "^(number|percent|percentage-points|currency:[A-Z]{3})$"
211
+ },
212
+ "xType": {
213
+ "default": "category",
214
+ "type": "string",
215
+ "enum": [
216
+ "category",
217
+ "date",
218
+ "number"
219
+ ]
220
+ },
221
+ "target": {
222
+ "type": "number"
223
+ },
224
+ "targetLabel": {
225
+ "type": "string",
226
+ "minLength": 1,
227
+ "maxLength": 1000
228
+ }
229
+ },
230
+ "required": [
231
+ "data",
232
+ "x",
233
+ "y",
234
+ "title",
235
+ "format",
236
+ "xType"
237
+ ],
238
+ "additionalProperties": false
239
+ },
240
+ "example": "::area{data=\"./usage.json\" x=\"date\" y=\"teams\" xType=\"date\" title=\"Active teams\" target=\"1600\"}"
241
+ },
242
+ "bar": {
243
+ "description": "An interactive category bar chart with a zero baseline.",
244
+ "forms": [
245
+ "leaf"
246
+ ],
247
+ "children": [],
248
+ "props": {
249
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
250
+ "type": "object",
251
+ "properties": {
252
+ "data": {
253
+ "type": "string",
254
+ "minLength": 1,
255
+ "maxLength": 1000
256
+ },
257
+ "x": {
258
+ "type": "string",
259
+ "minLength": 1,
260
+ "maxLength": 1000
261
+ },
262
+ "y": {
263
+ "type": "string",
264
+ "minLength": 1,
265
+ "maxLength": 1000
266
+ },
267
+ "title": {
268
+ "type": "string",
269
+ "minLength": 1,
270
+ "maxLength": 1000
271
+ },
272
+ "format": {
273
+ "default": "number",
274
+ "type": "string",
275
+ "pattern": "^(number|percent|percentage-points|currency:[A-Z]{3})$"
276
+ }
277
+ },
278
+ "required": [
279
+ "data",
280
+ "x",
281
+ "y",
282
+ "title",
283
+ "format"
284
+ ],
285
+ "additionalProperties": false
286
+ },
287
+ "example": "::bar{data=\"./errors.json\" x=\"category\" y=\"count\" title=\"Errors by category\"}"
288
+ },
289
+ "table": {
290
+ "description": "A semantic sortable table, with inferred or explicitly formatted columns.",
291
+ "forms": [
292
+ "leaf",
293
+ "container"
294
+ ],
295
+ "children": [
296
+ "column"
297
+ ],
298
+ "props": {
299
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
300
+ "type": "object",
301
+ "properties": {
302
+ "data": {
303
+ "type": "string",
304
+ "minLength": 1,
305
+ "maxLength": 1000
306
+ },
307
+ "title": {
308
+ "type": "string",
309
+ "minLength": 1,
310
+ "maxLength": 1000
311
+ }
312
+ },
313
+ "required": [
314
+ "data"
315
+ ],
316
+ "additionalProperties": false
317
+ },
318
+ "example": ":::table{data=\"./plans.json\" title=\"By plan\"}\n::column{field=\"plan\" label=\"Plan\"}\n::column{field=\"change\" label=\"Δ QoQ\" format=\"percent\" sign=\"always\"}\n:::"
319
+ },
320
+ "column": {
321
+ "description": "An explicit field, label and format inside a table.",
322
+ "forms": [
323
+ "leaf"
324
+ ],
325
+ "children": [],
326
+ "props": {
327
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
328
+ "type": "object",
329
+ "properties": {
330
+ "field": {
331
+ "type": "string",
332
+ "minLength": 1,
333
+ "maxLength": 1000
334
+ },
335
+ "label": {
336
+ "type": "string",
337
+ "minLength": 1,
338
+ "maxLength": 1000
339
+ },
340
+ "format": {
341
+ "type": "string",
342
+ "pattern": "^(number|percent|percentage-points|currency:[A-Z]{3})$"
343
+ },
344
+ "sign": {
345
+ "default": "auto",
346
+ "type": "string",
347
+ "enum": [
348
+ "auto",
349
+ "always"
350
+ ]
351
+ },
352
+ "semantics": {
353
+ "default": "neutral",
354
+ "type": "string",
355
+ "enum": [
356
+ "neutral",
357
+ "higher-is-better",
358
+ "lower-is-better"
359
+ ]
360
+ }
361
+ },
362
+ "required": [
363
+ "field",
364
+ "sign",
365
+ "semantics"
366
+ ],
367
+ "additionalProperties": false
368
+ },
369
+ "example": "::column{field=\"change\" label=\"Δ QoQ\" format=\"percent\" sign=\"always\" semantics=\"higher-is-better\"}"
370
+ },
371
+ "callout": {
372
+ "description": "A contextual note or warning containing ordinary Markdown.",
373
+ "forms": [
374
+ "container"
375
+ ],
376
+ "children": [
377
+ "markdown"
378
+ ],
379
+ "props": {
380
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
381
+ "type": "object",
382
+ "properties": {
383
+ "tone": {
384
+ "default": "note",
385
+ "type": "string",
386
+ "enum": [
387
+ "note",
388
+ "warning"
389
+ ]
390
+ },
391
+ "title": {
392
+ "type": "string",
393
+ "minLength": 1,
394
+ "maxLength": 1000
395
+ }
396
+ },
397
+ "required": [
398
+ "tone"
399
+ ],
400
+ "additionalProperties": false
401
+ },
402
+ "example": ":::callout{title=\"Coverage\"}\nSeven complete days of prepared data.\n:::"
403
+ }
404
+ },
405
+ "rules": [
406
+ "Use explicit closing fences for containers.",
407
+ "Components are standalone blocks, not inline/list/blockquote content.",
408
+ "column is only allowed inside table; grids cannot nest.",
409
+ "direction and explicitly supplied sentiment require metric comparison text.",
410
+ "targetLabel requires target.",
411
+ "Dataset fields are literal own-property keys, not expressions.",
412
+ "Charts preserve source order; date/number x values must increase.",
413
+ "Line/area null y values create gaps; bar null y values are invalid.",
414
+ "Filters, maps, images, nested data and executable components are not supported in this release."
415
+ ]
416
+ }