tsquare 0.2.0 → 0.3.1

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/dist/prompt.d.ts CHANGED
@@ -14,7 +14,7 @@ export declare function componentDocs(): {
14
14
  }[];
15
15
  export declare function componentReference(): string;
16
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.";
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
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
19
  /** The system prompt for writing wireframe text. */
20
20
  export declare function wireframePrompt(): string;
package/dist/render.d.ts CHANGED
@@ -5,7 +5,27 @@ export declare class SpecError extends Error {
5
5
  }
6
6
  /** Catalog validation plus the structural rules the renderer depends on. */
7
7
  export declare function checkSpec(spec: Spec): string[];
8
- /** Work out the canvas size from the Board's screens (Satori needs it up front). */
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. */
9
29
  export declare function boardSize(spec: Spec): {
10
30
  width: number;
11
31
  height: number;
@@ -0,0 +1,8 @@
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
+ /**
5
+ * The wireframe a link carries, upgraded to the current language: a link made
6
+ * by an older version means today what it meant then (see upgrade.ts).
7
+ */
8
+ export declare function decodeWireframe(data: string): string;
package/dist/text.d.ts CHANGED
@@ -17,11 +17,16 @@
17
17
  * key=value → any prop; value is "string", number, true/false, word, [list] or {key=value …}
18
18
  * list items are separated by commas only, so [Ana Torres, Admin] is two items;
19
19
  * quote an item that contains a comma: ["$1,200", "Smith, J"]
20
- * # comment
20
+ * # comment → only on a line of its own; anywhere else # is text ([#1001, …], accent=#1a73e8)
21
21
  */
22
22
  import type { Spec } from "@json-render/core";
23
23
  /** Which prop a quoted string fills, per component. */
24
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;
25
30
  /** What a component accepts as bare words, for docs: option values (with their prop), ambiguous values, and boolean props. */
26
31
  export declare function bareWords(component: string): {
27
32
  options: {
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Everything tsquare does for wireframes written in an older version of the
3
+ * language, kept in one place so the parser, catalog and renderer only know
4
+ * the current language.
5
+ *
6
+ * upgradeWireframe(text) always-safe rewrites (renamed props);
7
+ * compileWireframe runs it first
8
+ * upgradeWireframe(text, { fromLink: true }) also the rules for text that came
9
+ * from a link made by an older version;
10
+ * decodeWireframe runs it
11
+ * upgradeSpec(spec) the renames, for JSON specs
12
+ *
13
+ * Rewrites keep every line where it was (except moving a comment onto its own
14
+ * line, which only happens for links), so problems still point at the right line.
15
+ *
16
+ * Adding an upgrade: a rename goes in RENAMED_PROPS. A change in meaning goes
17
+ * here only if old text can be recognised without guessing; otherwise new links
18
+ * get a new prefix letter in share.ts and the upgrade applies to the old letter.
19
+ */
20
+ /** Props renamed in 0.3.0. The prompt, docs and autocomplete only teach the new names; `tsquare fmt` rewrites old ones. */
21
+ export declare const RENAMED_PROPS: Record<string, Record<string, string>>;
22
+ export interface UpgradeOptions {
23
+ /** The text came from a link (share link or render URL), which may predate 0.3.0. */
24
+ fromLink?: boolean;
25
+ }
26
+ /** Rewrites older syntax to the current language. Text that's already current comes back unchanged. */
27
+ export declare function upgradeWireframe(text: string, options?: UpgradeOptions): string;
28
+ /** The renames, for a JSON spec. In place; safe to call more than once. */
29
+ export declare function upgradeSpec<S extends {
30
+ elements: Record<string, {
31
+ type: string;
32
+ props?: Record<string, unknown>;
33
+ }>;
34
+ }>(spec: S): S;
35
+ /**
36
+ * Components whose main text may be a plain word (`avatar JD`, `icon search`), the parser's
37
+ * WORD_PRIMARY. Neither has option words, so their first plain word is always the main text.
38
+ * Kept here rather than imported, so the playground's browser bundle doesn't pull in the parser
39
+ * and catalog; scripts/build.ts fails if this drifts from the parser.
40
+ */
41
+ export declare const PLAIN_WORD_MAIN_TEXT: string[];
@@ -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"]]
@@ -16,7 +16,7 @@ board row "Notes app — mobile"
16
16
  text sm "or" muted
17
17
  stack grow
18
18
  divider
19
- button secondary lg "Continue with Google" fullWidth icon=globe
19
+ button secondary lg "Continue with Google" fullWidth leadingIcon=globe
20
20
  spacer
21
21
  text sm "New here? Create an account" align=center muted
22
22
  screen phone "2 · Notes"
@@ -24,13 +24,13 @@ board row "Notes app — mobile"
24
24
  input search placeholder="Search notes"
25
25
  tabs items=[All, Pinned, Shared] active=0
26
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"
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
29
  listitem leading=image trailing=chevron
30
30
  listitem leading=image trailing=chevron
31
31
  listitem leading=image trailing=chevron
32
32
  stack row justify=end grow align=end
33
- button primary "New note" icon=plus
33
+ button primary "New note" leadingIcon=plus
34
34
  tabbar items=[{label=Notes icon=notebook-pen}, {label=Search icon=search}, {label=Shared icon=users}, {label=Profile icon=user}] active=0
35
35
  screen phone "3 · Side menu (drawer)"
36
36
  navbar "Notes" leading=menu actions=[bell, more-vertical]
@@ -49,12 +49,12 @@ board row "Notes app — mobile"
49
49
  text sm "dana@example.com" muted
50
50
  divider
51
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
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
56
  spacer
57
- button secondary "Sign out" icon=log-out fullWidth
57
+ button secondary "Sign out" leadingIcon=log-out fullWidth
58
58
  screen phone "4 · Discard draft (modal)"
59
59
  navbar "New note" leading=close actions=[image, check] align=center
60
60
  heading "Untitled" level=2
@@ -7,10 +7,10 @@ board "Notes app — web"
7
7
  text "Notebook" bold
8
8
  spacer size=12
9
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
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
14
  spacer
15
15
  stack row gap=10
16
16
  avatar "DM" size=32
@@ -21,7 +21,7 @@ board "Notes app — web"
21
21
  spacer
22
22
  stack width=260
23
23
  input search placeholder=Search
24
- button "New note" icon=plus
24
+ button "New note" leadingIcon=plus
25
25
  grid columns=3 gap=20
26
26
  card "Notes this week"
27
27
  heading "42" level=1
@@ -38,7 +38,7 @@ board "Notes app — web"
38
38
  stack row gap=12
39
39
  heading "All notes" level=1
40
40
  spacer
41
- button secondary "Filters" icon=sliders-horizontal
41
+ button secondary "Filters" leadingIcon=sliders-horizontal
42
42
  table columns=[Title, Folder, Owner, Updated] rows=9
43
43
  drawer right "Filters" size=340
44
44
  select "Folder" placeholder="Any folder"
@@ -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.2.0",
3
+ "version": "0.3.1",
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",
@@ -76,6 +76,14 @@
76
76
  "zod": "^4.6.5"
77
77
  },
78
78
  "devDependencies": {
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",
79
87
  "esbuild": "^0.28.2",
80
88
  "gpt-tokenizer": "^4.0.0",
81
89
  "tsx": "^4.23.15",
@@ -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
- - `#` after a space starts a comment that runs to the end of the line
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
 
@@ -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.