tsquare 0.1.0 → 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/README.md +15 -3
- package/assets/mark.png +0 -0
- package/bin/tsquare.js +2 -6
- package/dist/catalog.d.ts +1021 -0
- package/dist/chunks/chunk-L6GTIZQH.js +2029 -0
- package/{src/cli.ts → dist/cli.js} +28 -24
- package/dist/colors.d.ts +29 -0
- package/dist/compile.d.ts +24 -0
- package/dist/components.d.ts +83 -0
- package/dist/icons.d.ts +12 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +38 -0
- package/dist/layout.d.ts +66 -0
- package/dist/playground/index.d.ts +27 -0
- package/dist/playground/index.html +501 -0
- package/dist/playground/index.js +376 -0
- package/dist/playground/reference.d.ts +11 -0
- package/dist/print.d.ts +2 -0
- package/dist/prompt-example.d.ts +3 -0
- package/dist/prompt.d.ts +22 -0
- package/dist/render.d.ts +20 -0
- package/dist/schema.d.ts +33 -0
- package/dist/suggest.d.ts +5 -0
- package/dist/text.d.ts +45 -0
- package/examples/notes-mobile.tsq +84 -0
- package/examples/notes-web.tsq +54 -0
- package/package.json +23 -7
- package/skill/tsquare/SKILL.md +3 -3
- package/skill/tsquare/reference.md +2 -2
- package/src/catalog.ts +0 -329
- package/src/colors.ts +0 -82
- package/src/compile.ts +0 -64
- package/src/components.tsx +0 -1057
- package/src/icons.ts +0 -54
- package/src/index.ts +0 -11
- package/src/layout.ts +0 -63
- package/src/print.ts +0 -73
- package/src/prompt-example.ts +0 -43
- package/src/prompt.ts +0 -121
- package/src/render.ts +0 -178
- package/src/schema.ts +0 -43
- package/src/suggest.ts +0 -34
- package/src/text.ts +0 -333
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
import {
|
|
2
|
+
compileWireframe,
|
|
3
|
+
componentDocs,
|
|
4
|
+
formatIssues,
|
|
5
|
+
renderWireframePng,
|
|
6
|
+
renderWireframeSvg,
|
|
7
|
+
wireframePrompt
|
|
8
|
+
} from "../chunks/chunk-L6GTIZQH.js";
|
|
9
|
+
|
|
10
|
+
// src/playground/index.ts
|
|
11
|
+
import { createServer } from "node:http";
|
|
12
|
+
import { readFile, readdir } from "node:fs/promises";
|
|
13
|
+
import path from "node:path";
|
|
14
|
+
import { fileURLToPath } from "node:url";
|
|
15
|
+
|
|
16
|
+
// src/playground/reference.ts
|
|
17
|
+
var GROUPS = [
|
|
18
|
+
{ name: "Canvas", components: ["Board", "Screen", "Note"] },
|
|
19
|
+
{ name: "Layout", components: ["Stack", "Grid", "Card", "Divider", "Spacer"] },
|
|
20
|
+
{ name: "Content", components: ["Heading", "Text", "Image", "Icon", "Avatar", "Badge"] },
|
|
21
|
+
{ name: "Controls", components: ["Button", "Input", "Checkbox", "Radio", "Toggle", "Select"] },
|
|
22
|
+
{ name: "Navigation & data", components: ["NavBar", "TabBar", "Tabs", "List", "ListItem", "Table"] },
|
|
23
|
+
{ name: "Overlays", components: ["Modal", "Drawer"] }
|
|
24
|
+
];
|
|
25
|
+
var EXAMPLES = {
|
|
26
|
+
Board: `board row "Checkout flow" gap=48 accent=blue
|
|
27
|
+
screen custom "Cart" width=240 height=200
|
|
28
|
+
heading "Cart" level=3
|
|
29
|
+
text lines=3
|
|
30
|
+
spacer
|
|
31
|
+
button primary "Checkout" fullWidth
|
|
32
|
+
screen custom "Payment" width=240 height=200
|
|
33
|
+
heading "Payment" level=3
|
|
34
|
+
toggle "Save card" on
|
|
35
|
+
spacer
|
|
36
|
+
button primary "Pay" fullWidth
|
|
37
|
+
note "accent colors primary buttons, toggles, checked controls and active tabs. Omit it for grayscale." width=180`,
|
|
38
|
+
Screen: `board
|
|
39
|
+
screen phone "phone"
|
|
40
|
+
heading "390 \xD7 844"
|
|
41
|
+
text "Phone and desktop screens show a status bar or browser bar (chrome). Turn it off with chrome=false." muted
|
|
42
|
+
screen custom "custom" width=260 height=200
|
|
43
|
+
heading "Any size" level=2
|
|
44
|
+
text "device=custom with width and height" muted`,
|
|
45
|
+
Note: `board gap=32
|
|
46
|
+
screen custom width=320 height=220
|
|
47
|
+
text lines=4
|
|
48
|
+
note "Notes can also sit inside a screen" color=blue
|
|
49
|
+
note "yellow (default)" width=130
|
|
50
|
+
note "blue" color=blue width=130
|
|
51
|
+
note "pink" color=pink width=130
|
|
52
|
+
note "green" color=green width=130`,
|
|
53
|
+
Stack: `board
|
|
54
|
+
screen custom width=560 height=300 padding=0
|
|
55
|
+
stack row grow
|
|
56
|
+
stack width=160 border=right fill padding=16 gap=12
|
|
57
|
+
text "Sidebar" bold
|
|
58
|
+
text "width, border, fill" sm muted
|
|
59
|
+
stack grow padding=16 gap=16
|
|
60
|
+
stack row justify=between align=center
|
|
61
|
+
heading "Toolbar" level=3
|
|
62
|
+
stack row gap=8
|
|
63
|
+
button secondary sm "Filter"
|
|
64
|
+
button primary sm "New"
|
|
65
|
+
stack row wrap gap=8
|
|
66
|
+
badge outline "row"
|
|
67
|
+
badge outline "wrap"
|
|
68
|
+
badge outline "gap"
|
|
69
|
+
text lines=3`,
|
|
70
|
+
Grid: `board
|
|
71
|
+
screen custom width=520 height=300
|
|
72
|
+
grid columns=3 gap=12
|
|
73
|
+
card
|
|
74
|
+
image height=60
|
|
75
|
+
text "Item" bold
|
|
76
|
+
card
|
|
77
|
+
image height=60
|
|
78
|
+
text "Item" bold
|
|
79
|
+
card
|
|
80
|
+
image height=60
|
|
81
|
+
text "Item" bold
|
|
82
|
+
card
|
|
83
|
+
image height=60
|
|
84
|
+
text "Item" bold
|
|
85
|
+
card
|
|
86
|
+
image height=60
|
|
87
|
+
text "Item" bold`,
|
|
88
|
+
Card: `board
|
|
89
|
+
screen custom width=520 height=170
|
|
90
|
+
stack row gap=16
|
|
91
|
+
card "outline (default)" grow
|
|
92
|
+
text lines=3
|
|
93
|
+
card "filled" variant=filled grow
|
|
94
|
+
text lines=3`,
|
|
95
|
+
Divider: `board
|
|
96
|
+
screen custom width=360 height=150
|
|
97
|
+
text "Above"
|
|
98
|
+
divider
|
|
99
|
+
text "Below"
|
|
100
|
+
stack row gap=12 align=center
|
|
101
|
+
text "Left"
|
|
102
|
+
divider vertical
|
|
103
|
+
text "Right"`,
|
|
104
|
+
Spacer: `board
|
|
105
|
+
screen custom width=360 height=260
|
|
106
|
+
heading "Fixed spacer" level=3
|
|
107
|
+
spacer size=24
|
|
108
|
+
text "24px gap above"
|
|
109
|
+
spacer
|
|
110
|
+
button primary "Pushed to the bottom" fullWidth`,
|
|
111
|
+
Heading: `board
|
|
112
|
+
screen custom width=360 height=170
|
|
113
|
+
heading "Level 1" level=1
|
|
114
|
+
heading "Level 2" level=2
|
|
115
|
+
heading "Level 3" level=3
|
|
116
|
+
heading "Centered" level=3 align=center`,
|
|
117
|
+
Text: `board
|
|
118
|
+
screen custom width=360 height=300
|
|
119
|
+
text "Body text, default size"
|
|
120
|
+
text lg "Large"
|
|
121
|
+
text sm "Small and muted" muted
|
|
122
|
+
text "Bold" bold
|
|
123
|
+
text "Placeholder lines when lines is set and there's no text:" sm muted
|
|
124
|
+
text lines=3`,
|
|
125
|
+
Image: `board
|
|
126
|
+
screen custom width=480 height=210
|
|
127
|
+
stack row gap=12
|
|
128
|
+
image "label" height=120 width=160
|
|
129
|
+
image height=120 width=120 rounded
|
|
130
|
+
image height=120
|
|
131
|
+
text "In a row, an image without a width is square." sm muted`,
|
|
132
|
+
Icon: `board
|
|
133
|
+
screen custom width=360 height=120
|
|
134
|
+
stack row gap=16 align=center
|
|
135
|
+
icon home
|
|
136
|
+
icon search
|
|
137
|
+
icon bell
|
|
138
|
+
icon settings
|
|
139
|
+
icon heart size=32
|
|
140
|
+
text "Any Lucide name in kebab-case (lucide.dev/icons)" sm muted`,
|
|
141
|
+
Avatar: `board
|
|
142
|
+
screen custom width=300 height=100
|
|
143
|
+
stack row gap=12 align=center
|
|
144
|
+
avatar "JD"
|
|
145
|
+
avatar "AB" size=48
|
|
146
|
+
avatar
|
|
147
|
+
avatar size=24`,
|
|
148
|
+
Badge: `board
|
|
149
|
+
screen custom width=340 height=110
|
|
150
|
+
stack row gap=8
|
|
151
|
+
badge "solid" variant=solid
|
|
152
|
+
badge outline "outline"
|
|
153
|
+
badge "3"
|
|
154
|
+
stack row gap=8
|
|
155
|
+
badge "Active" tone=success
|
|
156
|
+
badge "Pending" tone=warning
|
|
157
|
+
badge "Failed" tone=danger
|
|
158
|
+
badge outline "Failed" tone=danger`,
|
|
159
|
+
Button: `board
|
|
160
|
+
screen custom width=420 height=310
|
|
161
|
+
stack row gap=8 align=center
|
|
162
|
+
button primary "Primary"
|
|
163
|
+
button secondary "Secondary"
|
|
164
|
+
button ghost "Ghost"
|
|
165
|
+
stack row gap=8 align=center
|
|
166
|
+
button primary sm "Small"
|
|
167
|
+
button primary "Medium"
|
|
168
|
+
button primary lg "Large"
|
|
169
|
+
button secondary "With icon" icon=download
|
|
170
|
+
button primary "Full width" fullWidth
|
|
171
|
+
text "With a board accent, primary buttons fill with it and ghost buttons use it for text." sm muted`,
|
|
172
|
+
Input: `board
|
|
173
|
+
screen custom width=420 height=540
|
|
174
|
+
input "Email" placeholder="you@example.com" helper="We never share it"
|
|
175
|
+
input "Username" value="dana moore" error helper="No spaces allowed"
|
|
176
|
+
input password "Password" value=secret
|
|
177
|
+
input search placeholder="Search"
|
|
178
|
+
input "Message" multiline=3
|
|
179
|
+
stack row gap=8
|
|
180
|
+
input placeholder="grow fills the row" grow
|
|
181
|
+
button primary "Send"`,
|
|
182
|
+
Checkbox: `board
|
|
183
|
+
screen custom width=300 height=100
|
|
184
|
+
checkbox "Checked" checked
|
|
185
|
+
checkbox "Unchecked"`,
|
|
186
|
+
Radio: `board
|
|
187
|
+
screen custom width=300 height=130
|
|
188
|
+
radio "Selected" checked
|
|
189
|
+
radio "Not selected"
|
|
190
|
+
radio "Not selected"`,
|
|
191
|
+
Toggle: `board
|
|
192
|
+
screen custom width=300 height=100
|
|
193
|
+
toggle "On" on
|
|
194
|
+
toggle "Off" off`,
|
|
195
|
+
Select: `board
|
|
196
|
+
screen custom width=320 height=170
|
|
197
|
+
select "Country" value="Mexico"
|
|
198
|
+
select "Size" placeholder="Choose one" width=160`,
|
|
199
|
+
NavBar: `board
|
|
200
|
+
screen custom width=380 height=240 padding=0 gap=0
|
|
201
|
+
navbar "leading=menu" leading=menu actions=[search, bell]
|
|
202
|
+
navbar "leading=back, centered" leading=back align=center actions=[share]
|
|
203
|
+
navbar "leading=close" leading=close
|
|
204
|
+
navbar "leading=logo" leading=logo actions=[user]`,
|
|
205
|
+
TabBar: `board
|
|
206
|
+
screen phone "Tab bar sits at the bottom"
|
|
207
|
+
heading "Home"
|
|
208
|
+
text lines=4
|
|
209
|
+
tabbar items=[{label=Home icon=house}, {label=Search icon=search}, {label=Inbox icon=inbox}, {label=Profile icon=user}] active=0`,
|
|
210
|
+
Tabs: `board
|
|
211
|
+
screen custom width=400 height=150
|
|
212
|
+
tabs items=[Overview, Activity, Settings] active=0
|
|
213
|
+
text lines=2`,
|
|
214
|
+
List: `board
|
|
215
|
+
screen custom width=380 height=420
|
|
216
|
+
list
|
|
217
|
+
listitem "Dividers on (default)"
|
|
218
|
+
listitem "Second row"
|
|
219
|
+
list dividers=false
|
|
220
|
+
listitem "dividers=false"
|
|
221
|
+
listitem "Second row"
|
|
222
|
+
list
|
|
223
|
+
listitem
|
|
224
|
+
listitem`,
|
|
225
|
+
ListItem: `board
|
|
226
|
+
screen custom width=400 height=440
|
|
227
|
+
list
|
|
228
|
+
listitem "leading=icon" subtitle="trailing=chevron" leading=icon icon=settings trailing=chevron
|
|
229
|
+
listitem "leading=avatar" subtitle="trailing=text" leading=avatar trailing=text trailingText="9:41"
|
|
230
|
+
listitem "leading=image" subtitle="trailing=badge" leading=image trailing=badge trailingText="3"
|
|
231
|
+
listitem "leading=checkbox" leading=checkbox trailing=toggle
|
|
232
|
+
listitem "trailing=icon" trailing=icon trailingIcon=trash-2
|
|
233
|
+
listitem leading=image trailing=chevron`,
|
|
234
|
+
Table: `board
|
|
235
|
+
screen custom width=520 height=400
|
|
236
|
+
table columns=[Name, Role, Status] data=[["Ana Ruiz", Admin, Active], ["Ben Cho", Editor, Invited]]
|
|
237
|
+
text "Without data, rows are placeholder bars:" sm muted
|
|
238
|
+
table columns=[Name, Role, Status] rows=3`,
|
|
239
|
+
Modal: `board
|
|
240
|
+
screen phone "Modal"
|
|
241
|
+
heading "Settings"
|
|
242
|
+
text lines=6
|
|
243
|
+
modal "Delete account?"
|
|
244
|
+
text "This can't be undone." muted
|
|
245
|
+
stack row justify=end gap=8
|
|
246
|
+
button ghost "Cancel"
|
|
247
|
+
button primary "Delete"`,
|
|
248
|
+
Drawer: `board
|
|
249
|
+
screen phone "side=left"
|
|
250
|
+
navbar "Inbox" leading=menu
|
|
251
|
+
text lines=6
|
|
252
|
+
drawer left size=260
|
|
253
|
+
heading "Menu" level=3
|
|
254
|
+
list dividers=false
|
|
255
|
+
listitem "Inbox" leading=icon icon=inbox
|
|
256
|
+
listitem "Sent" leading=icon icon=send
|
|
257
|
+
screen phone "side=bottom"
|
|
258
|
+
navbar "Photo" leading=back
|
|
259
|
+
image height=300
|
|
260
|
+
drawer bottom "Share" size=280
|
|
261
|
+
list dividers=false
|
|
262
|
+
listitem "Copy link" leading=icon icon=link
|
|
263
|
+
listitem "Message" leading=icon icon=message-circle`
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
// src/playground/index.ts
|
|
267
|
+
var here = path.dirname(fileURLToPath(import.meta.url));
|
|
268
|
+
var packageRoot = path.resolve(here, "..", "..");
|
|
269
|
+
var MAX_BODY = 256 * 1024;
|
|
270
|
+
function checkReferenceExamples() {
|
|
271
|
+
const problems = [];
|
|
272
|
+
for (const c of componentDocs()) {
|
|
273
|
+
if (!EXAMPLES[c.name]) problems.push(`${c.name}: no example in src/playground/reference.ts`);
|
|
274
|
+
if (!GROUPS.some((g) => g.components.includes(c.name))) problems.push(`${c.name}: not in any group in src/playground/reference.ts`);
|
|
275
|
+
}
|
|
276
|
+
for (const [name, src] of Object.entries(EXAMPLES)) {
|
|
277
|
+
const { issues } = compileWireframe(src);
|
|
278
|
+
if (issues.length) problems.push(`${name} example:
|
|
279
|
+
${formatIssues(issues)}`);
|
|
280
|
+
}
|
|
281
|
+
return problems;
|
|
282
|
+
}
|
|
283
|
+
var referenceCache = null;
|
|
284
|
+
function reference() {
|
|
285
|
+
referenceCache ??= Promise.all(
|
|
286
|
+
componentDocs().map(async (c) => {
|
|
287
|
+
const { spec } = compileWireframe(EXAMPLES[c.name]);
|
|
288
|
+
return { ...c, example: EXAMPLES[c.name], svg: await renderWireframeSvg(spec, { skipValidation: true }) };
|
|
289
|
+
})
|
|
290
|
+
).then((components) => ({ groups: GROUPS, components }));
|
|
291
|
+
return referenceCache;
|
|
292
|
+
}
|
|
293
|
+
async function examples() {
|
|
294
|
+
const dir = path.join(packageRoot, "examples");
|
|
295
|
+
const files = (await readdir(dir)).filter((f) => f.endsWith(".tsq")).sort();
|
|
296
|
+
return Promise.all(files.map(async (f) => ({ name: f.replace(/\.tsq$/, ""), source: await readFile(path.join(dir, f), "utf8") })));
|
|
297
|
+
}
|
|
298
|
+
function readBody(req) {
|
|
299
|
+
return new Promise((resolve, reject) => {
|
|
300
|
+
let size = 0;
|
|
301
|
+
const chunks = [];
|
|
302
|
+
req.on("data", (c) => {
|
|
303
|
+
size += c.length;
|
|
304
|
+
if (size > MAX_BODY) reject(new Error("body too large"));
|
|
305
|
+
else chunks.push(c);
|
|
306
|
+
});
|
|
307
|
+
req.on("end", () => resolve(Buffer.concat(chunks).toString("utf8")));
|
|
308
|
+
req.on("error", reject);
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
var json = (res, status, body) => {
|
|
312
|
+
res.writeHead(status, { "content-type": "application/json" });
|
|
313
|
+
res.end(JSON.stringify(body));
|
|
314
|
+
return true;
|
|
315
|
+
};
|
|
316
|
+
function createPlaygroundHandler() {
|
|
317
|
+
return async function handle(req, res) {
|
|
318
|
+
const url = new URL(req.url ?? "/", "http://localhost");
|
|
319
|
+
if (req.method === "GET" && url.pathname === "/") {
|
|
320
|
+
res.writeHead(200, { "content-type": "text/html; charset=utf-8" });
|
|
321
|
+
res.end(await readFile(path.join(here, "index.html")));
|
|
322
|
+
return true;
|
|
323
|
+
}
|
|
324
|
+
if (req.method === "GET" && url.pathname === "/mark.png") {
|
|
325
|
+
res.writeHead(200, { "content-type": "image/png", "cache-control": "max-age=3600" });
|
|
326
|
+
res.end(await readFile(path.join(packageRoot, "assets", "mark.png")));
|
|
327
|
+
return true;
|
|
328
|
+
}
|
|
329
|
+
if (req.method === "GET" && url.pathname === "/api/reference") return json(res, 200, await reference());
|
|
330
|
+
if (req.method === "GET" && url.pathname === "/api/examples") return json(res, 200, await examples());
|
|
331
|
+
if (req.method === "GET" && url.pathname === "/api/prompt") {
|
|
332
|
+
res.writeHead(200, { "content-type": "text/plain; charset=utf-8" });
|
|
333
|
+
res.end(wireframePrompt());
|
|
334
|
+
return true;
|
|
335
|
+
}
|
|
336
|
+
if (req.method === "POST" && (url.pathname === "/api/render" || url.pathname === "/api/png")) {
|
|
337
|
+
const { spec, issues } = compileWireframe(await readBody(req));
|
|
338
|
+
if (url.pathname === "/api/render") {
|
|
339
|
+
if (!spec || issues.length) return json(res, 200, { issues });
|
|
340
|
+
return json(res, 200, { issues, svg: await renderWireframeSvg(spec, { skipValidation: true }) });
|
|
341
|
+
}
|
|
342
|
+
if (!spec || issues.length) return json(res, 400, { issues });
|
|
343
|
+
const scale = Math.min(4, Math.max(1, Number(url.searchParams.get("scale") ?? 2)));
|
|
344
|
+
res.writeHead(200, { "content-type": "image/png" });
|
|
345
|
+
res.end(await renderWireframePng(spec, { skipValidation: true, scale }));
|
|
346
|
+
return true;
|
|
347
|
+
}
|
|
348
|
+
return false;
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
function startPlayground(port = 4321) {
|
|
352
|
+
const problems = checkReferenceExamples();
|
|
353
|
+
if (problems.length) {
|
|
354
|
+
console.error(`Reference examples need fixing:
|
|
355
|
+
|
|
356
|
+
${problems.join("\n")}`);
|
|
357
|
+
process.exit(1);
|
|
358
|
+
}
|
|
359
|
+
const handle = createPlaygroundHandler();
|
|
360
|
+
const server = createServer((req, res) => {
|
|
361
|
+
handle(req, res).then((handled) => {
|
|
362
|
+
if (!handled) json(res, 404, { error: "not found" });
|
|
363
|
+
}).catch((err) => {
|
|
364
|
+
console.error(err);
|
|
365
|
+
if (!res.headersSent) json(res, 500, { error: String(err?.message ?? err) });
|
|
366
|
+
else res.end();
|
|
367
|
+
});
|
|
368
|
+
});
|
|
369
|
+
server.listen(port, "127.0.0.1", () => console.log(`tsquare playground \u2192 http://localhost:${port}`));
|
|
370
|
+
return server;
|
|
371
|
+
}
|
|
372
|
+
export {
|
|
373
|
+
checkReferenceExamples,
|
|
374
|
+
createPlaygroundHandler,
|
|
375
|
+
startPlayground
|
|
376
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Examples for the playground's Components tab and the generated component
|
|
3
|
+
* docs. Each one is a complete wireframe so it can be opened in the editor
|
|
4
|
+
* as-is. Every catalog component needs one, in a group, and each must compile:
|
|
5
|
+
* `npm run build` fails otherwise, and the playground refuses to start.
|
|
6
|
+
*/
|
|
7
|
+
export declare const GROUPS: {
|
|
8
|
+
name: string;
|
|
9
|
+
components: string[];
|
|
10
|
+
}[];
|
|
11
|
+
export declare const EXAMPLES: Record<string, string>;
|
package/dist/print.d.ts
ADDED
package/dist/prompt.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export interface PropDoc {
|
|
2
|
+
name: string;
|
|
3
|
+
type: string;
|
|
4
|
+
required: boolean;
|
|
5
|
+
/** This prop is filled by a quoted string, e.g. the label of `button "Save"`. */
|
|
6
|
+
main: boolean;
|
|
7
|
+
description?: string;
|
|
8
|
+
}
|
|
9
|
+
/** Every component with its description and props, straight from the catalog. */
|
|
10
|
+
export declare function componentDocs(): {
|
|
11
|
+
name: string;
|
|
12
|
+
description: string;
|
|
13
|
+
props: PropDoc[];
|
|
14
|
+
}[];
|
|
15
|
+
export declare function componentReference(): string;
|
|
16
|
+
export declare const RULES = "## Rules\n1. The top element is a Board. The Board's children are Screens, plus optional Notes beside them.\n2. Each Screen is one view of the product. Use several Screens to show several views or states.\n3. A Screen lays out its children top to bottom. Use Stack (direction row or column) and Grid to arrange content.\n4. NavBar is pinned to the top of its Screen and TabBar to the bottom.\n5. Modal and Drawer are overlays. They must be direct children of a Screen.\n6. This is a low-fidelity wireframe. Prefer placeholders (Image boxes, Text with lines) over invented copy unless the copy matters.\n7. Only use the components and props listed below. All props are optional unless marked required.\n8. Wireframes are grayscale. The only UI colors: Board accent (one color for primary buttons, checked controls, toggles, active tabs and ghost buttons), Badge tone (success, warning, danger) and Input error. Only add an accent if the request asks for color or a brand.";
|
|
17
|
+
export declare const TEXT_FORMAT = "## Output format: wireframe text\nOne element per line. Indent children two spaces under their parent. The first line is the board.\n\nA line is the component name in lowercase, followed by arguments separated by spaces:\n- \"a quoted string\" sets the component's main text prop (marked \"main text\" below)\n- a bare word that is one of the component's option values sets that option: phone, desktop, primary, ghost, row, sm, left, bottom, password, \u2026\n- a bare prop name sets a boolean prop to true: checked, fullWidth, grow, muted. `off` and `unchecked` set on/checked to false\n- key=value sets any prop. Values: \"string\", number, true/false, bare word, [list, of, values], {key=value key=value}\n- `#` after a space starts a comment that runs to the end of the line\n\nLists: items are separated by commas, and an item can contain spaces without quotes: [All notes, Pinned, Shared]. To put a comma inside an item, quote the item: [\"$1,200\", \"Smith, J\"]. Inside quotes, write \\\" for a quote character. Numbers in a list of text are fine: [2023, 2024].\n\nIf a bare word could mean more than one prop, write it as key=value.";
|
|
18
|
+
export declare const EXAMPLE_REQUEST = "Two phone screens for a recipe app: a browse screen with search, category tabs, a grid of recipe cards and a tab bar; and a recipe detail screen with its options sheet open.";
|
|
19
|
+
/** The system prompt for writing wireframe text. */
|
|
20
|
+
export declare function wireframePrompt(): string;
|
|
21
|
+
/** Follow-up message asking a model to fix the problems in its last reply. */
|
|
22
|
+
export declare function repairPrompt(issues: string): string;
|
package/dist/render.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Spec } from "@json-render/core";
|
|
2
|
+
export declare class SpecError extends Error {
|
|
3
|
+
issues: string[];
|
|
4
|
+
constructor(issues: string[]);
|
|
5
|
+
}
|
|
6
|
+
/** Catalog validation plus the structural rules the renderer depends on. */
|
|
7
|
+
export declare function checkSpec(spec: Spec): string[];
|
|
8
|
+
/** Work out the canvas size from the Board's screens (Satori needs it up front). */
|
|
9
|
+
export declare function boardSize(spec: Spec): {
|
|
10
|
+
width: number;
|
|
11
|
+
height: number;
|
|
12
|
+
};
|
|
13
|
+
export interface RenderWireframeOptions {
|
|
14
|
+
/** Skip validation (e.g. while a spec is still streaming in). */
|
|
15
|
+
skipValidation?: boolean;
|
|
16
|
+
}
|
|
17
|
+
export declare function renderWireframeSvg(spec: Spec, opts?: RenderWireframeOptions): Promise<string>;
|
|
18
|
+
export declare function renderWireframePng(spec: Spec, opts?: RenderWireframeOptions & {
|
|
19
|
+
scale?: number;
|
|
20
|
+
}): Promise<Buffer<ArrayBufferLike>>;
|
package/dist/schema.d.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wireframe spec schema. Same flat element shape as the other json-render
|
|
3
|
+
* renderers (root + elements map), with wireframe-specific rules for the
|
|
4
|
+
* generated LLM prompt.
|
|
5
|
+
*/
|
|
6
|
+
export declare const wireframeSchema: import("@json-render/core").Schema<{
|
|
7
|
+
spec: import("@json-render/core").SchemaType<"object", {
|
|
8
|
+
root: import("@json-render/core").SchemaType<"string", unknown>;
|
|
9
|
+
elements: import("@json-render/core").SchemaType<"record", import("@json-render/core").SchemaType<"object", {
|
|
10
|
+
type: import("@json-render/core").SchemaType<"ref", string>;
|
|
11
|
+
props: import("@json-render/core").SchemaType<"propsOf", string>;
|
|
12
|
+
children: import("@json-render/core").SchemaType<"array", import("@json-render/core").SchemaType<"string", unknown>>;
|
|
13
|
+
visible: {
|
|
14
|
+
optional: true;
|
|
15
|
+
kind: "any";
|
|
16
|
+
inner?: unknown;
|
|
17
|
+
};
|
|
18
|
+
repeat: {
|
|
19
|
+
optional: true;
|
|
20
|
+
kind: "any";
|
|
21
|
+
inner?: unknown;
|
|
22
|
+
};
|
|
23
|
+
}>>;
|
|
24
|
+
}>;
|
|
25
|
+
catalog: import("@json-render/core").SchemaType<"object", {
|
|
26
|
+
components: import("@json-render/core").SchemaType<"map", {
|
|
27
|
+
props: import("@json-render/core").SchemaType<"zod", unknown>;
|
|
28
|
+
slots: import("@json-render/core").SchemaType<"array", import("@json-render/core").SchemaType<"string", unknown>>;
|
|
29
|
+
description: import("@json-render/core").SchemaType<"string", unknown>;
|
|
30
|
+
example: import("@json-render/core").SchemaType<"any", unknown>;
|
|
31
|
+
}>;
|
|
32
|
+
}>;
|
|
33
|
+
}>;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** Edit distance counting a swap of neighbouring letters as one edit ("serach" → search). */
|
|
2
|
+
export declare function editDistance(a: string, b: string): any;
|
|
3
|
+
/** Up to `limit` candidates within a small edit distance of `word`, closest first. */
|
|
4
|
+
export declare function closeMatches(word: string, candidates: string[], limit?: number): string[];
|
|
5
|
+
export declare function unknownComponentMessage(name: string, components: string[]): string;
|
package/dist/text.d.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Text syntax for wireframes. Compiles to the same flat json-render spec.
|
|
3
|
+
*
|
|
4
|
+
* board "Notes app"
|
|
5
|
+
* screen phone "Sign in" padding=28
|
|
6
|
+
* heading "Welcome back"
|
|
7
|
+
* input "Email" placeholder="you@example.com"
|
|
8
|
+
* stack row justify=between
|
|
9
|
+
* checkbox "Remember me" checked
|
|
10
|
+
* button "Forgot?" ghost sm
|
|
11
|
+
* button "Sign in" primary lg fullWidth
|
|
12
|
+
*
|
|
13
|
+
* Line = component name, then arguments, children indented below.
|
|
14
|
+
* Arguments:
|
|
15
|
+
* "quoted" → the component's main text prop (label, title, text…)
|
|
16
|
+
* bare word → an enum value (phone, primary, row, sm…) or a boolean prop set to true (checked)
|
|
17
|
+
* key=value → any prop; value is "string", number, true/false, word, [list] or {key=value …}
|
|
18
|
+
* list items are separated by commas only, so [Ana Torres, Admin] is two items;
|
|
19
|
+
* quote an item that contains a comma: ["$1,200", "Smith, J"]
|
|
20
|
+
* # comment
|
|
21
|
+
*/
|
|
22
|
+
import type { Spec } from "@json-render/core";
|
|
23
|
+
/** Which prop a quoted string fills, per component. */
|
|
24
|
+
export declare const PRIMARY_PROP: Record<string, string>;
|
|
25
|
+
/** What a component accepts as bare words, for docs: option values (with their prop), ambiguous values, and boolean props. */
|
|
26
|
+
export declare function bareWords(component: string): {
|
|
27
|
+
options: {
|
|
28
|
+
value: string;
|
|
29
|
+
prop: string;
|
|
30
|
+
}[];
|
|
31
|
+
ambiguous: string[];
|
|
32
|
+
booleans: string[];
|
|
33
|
+
wordPrimary: boolean;
|
|
34
|
+
};
|
|
35
|
+
export interface TextIssue {
|
|
36
|
+
line: number;
|
|
37
|
+
message: string;
|
|
38
|
+
}
|
|
39
|
+
export interface ParseResult {
|
|
40
|
+
spec: Spec | null;
|
|
41
|
+
issues: TextIssue[];
|
|
42
|
+
/** element id → source line, for mapping validation errors back */
|
|
43
|
+
lines: Record<string, number>;
|
|
44
|
+
}
|
|
45
|
+
export declare function parseWireframeText(source: string): ParseResult;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
board row "Notes app — mobile"
|
|
2
|
+
screen phone "1 · Sign in" padding=28 gap=18
|
|
3
|
+
spacer size=36
|
|
4
|
+
image width=64 height=64 rounded
|
|
5
|
+
heading "Welcome back" level=1
|
|
6
|
+
text "Sign in to sync your notes." muted
|
|
7
|
+
input email "Email" placeholder="you@example.com"
|
|
8
|
+
input password "Password" value=hunter22
|
|
9
|
+
stack row justify=between
|
|
10
|
+
checkbox "Remember me" checked
|
|
11
|
+
button ghost sm "Forgot?"
|
|
12
|
+
button primary lg "Sign in" fullWidth
|
|
13
|
+
stack row gap=12 align=center
|
|
14
|
+
stack grow
|
|
15
|
+
divider
|
|
16
|
+
text sm "or" muted
|
|
17
|
+
stack grow
|
|
18
|
+
divider
|
|
19
|
+
button secondary lg "Continue with Google" fullWidth icon=globe
|
|
20
|
+
spacer
|
|
21
|
+
text sm "New here? Create an account" align=center muted
|
|
22
|
+
screen phone "2 · Notes"
|
|
23
|
+
navbar "Notes" leading=menu actions=[bell, more-vertical]
|
|
24
|
+
input search placeholder="Search notes"
|
|
25
|
+
tabs items=[All, Pinned, Shared] active=0
|
|
26
|
+
list
|
|
27
|
+
listitem "Grocery list" subtitle="Eggs, oat milk, basil…" leading=icon icon=shopping-cart trailing=text trailingText="9:12"
|
|
28
|
+
listitem "Trip ideas" subtitle="Lisbon in October?" leading=icon icon=map trailing=badge trailingText="2"
|
|
29
|
+
listitem leading=image trailing=chevron
|
|
30
|
+
listitem leading=image trailing=chevron
|
|
31
|
+
listitem leading=image trailing=chevron
|
|
32
|
+
stack row justify=end grow align=end
|
|
33
|
+
button primary "New note" icon=plus
|
|
34
|
+
tabbar items=[{label=Notes icon=notebook-pen}, {label=Search icon=search}, {label=Shared icon=users}, {label=Profile icon=user}] active=0
|
|
35
|
+
screen phone "3 · Side menu (drawer)"
|
|
36
|
+
navbar "Notes" leading=menu actions=[bell, more-vertical]
|
|
37
|
+
list
|
|
38
|
+
listitem leading=image
|
|
39
|
+
listitem leading=image
|
|
40
|
+
listitem leading=image
|
|
41
|
+
listitem leading=image
|
|
42
|
+
listitem leading=image
|
|
43
|
+
listitem leading=image
|
|
44
|
+
drawer left size=290
|
|
45
|
+
stack row gap=12
|
|
46
|
+
avatar "DM" size=44
|
|
47
|
+
stack gap=2
|
|
48
|
+
text "Dana Moore" bold
|
|
49
|
+
text sm "dana@example.com" muted
|
|
50
|
+
divider
|
|
51
|
+
list dividers=false
|
|
52
|
+
listitem "All notes" leading=icon icon=notebook-pen trailing=text trailingText="24"
|
|
53
|
+
listitem "Folders" leading=icon icon=folder trailing=chevron
|
|
54
|
+
listitem "Archive" leading=icon icon=archive
|
|
55
|
+
listitem "Settings" leading=icon icon=settings
|
|
56
|
+
spacer
|
|
57
|
+
button secondary "Sign out" icon=log-out fullWidth
|
|
58
|
+
screen phone "4 · Discard draft (modal)"
|
|
59
|
+
navbar "New note" leading=close actions=[image, check] align=center
|
|
60
|
+
heading "Untitled" level=2
|
|
61
|
+
text lines=7
|
|
62
|
+
stack row gap=8
|
|
63
|
+
badge outline "work"
|
|
64
|
+
badge outline "ideas"
|
|
65
|
+
modal "Discard this draft?"
|
|
66
|
+
text "You'll lose what you've written so far." muted
|
|
67
|
+
stack row justify=end gap=8
|
|
68
|
+
button ghost "Keep editing"
|
|
69
|
+
button primary "Discard"
|
|
70
|
+
screen phone "5 · Note options (bottom sheet)"
|
|
71
|
+
navbar leading=back actions=[share, more-horizontal]
|
|
72
|
+
image "Cover image" height=180
|
|
73
|
+
heading "Trip ideas" level=1
|
|
74
|
+
text lines=5
|
|
75
|
+
drawer bottom "Note options" size=400
|
|
76
|
+
toggle "Pin to top" on
|
|
77
|
+
toggle "Share with link" on=false
|
|
78
|
+
select "Folder" value=Travel
|
|
79
|
+
stack gap=10
|
|
80
|
+
text sm "Reminder" bold
|
|
81
|
+
stack row gap=20
|
|
82
|
+
radio "None" checked
|
|
83
|
+
radio "Tomorrow"
|
|
84
|
+
radio "Pick date"
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
board "Notes app — web"
|
|
2
|
+
screen desktop "Workspace overview" padding=0 gap=0
|
|
3
|
+
stack row grow gap=0 align=stretch
|
|
4
|
+
stack width=240 padding=20 gap=6 border=right fill
|
|
5
|
+
stack row gap=10
|
|
6
|
+
image width=32 height=32 rounded
|
|
7
|
+
text "Notebook" bold
|
|
8
|
+
spacer size=12
|
|
9
|
+
list dividers=false
|
|
10
|
+
listitem "Overview" leading=icon icon=layout-dashboard
|
|
11
|
+
listitem "All notes" leading=icon icon=notebook-pen trailing=text trailingText="128"
|
|
12
|
+
listitem "Shared" leading=icon icon=users
|
|
13
|
+
listitem "Settings" leading=icon icon=settings
|
|
14
|
+
spacer
|
|
15
|
+
stack row gap=10
|
|
16
|
+
avatar "DM" size=32
|
|
17
|
+
text sm "Dana Moore"
|
|
18
|
+
stack grow padding=32 gap=24
|
|
19
|
+
stack row gap=12
|
|
20
|
+
heading "Overview" level=1
|
|
21
|
+
spacer
|
|
22
|
+
stack width=260
|
|
23
|
+
input search placeholder=Search
|
|
24
|
+
button "New note" icon=plus
|
|
25
|
+
grid columns=3 gap=20
|
|
26
|
+
card "Notes this week"
|
|
27
|
+
heading "42" level=1
|
|
28
|
+
text lines=1
|
|
29
|
+
card "Shared with you"
|
|
30
|
+
heading "7" level=1
|
|
31
|
+
text lines=1
|
|
32
|
+
card filled "Storage"
|
|
33
|
+
heading "1.2 GB" level=1
|
|
34
|
+
text lines=1
|
|
35
|
+
tabs items=[Recent, Pinned, Archived] active=0
|
|
36
|
+
table columns=[Title, Folder, Owner, Updated] data=[["Grocery list", Personal, Dana, "9:12 AM"], ["Trip ideas", Travel, Dana, Yesterday], ["Q4 planning", Work, Sam, Mon], ["Reading list", Personal, Dana, "Sep 20"]]
|
|
37
|
+
screen desktop "Filters (right drawer)" padding=32 gap=24
|
|
38
|
+
stack row gap=12
|
|
39
|
+
heading "All notes" level=1
|
|
40
|
+
spacer
|
|
41
|
+
button secondary "Filters" icon=sliders-horizontal
|
|
42
|
+
table columns=[Title, Folder, Owner, Updated] rows=9
|
|
43
|
+
drawer right "Filters" size=340
|
|
44
|
+
select "Folder" placeholder="Any folder"
|
|
45
|
+
select "Owner" value=Me
|
|
46
|
+
divider
|
|
47
|
+
checkbox "Pinned only" checked
|
|
48
|
+
checkbox "Has attachments"
|
|
49
|
+
checkbox "Shared with me"
|
|
50
|
+
spacer
|
|
51
|
+
stack row justify=end gap=8
|
|
52
|
+
button ghost "Reset"
|
|
53
|
+
button "Apply filters"
|
|
54
|
+
note "Filters open in a right drawer so the table keeps its width. Open question: persist filters per workspace?" color=yellow
|