tsquare 0.1.0 → 0.3.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.
Files changed (50) hide show
  1. package/README.md +34 -8
  2. package/assets/mark.png +0 -0
  3. package/bin/tsquare.js +2 -6
  4. package/dist/catalog.d.ts +1055 -0
  5. package/dist/chunks/chunk-46MGJLGB.js +2126 -0
  6. package/{src/cli.ts → dist/cli.js} +28 -24
  7. package/dist/colors.d.ts +29 -0
  8. package/dist/compile.d.ts +24 -0
  9. package/dist/components.d.ts +83 -0
  10. package/dist/icons.d.ts +12 -0
  11. package/dist/index.d.ts +10 -0
  12. package/dist/index.js +54 -0
  13. package/dist/layout.d.ts +66 -0
  14. package/dist/playground/index.d.ts +38 -0
  15. package/dist/playground/index.html +325 -0
  16. package/dist/playground/index.js +466 -0
  17. package/dist/playground/language-data.d.ts +28 -0
  18. package/dist/playground/playground.js +47 -0
  19. package/dist/playground/reference.d.ts +11 -0
  20. package/dist/print.d.ts +2 -0
  21. package/dist/prompt-example.d.ts +3 -0
  22. package/dist/prompt.d.ts +22 -0
  23. package/dist/render.d.ts +40 -0
  24. package/dist/schema.d.ts +33 -0
  25. package/dist/share.d.ts +4 -0
  26. package/dist/suggest.d.ts +5 -0
  27. package/dist/text.d.ts +50 -0
  28. package/examples/checkout.tsq +15 -0
  29. package/examples/dashboard.tsq +27 -0
  30. package/examples/notes-mobile.tsq +84 -0
  31. package/examples/notes-web.tsq +54 -0
  32. package/examples/sign-in.tsq +14 -0
  33. package/examples/states.tsq +16 -0
  34. package/package.json +31 -7
  35. package/skill/tsquare/SKILL.md +5 -4
  36. package/skill/tsquare/reference.md +7 -6
  37. package/src/catalog.ts +0 -329
  38. package/src/colors.ts +0 -82
  39. package/src/compile.ts +0 -64
  40. package/src/components.tsx +0 -1057
  41. package/src/icons.ts +0 -54
  42. package/src/index.ts +0 -11
  43. package/src/layout.ts +0 -63
  44. package/src/print.ts +0 -73
  45. package/src/prompt-example.ts +0 -43
  46. package/src/prompt.ts +0 -121
  47. package/src/render.ts +0 -178
  48. package/src/schema.ts +0 -43
  49. package/src/suggest.ts +0 -34
  50. package/src/text.ts +0 -333
@@ -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>;
@@ -0,0 +1,2 @@
1
+ import type { Spec } from "@json-render/core";
2
+ export declare function printWireframeText(spec: Spec, indentUnit?: string): string;
@@ -0,0 +1,3 @@
1
+ import type { Spec } from "@json-render/core";
2
+ /** The worked example in the model prompt (also used by the format eval). */
3
+ export declare const exampleSpec: Spec;
@@ -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 that component's yes/no prop to true, only on components that have it (see each component's props below): checked (checkbox, radio), fullWidth (button), muted (text), grow (stack, card, input, list). `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\nComments: a line that starts with # is a comment. A comment must be on its own line, never after an element on the same line. Anywhere else, # is ordinary text: [#1001, #1002], \"Order #12345\", accent=#1a73e8.\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;
@@ -0,0 +1,40 @@
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
+ export interface BoardItem {
9
+ type: "Screen" | "Note";
10
+ /** Screen name or note text, for labels in tools like the playground. */
11
+ name: string;
12
+ /** Box on the board in px, including the screen's name label above its frame. */
13
+ x: number;
14
+ y: number;
15
+ width: number;
16
+ height: number;
17
+ }
18
+ /**
19
+ * Where everything sits on the board: the canvas size (Satori needs it up
20
+ * front) and each screen's and note's box, laid out in rows exactly as the
21
+ * Board component does.
22
+ */
23
+ export declare function boardLayout(spec: Spec): {
24
+ width: number;
25
+ height: number;
26
+ items: BoardItem[];
27
+ };
28
+ /** The canvas size for a board. */
29
+ export declare function boardSize(spec: Spec): {
30
+ width: number;
31
+ height: number;
32
+ };
33
+ export interface RenderWireframeOptions {
34
+ /** Skip validation (e.g. while a spec is still streaming in). */
35
+ skipValidation?: boolean;
36
+ }
37
+ export declare function renderWireframeSvg(spec: Spec, opts?: RenderWireframeOptions): Promise<string>;
38
+ export declare function renderWireframePng(spec: Spec, opts?: RenderWireframeOptions & {
39
+ scale?: number;
40
+ }): Promise<Buffer<ArrayBufferLike>>;
@@ -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,4 @@
1
+ /** Largest decoded text accepted, in bytes. Plenty for a board; stops a tiny URL from inflating into megabytes. */
2
+ export declare const MAX_SHARED_TEXT = 64000;
3
+ export declare function encodeWireframe(text: string): string;
4
+ export declare function decodeWireframe(data: string): string;
@@ -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,50 @@
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 → only on a line of its own; anywhere else # is text ([#1001, …], accent=#1a73e8)
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
+ /**
26
+ * For an error message: where a prop name or bare word that this component doesn't take
27
+ * belongs instead, e.g. fullWidth on an input → "fullWidth is a Button prop". Empty if nowhere.
28
+ */
29
+ export declare function belongsElsewhere(component: string, word: string, asProp: boolean): string;
30
+ /** What a component accepts as bare words, for docs: option values (with their prop), ambiguous values, and boolean props. */
31
+ export declare function bareWords(component: string): {
32
+ options: {
33
+ value: string;
34
+ prop: string;
35
+ }[];
36
+ ambiguous: string[];
37
+ booleans: string[];
38
+ wordPrimary: boolean;
39
+ };
40
+ export interface TextIssue {
41
+ line: number;
42
+ message: string;
43
+ }
44
+ export interface ParseResult {
45
+ spec: Spec | null;
46
+ issues: TextIssue[];
47
+ /** element id → source line, for mapping validation errors back */
48
+ lines: Record<string, number>;
49
+ }
50
+ export declare function parseWireframeText(source: string): ParseResult;
@@ -0,0 +1,15 @@
1
+ board row "Checkout" accent=green
2
+ screen phone "Cart"
3
+ navbar "Cart" leading=back
4
+ list
5
+ listitem "Oat latte" subtitle="Large, extra shot" leading=image trailing=text trailingText="$5.40"
6
+ listitem "Croissant" subtitle="Warm" leading=image trailing=text trailingText="$3.20"
7
+ spacer
8
+ button primary lg "Checkout · $8.60" fullWidth
9
+ screen phone "Pay"
10
+ navbar "Cart" leading=back
11
+ text lines=4
12
+ drawer bottom "Payment" size=300
13
+ select "Card" value="Visa ending 6411"
14
+ toggle "Save card" on
15
+ button primary lg "Pay $8.60" fullWidth
@@ -0,0 +1,27 @@
1
+ board "Analytics" accent=blue
2
+ screen desktop "Dashboard" padding=0 height=640
3
+ stack row grow
4
+ stack width=220 border=right fill padding=16 gap=6
5
+ text "Acme" bold
6
+ list dividers=false
7
+ listitem "Overview" leadingIcon=layout-grid
8
+ listitem "Customers" leadingIcon=users
9
+ listitem "Orders" leadingIcon=receipt
10
+ listitem "Settings" leadingIcon=settings
11
+ stack grow padding=24 gap=20
12
+ stack row justify=between align=center
13
+ heading "Overview" level=2
14
+ stack row gap=8
15
+ input search placeholder="Search" width=220
16
+ button primary "Export" leadingIcon=download
17
+ avatar "DM"
18
+ grid columns=4 gap=12
19
+ card "Revenue"
20
+ heading "$48.2k" level=2
21
+ card "Orders"
22
+ heading "1,284" level=2
23
+ card "Customers"
24
+ heading "932" level=2
25
+ card "Refunds"
26
+ heading "12" level=2
27
+ table columns=[Order, Customer, Status, Total] data=[["#1042", Ana Torres, Paid, "$120"], ["#1041", Ben Cho, Pending, "$80"], ["#1040", Priya Nair, Paid, "$45"]]
@@ -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 leadingIcon=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…" leadingIcon=shopping-cart trailing=text trailingText="9:12"
28
+ listitem "Trip ideas" subtitle="Lisbon in October?" leadingIcon=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" leadingIcon=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" leadingIcon=notebook-pen trailing=text trailingText="24"
53
+ listitem "Folders" leadingIcon=folder trailing=chevron
54
+ listitem "Archive" leadingIcon=archive
55
+ listitem "Settings" leadingIcon=settings
56
+ spacer
57
+ button secondary "Sign out" leadingIcon=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" leadingIcon=layout-dashboard
11
+ listitem "All notes" leadingIcon=notebook-pen trailing=text trailingText="128"
12
+ listitem "Shared" leadingIcon=users
13
+ listitem "Settings" leadingIcon=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" leadingIcon=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" leadingIcon=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
@@ -0,0 +1,14 @@
1
+ board "Sign in"
2
+ screen phone "Sign in" padding=28 gap=16
3
+ spacer size=24
4
+ heading "Welcome back" level=1
5
+ text "Sign in to sync your notes." muted
6
+ input email "Email" placeholder="you@example.com"
7
+ input password "Password"
8
+ stack row justify=between
9
+ checkbox "Remember me" checked
10
+ button ghost sm "Forgot?"
11
+ button primary lg "Sign in" fullWidth
12
+ spacer
13
+ text sm "New here? Create an account" align=center muted
14
+ note "Show the error state when the password is wrong" width=200
@@ -0,0 +1,16 @@
1
+ board row "Empty and error states"
2
+ screen phone "No notes yet"
3
+ navbar "Notes" actions=[plus]
4
+ stack grow justify=center align=center gap=12 padding=24
5
+ image "Illustration" height=140 width=200
6
+ heading "No notes yet" level=2
7
+ text "Your notes will show up here." muted align=center
8
+ button primary "New note" leadingIcon=plus
9
+ screen phone "Couldn't load"
10
+ navbar "Notes" actions=[plus]
11
+ stack grow justify=center align=center gap=12 padding=24
12
+ icon triangle-alert size=40
13
+ heading "Something went wrong" level=2
14
+ text "Check your connection and try again." muted align=center
15
+ button secondary "Try again" leadingIcon=refresh-cw
16
+ badge "Offline" tone=warning
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tsquare",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "Wireframes in plain text: easy for LLMs to write, fast to render as SVG or PNG.",
5
5
  "keywords": [
6
6
  "wireframe",
@@ -23,13 +23,26 @@
23
23
  "url": "https://github.com/tsquare-js/tsquare/issues"
24
24
  },
25
25
  "type": "module",
26
+ "exports": {
27
+ ".": {
28
+ "types": "./dist/index.d.ts",
29
+ "import": "./dist/index.js"
30
+ },
31
+ "./playground": {
32
+ "types": "./dist/playground/index.d.ts",
33
+ "import": "./dist/playground/index.js"
34
+ },
35
+ "./package.json": "./package.json"
36
+ },
26
37
  "bin": {
27
38
  "tsquare": "bin/tsquare.js"
28
39
  },
29
40
  "files": [
30
41
  "bin/",
31
- "src/",
42
+ "dist/",
32
43
  "skill/tsquare/",
44
+ "examples/*.tsq",
45
+ "assets/mark.png",
33
46
  "LICENSE",
34
47
  "README.md"
35
48
  ],
@@ -41,28 +54,39 @@
41
54
  "check": "tsx src/cli.ts check",
42
55
  "fmt": "tsx src/cli.ts fmt",
43
56
  "prompt": "tsx src/cli.ts prompt",
44
- "playground": "tsx playground/server.ts",
57
+ "playground": "tsx src/cli.ts playground",
45
58
  "skill": "tsx skill/build.ts",
46
59
  "docs": "tsx docs/build.ts",
47
60
  "typecheck": "tsc",
48
61
  "examples": "for f in examples/*.tsq; do tsx src/cli.ts render $f -o ${f%.tsq}.png --scale 2; done",
49
62
  "eval:prompts": "tsx eval/prompts.ts",
50
- "eval:score": "tsx eval/score.ts"
63
+ "eval:score": "tsx eval/score.ts",
64
+ "build": "tsx scripts/build.ts",
65
+ "prepare": "npm run build"
51
66
  },
52
67
  "dependencies": {
53
68
  "@fontsource/inter": "5.3.0",
54
69
  "@json-render/core": "^0.21.0",
55
70
  "@json-render/image": "^0.21.0",
56
71
  "@resvg/resvg-js": "^2.6.2",
72
+ "@types/node": ">=20",
73
+ "@types/react": "^19.3.0",
57
74
  "lucide": "1.48.0",
58
75
  "react": "^19.3.0",
59
- "tsx": "^4.23.15",
60
76
  "zod": "^4.6.5"
61
77
  },
62
78
  "devDependencies": {
63
- "@types/node": "^26.6.3",
64
- "@types/react": "^19.3.0",
79
+ "@codemirror/autocomplete": "^6.20.3",
80
+ "@codemirror/commands": "^6.11.1",
81
+ "@codemirror/language": "^6.12.4",
82
+ "@codemirror/lint": "^6.9.7",
83
+ "@codemirror/search": "^6.7.2",
84
+ "@codemirror/state": "^6.7.6",
85
+ "@codemirror/view": "^6.43.13",
86
+ "@lezer/highlight": "^1.2.5",
87
+ "esbuild": "^0.28.2",
65
88
  "gpt-tokenizer": "^4.0.0",
89
+ "tsx": "^4.23.15",
66
90
  "typescript": "^7.0.2"
67
91
  }
68
92
  }
@@ -13,13 +13,13 @@ tsquare is a small text language for UI wireframe boards. You write a `.tsq` fil
13
13
  2. **Check:** `tsquare check <name>.tsq`. It lists problems by line, with the valid options or a "did you mean". Fix every one and check again until it says the file is valid.
14
14
  3. **Render:** `tsquare render <name>.tsq -o <name>.png --scale 2` (use `-o <name>.svg` for docs that take SVG).
15
15
  4. **Look at the PNG** before showing it. The checker can't see layout, so check for:
16
- - content cut off at the bottom of a screen (screens have a fixed height; nothing warns about clipping). Remove content, split it into another screen, or use `device=custom` with a larger `height`.
16
+ - content cut off at the bottom of a screen (screens have a fixed height; nothing warns about clipping). Remove content, split it into another screen, or give the screen a larger `height` (it works on any device).
17
17
  - cramped rows, or text squeezed into narrow columns.
18
18
  - anything the request asked for that's missing.
19
19
  Fix the `.tsq` and render again.
20
20
  5. **Deliver** the image path and the `.tsq` source, so the user can edit it later.
21
21
 
22
- If `tsquare` isn't found, stop and ask the user how it's installed. From a checkout of the repo, `npm link` makes the command available.
22
+ If `tsquare` isn't installed, run it through npx (`npx tsquare check <name>.tsq`); it needs Node 20 or newer. If that fails, stop and ask the user how they want it installed.
23
23
 
24
24
  ## Keep in mind
25
25
 
@@ -33,9 +33,10 @@ One element per line. Indent children two spaces under their parent. The first l
33
33
  A line is the component name in lowercase, followed by arguments separated by spaces:
34
34
  - "a quoted string" sets the component's main text prop (marked "main text" in reference.md)
35
35
  - a bare word that is one of the component's option values sets that option: phone, desktop, primary, ghost, row, sm, left, bottom, password, …
36
- - a bare prop name sets a boolean prop to true: checked, fullWidth, grow, muted. `off` and `unchecked` set on/checked to false
36
+ - a bare prop name sets that component's yes/no prop to true, only on components that have it (see each component's props below): checked (checkbox, radio), fullWidth (button), muted (text), grow (stack, card, input, list). `off` and `unchecked` set on/checked to false
37
37
  - key=value sets any prop. Values: "string", number, true/false, bare word, [list, of, values], {key=value key=value}
38
- - # starts a comment
38
+
39
+ Comments: a line that starts with # is a comment. A comment must be on its own line, never after an element on the same line. Anywhere else, # is ordinary text: [#1001, #1002], "Order #12345", accent=#1a73e8.
39
40
 
40
41
  Lists: 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].
41
42
 
@@ -15,8 +15,8 @@ Root canvas (artboard). Holds Screens side by side, plus optional Notes. Must be
15
15
  One view of the product in a device frame. Children stack vertically.
16
16
  - name: string (main text) — Label shown above the screen
17
17
  - device: "phone" | "tablet" | "desktop" | "custom"
18
- - width: number — Only for device=custom
19
- - height: number — Only for device=custom
18
+ - width: number — Overrides the device width
19
+ - height: number — Overrides the device height, e.g. height=1400 for a long scrolling page
20
20
  - chrome: boolean — Phone status bar / desktop browser bar. Default true for phone and desktop.
21
21
  - padding: number
22
22
  - gap: number
@@ -106,7 +106,8 @@ Button. Primary is filled, secondary is outlined, ghost is text only.
106
106
  - label: string (main text)
107
107
  - variant: "primary" | "secondary" | "ghost"
108
108
  - size: "sm" | "md" | "lg"
109
- - icon: string — Lucide icon name in kebab-case, e.g. menu, search, arrow-left, settings, bell, user
109
+ - leadingIcon: string — Lucide icon before the label, e.g. chevron-left for Back
110
+ - trailingIcon: string — Lucide icon after the label, e.g. chevron-right for Next
110
111
  - fullWidth: boolean
111
112
 
112
113
  ### Input
@@ -170,10 +171,10 @@ Row in a List. Omit title for a placeholder bar.
170
171
  - title: string (main text)
171
172
  - subtitle: string
172
173
  - leading: "none" | "icon" | "avatar" | "image" | "checkbox"
173
- - icon: string — Lucide icon name in kebab-case, e.g. menu, search, arrow-left, settings, bell, user
174
+ - leadingIcon: string — Lucide icon on the left; setting it implies leading=icon
174
175
  - trailing: "none" | "chevron" | "toggle" | "text" | "badge" | "icon"
175
- - trailingText: string
176
- - trailingIcon: string — Lucide icon name in kebab-case, e.g. menu, search, arrow-left, settings, bell, user
176
+ - trailingText: string — Text for trailing=text or badge; setting it implies trailing=text
177
+ - trailingIcon: string — Lucide icon on the right; setting it implies trailing=icon
177
178
 
178
179
  ### Table
179
180
  Table with headers. Rows are placeholder bars unless `data` is given.