tsquare 0.7.1 → 0.8.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/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.8.0
4
+
5
+ ### Brand logos
6
+
7
+ ```tsquare
8
+ button secondary "Continue with Google" leadingIcon=brand-google fullWidth
9
+ listitem "GitHub" subtitle="Connected" leadingIcon=brand-github trailing=toggle
10
+ ```
11
+
12
+ - **Seven brand logos**, usable anywhere an icon is: `brand-apple`, `brand-facebook`, `brand-github`, `brand-google`, `brand-notion`, `brand-stripe`, `brand-x`. They're for sign-in buttons and integrations, and are drawn in the wireframe's gray at the same visual size as Lucide icons.
13
+ - **The `brand-` prefix keeps them apart from Lucide:** `apple` is still Lucide's fruit and `x` its close icon. A brand name without the prefix is an error that suggests it (`github` → `brand-github`, `twitter` → `brand-x`), and an unknown `brand-…` name lists the logos there are.
14
+ - The logos come from [Simple Icons](https://simpleicons.org) 16.34.0 (CC0), a new dependency pinned exactly like Lucide, since a release can change or remove logos. Simple Icons has no Slack, LinkedIn or Microsoft logo, so tsquare doesn't either.
15
+ - **Prompt:** rule 11 names the logos and the `apple`/`x` trap. The skill and the docs (Icons page, Icon component) are regenerated.
16
+
17
+ ### Flow wording
18
+
19
+ - **"Board level" is gone.** The Flow description, prompt rule 10, the docs and the error for a flow written inside a screen now say what to do: write each flow on its own line after the screens, indented like a screen (not inside one). The error used to read "flow lines go at the board level, after the screens (indented like a screen)". A clarification only: what's valid hasn't changed.
20
+
3
21
  ## 0.7.1
4
22
 
5
23
  - **`share_wireframe` returns only the links asked for.** A new `links` option takes the kinds from the playground's Copy link menu, with the same names: `share`, `svg`, `png`, `markdown`, `html`. The default is `["svg", "share"]`, an image link and the link to edit it; before, every call returned four links. Each link carries the whole wireframe (about 1,200 characters for a three-screen board), and the model has to write out every link it passes on, exactly, so fewer links means less to write and fewer chances to break one. A wireframe too long for an image link gets its share link instead, with a note saying why. New exports: `LINK_KINDS`, `DEFAULT_LINKS`, `LinkKind`.
package/README.md CHANGED
@@ -95,7 +95,7 @@ Sticky notes beside the wireframe have their own colors, since they're annotatio
95
95
  |---|---|
96
96
  | Canvas | board, screen (`phone` 390×844, `tablet` 820×1180, `desktop` 1280×800, `custom`), note |
97
97
  | Layout | stack, grid, card, divider, spacer |
98
- | Content | heading, text (`lines=3` draws placeholder lines), image (X-box placeholder), icon (any [Lucide](https://lucide.dev/icons) name, see [Icons](docs/icons.md)), avatar, badge |
98
+ | Content | heading, text (`lines=3` draws placeholder lines), image (X-box placeholder), icon (any [Lucide](https://lucide.dev/icons) name or a brand logo such as `brand-google`, see [Icons](docs/icons.md)), avatar, badge |
99
99
  | Controls | button, input (`multiline=4` for a textarea), checkbox, radio, toggle, select |
100
100
  | Navigation & data | navbar, tabbar, tabs, list, listitem, table |
101
101
  | Overlays | modal, drawer (`left`, `right`, `bottom` sheet) |
@@ -158,4 +158,4 @@ How it was measured, the tasks, every model output, and the scripts: [`eval/`](e
158
158
 
159
159
  ## License
160
160
 
161
- MIT. The Inter font comes from the `@fontsource/inter` dependency, under the SIL Open Font License 1.1. Icons are from [Lucide](https://lucide.dev), under the ISC license.
161
+ MIT. The Inter font comes from the `@fontsource/inter` dependency, under the SIL Open Font License 1.1. Icons are from [Lucide](https://lucide.dev), under the ISC license. Brand logos are from [Simple Icons](https://simpleicons.org) (CC0); the logos themselves are trademarks of their owners.
@@ -131,14 +131,20 @@ function paletteFor(accent) {
131
131
 
132
132
  // src/icons.ts
133
133
  import { icons } from "lucide";
134
+ import { siApple, siFacebook, siGithub, siGoogle, siNotion, siStripe, siX } from "simple-icons";
135
+ var BRAND_ICONS = Object.fromEntries(
136
+ [siApple, siFacebook, siGithub, siGoogle, siNotion, siStripe, siX].map((i) => [`brand-${i.slug}`, { title: i.title, path: i.path }])
137
+ );
138
+ var brandIconNames = Object.keys(BRAND_ICONS).sort();
139
+ var brandPath = (name) => BRAND_ICONS[name.toLowerCase()]?.path;
134
140
  var pascal = (s) => s.replace(/(^|[-_ ])(\w)/g, (_, __, c) => c.toUpperCase());
135
141
  var kebab = (s) => s.replace(/([a-z])([A-Z0-9])/g, "$1-$2").replace(/([0-9])([A-Z])/g, "$1-$2").replace(/([A-Z])([A-Z][a-z])/g, "$1-$2").toLowerCase();
136
142
  var table = icons;
137
- var iconNames = Object.keys(table).map(kebab);
143
+ var iconNames = [...Object.keys(table).map(kebab), ...brandIconNames];
138
144
  function iconNode(name) {
139
145
  return table[pascal(name)];
140
146
  }
141
- var isIcon = (name) => iconNode(name) !== void 0;
147
+ var isIcon = (name) => iconNode(name) !== void 0 || brandPath(name) !== void 0;
142
148
  var GUESSES = {
143
149
  call: ["phone"],
144
150
  compose: ["square-pen", "pencil"],
@@ -148,7 +154,8 @@ var GUESSES = {
148
154
  notifications: ["bell"],
149
155
  profile: ["user", "circle-user"],
150
156
  envelope: ["mail"],
151
- close: ["x"]
157
+ close: ["x"],
158
+ twitter: ["brand-x"]
152
159
  };
153
160
  var DIRECTIONS = ["right", "left", "down", "up"];
154
161
  var directionRank = (n) => {
@@ -157,7 +164,7 @@ var directionRank = (n) => {
157
164
  };
158
165
  function suggestIcons(name, limit = 4) {
159
166
  const q = name.toLowerCase().replace(/[_ ]/g, "-");
160
- const guessed = GUESSES[q] ?? [];
167
+ const guessed = [...BRAND_ICONS[`brand-${q}`] ? [`brand-${q}`] : [], ...GUESSES[q] ?? []];
161
168
  const words = q.split("-").filter((w) => w.length > 2);
162
169
  const maxEdits = Math.max(1, Math.floor(q.length / 3));
163
170
  const scored = [];
@@ -173,6 +180,7 @@ function suggestIcons(name, limit = 4) {
173
180
  return [.../* @__PURE__ */ new Set([...guessed, ...close])].slice(0, limit);
174
181
  }
175
182
  function unknownIconMessage(name) {
183
+ if (/^brand-/i.test(name)) return `unknown icon "${name}"; the brand logos are ${brandIconNames.join(", ")}`;
176
184
  const close = suggestIcons(name);
177
185
  return close.length ? `unknown icon "${name}" (did you mean ${close.join(", ")}?)` : `unknown icon "${name}" (use a Lucide name in kebab-case, e.g. search, user, settings)`;
178
186
  }
@@ -297,7 +305,7 @@ var componentDefinitions = {
297
305
  )
298
306
  }),
299
307
  slots: [],
300
- description: 'An arrow between two elements or screens, named with #id. Written at the board level after the screens: flow signin -> home "Tap Sign in".',
308
+ description: 'An arrow between two elements or screens, named with #id. Write each flow on its own line after the screens, indented like a screen (not inside one): flow signin -> home "Tap Sign in".',
301
309
  example: { from: "signin", to: "home", label: "Tap Sign in" }
302
310
  },
303
311
  // ── Layout ────────────────────────────────────────────────────────────
@@ -427,7 +435,7 @@ var componentDefinitions = {
427
435
  size: o(z.number())
428
436
  }),
429
437
  slots: [],
430
- description: "Line icon from Lucide.",
438
+ description: "Line icon from Lucide, or a brand logo (brand-google, brand-apple, \u2026).",
431
439
  example: { name: "search", size: 20 }
432
440
  },
433
441
  Avatar: {
@@ -735,6 +743,9 @@ function hug(dir, aligned = false) {
735
743
  return dir === "row" || aligned ? {} : { alignSelf: "flex-start" };
736
744
  }
737
745
  function IconGlyph({ name, size = 20, color = theme.ink, strokeWidth = 2 }) {
746
+ const brand = name ? brandPath(name) : void 0;
747
+ if (brand)
748
+ return /* @__PURE__ */ jsx("svg", { width: size, height: size, viewBox: "-2 -2 28 28", fill: color, style: { flexShrink: 0 }, children: /* @__PURE__ */ jsx("path", { d: brand }) });
738
749
  const node = name ? iconNode(name) : null;
739
750
  return /* @__PURE__ */ jsx(
740
751
  "svg",
@@ -2848,7 +2859,7 @@ function checkSpec(spec) {
2848
2859
  for (const [k, el] of Object.entries(spec.elements)) for (const c of el.children ?? []) parentOf.set(c, k);
2849
2860
  for (const [key, el] of Object.entries(spec.elements)) {
2850
2861
  if (el.type !== "Flow") continue;
2851
- if (!(root.children ?? []).includes(key)) issues.push(`${key}: flow lines go at the board level, after the screens (indented like a screen)`);
2862
+ if (!(root.children ?? []).includes(key)) issues.push(`${key}: write each flow on its own line after the screens, indented like a screen (not inside one)`);
2852
2863
  const p = el.props ?? {};
2853
2864
  for (const end of ["from", "to"]) {
2854
2865
  if (typeof p[end] !== "string") continue;
@@ -3229,7 +3240,8 @@ var RULES = `## Rules
3229
3240
  7. Only use the components and props listed below. All props are optional unless marked required.
3230
3241
  8. 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), Input error and Flow color. Only add an accent if the request asks for color or a brand.
3231
3242
  9. Any element except Board, Screen, Note and Flow can take tooltip="text" to show a tooltip next to it. Select (with options), Input type=date, and Button, ListItem or NavBar (with menu) take open to show their list, calendar or menu over the screen.
3232
- 10. To show how screens connect, name elements or screens by writing #name after them (button primary "Sign in" #signin, screen phone "Home" #home), then add board-level lines after the screens: flow signin -> home "Tap Sign in".`;
3243
+ 10. To show how screens connect, name elements or screens by writing #name after them (button primary "Sign in" #signin, screen phone "Home" #home), then write each flow on its own line after the screens, indented like a screen (not inside one): flow signin -> home "Tap Sign in".
3244
+ 11. Icons are Lucide names, plus brand logos named brand-\u2026: brand-google, brand-apple, brand-github, brand-facebook, brand-x, brand-notion, brand-stripe (button "Continue with Google" leadingIcon=brand-google). Plain apple is a fruit and x is a close icon.`;
3233
3245
  var TEXT_FORMAT = `## Output format: wireframe text
3234
3246
  One element per line. Indent children two spaces under their parent. The first line is the board.
3235
3247
 
package/dist/cli.js CHANGED
@@ -6,7 +6,7 @@ import {
6
6
  printWireframeText,
7
7
  renderWireframe,
8
8
  wireframePrompt
9
- } from "./chunks/chunk-ZLVSCEWO.js";
9
+ } from "./chunks/chunk-W3OU5IS6.js";
10
10
 
11
11
  // src/cli.ts
12
12
  import { readFile, writeFile } from "node:fs/promises";
package/dist/icons.d.ts CHANGED
@@ -1,4 +1,17 @@
1
- /** Kebab-case names for every Lucide icon (including Lucide's aliases, e.g. home and house). */
1
+ /**
2
+ * Brand logos, from Simple Icons (CC0; pinned exactly, like lucide). Filled shapes on a 24×24 grid,
3
+ * named brand-… so they can't collide with Lucide: `apple` is Lucide's fruit and `x` its close icon.
4
+ * Imported one by one so a browser bundle carries only these. Simple Icons has no Slack, LinkedIn or
5
+ * Microsoft logo (removed); don't draw our own.
6
+ */
7
+ export declare const BRAND_ICONS: Record<string, {
8
+ title: string;
9
+ path: string;
10
+ }>;
11
+ export declare const brandIconNames: string[];
12
+ /** The SVG path of a brand-… logo, or undefined. */
13
+ export declare const brandPath: (name: string) => string | undefined;
14
+ /** Kebab-case names for every Lucide icon (including Lucide's aliases, e.g. home and house), then the brand logos. */
2
15
  export declare const iconNames: string[];
3
16
  /** The Lucide icon node for a kebab-case name, or undefined if there's no such icon. */
4
17
  export declare function iconNode(name: string): any;
package/dist/index.js CHANGED
@@ -21,7 +21,7 @@ import {
21
21
  upgradeSpec,
22
22
  upgradeWireframe,
23
23
  wireframePrompt
24
- } from "./chunks/chunk-ZLVSCEWO.js";
24
+ } from "./chunks/chunk-W3OU5IS6.js";
25
25
 
26
26
  // src/share.ts
27
27
  import { deflateRawSync, inflateRawSync } from "node:zlib";
@@ -13,7 +13,7 @@ import {
13
13
  renderWireframeSvg,
14
14
  takesUniversalProps,
15
15
  wireframePrompt
16
- } from "../chunks/chunk-ZLVSCEWO.js";
16
+ } from "../chunks/chunk-W3OU5IS6.js";
17
17
 
18
18
  // src/playground/index.ts
19
19
  import { createServer } from "node:http";
package/dist/prompt.d.ts CHANGED
@@ -13,7 +13,7 @@ export declare function componentDocs(): {
13
13
  props: PropDoc[];
14
14
  }[];
15
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 and Flow lines after them (rule 10).\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, Drawer and Toast 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), Input error and Flow color. Only add an accent if the request asks for color or a brand.\n9. Any element except Board, Screen, Note and Flow can take tooltip=\"text\" to show a tooltip next to it. Select (with options), Input type=date, and Button, ListItem or NavBar (with menu) take open to show their list, calendar or menu over the screen.\n10. To show how screens connect, name elements or screens by writing #name after them (button primary \"Sign in\" #signin, screen phone \"Home\" #home), then add board-level lines after the screens: flow signin -> home \"Tap Sign in\".";
16
+ export declare const RULES = "## Rules\n1. The top element is a Board. The Board's children are Screens, plus optional Notes beside them and Flow lines after them (rule 10).\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, Drawer and Toast 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), Input error and Flow color. Only add an accent if the request asks for color or a brand.\n9. Any element except Board, Screen, Note and Flow can take tooltip=\"text\" to show a tooltip next to it. Select (with options), Input type=date, and Button, ListItem or NavBar (with menu) take open to show their list, calendar or menu over the screen.\n10. To show how screens connect, name elements or screens by writing #name after them (button primary \"Sign in\" #signin, screen phone \"Home\" #home), then write each flow on its own line after the screens, indented like a screen (not inside one): flow signin -> home \"Tap Sign in\".\n11. Icons are Lucide names, plus brand logos named brand-\u2026: brand-google, brand-apple, brand-github, brand-facebook, brand-x, brand-notion, brand-stripe (button \"Continue with Google\" leadingIcon=brand-google). Plain apple is a fruit and x is a close icon.";
17
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. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tsquare",
3
- "version": "0.7.1",
3
+ "version": "0.8.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",
@@ -75,6 +75,7 @@
75
75
  "@types/react": "^19.3.0",
76
76
  "lucide": "1.48.0",
77
77
  "react": "^19.3.0",
78
+ "simple-icons": "16.34.0",
78
79
  "zod": "^4.6.5"
79
80
  },
80
81
  "devDependencies": {
@@ -52,7 +52,8 @@ If a bare word could mean more than one prop, write it as key=value.
52
52
  7. Only use the components and props listed below. All props are optional unless marked required.
53
53
  8. 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), Input error and Flow color. Only add an accent if the request asks for color or a brand.
54
54
  9. Any element except Board, Screen, Note and Flow can take tooltip="text" to show a tooltip next to it. Select (with options), Input type=date, and Button, ListItem or NavBar (with menu) take open to show their list, calendar or menu over the screen.
55
- 10. To show how screens connect, name elements or screens by writing #name after them (button primary "Sign in" #signin, screen phone "Home" #home), then add board-level lines after the screens: flow signin -> home "Tap Sign in".
55
+ 10. To show how screens connect, name elements or screens by writing #name after them (button primary "Sign in" #signin, screen phone "Home" #home), then write each flow on its own line after the screens, indented like a screen (not inside one): flow signin -> home "Tap Sign in".
56
+ 11. Icons are Lucide names, plus brand logos named brand-…: brand-google, brand-apple, brand-github, brand-facebook, brand-x, brand-notion, brand-stripe (button "Continue with Google" leadingIcon=brand-google). Plain apple is a fruit and x is a close icon.
56
57
 
57
58
  ## Example
58
59
 
@@ -28,7 +28,7 @@ Sticky-note annotation. Put it on the Board beside screens, or inside a Screen n
28
28
  - width: number
29
29
 
30
30
  ### Flow
31
- An arrow between two elements or screens, named with #id. Written at the board level after the screens: flow signin -> home "Tap Sign in".
31
+ An arrow between two elements or screens, named with #id. Write each flow on its own line after the screens, indented like a screen (not inside one): flow signin -> home "Tap Sign in".
32
32
  - from (required): string — The id where the arrow starts (written before ->)
33
33
  - to (required): string — The id it points at (written after ->)
34
34
  - label: string (main text)
@@ -116,7 +116,7 @@ Chart placeholder: a generic line, bar, area, pie or donut shape, no data.
116
116
  - width: number | string — Default fills the container width
117
117
 
118
118
  ### Icon
119
- Line icon from Lucide.
119
+ Line icon from Lucide, or a brand logo (brand-google, brand-apple, …).
120
120
  - name: string (main text) — Lucide icon name in kebab-case, e.g. menu, search, arrow-left, settings, bell, user
121
121
  - size: number
122
122