tsquare 0.10.1 → 0.11.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,12 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.11.0
4
+
5
+ The language and the renderer haven't changed: every wireframe renders exactly as in 0.10.1, and render URLs give the same images.
6
+
7
+ - **A script for any web page:** `<script src="https://cdn.jsdelivr.net/npm/tsquare@0.11/dist/tsquare.min.js"></script>` draws every ```` ```tsquare ```` code block on the page once it has loaded, so wireframes work on Markdown sites (MkDocs, Hugo, Jekyll, Docsify, VitePress, …) with no build step. It finds the HTML Markdown renderers make of a fence, lists a block's problems by line in its place, and sets `window.tsquare` with the whole API. Rendering happens in the reader's browser and matches Node byte for byte. `data-start="manual"` on the script tag leaves it to you to call `tsquare.renderAll()`.
8
+ - **`renderAll()`** is exported for pages that bundle tsquare themselves: it does the same for a document or part of one (`{ root, selector }`).
9
+
3
10
  ## 0.10.1
4
11
 
5
12
  Docs and license only: the code is the same as 0.10.0, and every wireframe renders the same.
package/README.md CHANGED
@@ -128,7 +128,7 @@ const { issues } = compileWireframe(source); // problems by line, empt
128
128
  const png = await renderWireframe(source, { format: "png", scale: 2 });
129
129
  ```
130
130
 
131
- `tsquare/playground` exports the playground's request handler, for hosting it inside your own Node server. `tsquare/viewer` is the playground's zoomable board (zoom, pan, pinch, a chip per screen) for your own page: pass it the elements, then `show()` the SVG from `renderWireframeSvg` with the size and screens from `boardLayout`. It sets the styles its zoom math needs itself; give the stage a size, and optionally style the classes it toggles on the stage: `panning` (while dragging), `animate` (while fitting or zooming to a screen, e.g. `.animate > * { transition: transform .25s }`) and `stale` (from `setStale`, e.g. `.stale img { opacity: .35 }`). `languageData()` lists every component, prop, option word, icon and accent, for editor highlighting and autocomplete. In a browser bundle (esbuild, Vite, webpack) the same imports render SVG with no setup; PNG and share links need Node. See [Using it with AI](docs/ai.md) for the prompt and the repair loop.
131
+ `tsquare/playground` exports the playground's request handler, for hosting it inside your own Node server. `tsquare/viewer` is the playground's zoomable board (zoom, pan, pinch, a chip per screen) for your own page: pass it the elements, then `show()` the SVG from `renderWireframeSvg` with the size and screens from `boardLayout`. It sets the styles its zoom math needs itself; give the stage a size, and optionally style the classes it toggles on the stage: `panning` (while dragging), `animate` (while fitting or zooming to a screen, e.g. `.animate > * { transition: transform .25s }`) and `stale` (from `setStale`, e.g. `.stale img { opacity: .35 }`). `languageData()` lists every component, prop, option word, icon and accent, for editor highlighting and autocomplete. In a browser bundle (esbuild, Vite, webpack) the same imports render SVG with no setup; PNG and share links need Node. On a page without a build step, `<script src="https://cdn.jsdelivr.net/npm/tsquare@0.11/dist/tsquare.min.js"></script>` draws every ```` ```tsquare ```` code block on the page (see [Embedding](docs/embedding.md#pages-written-in-markdown)). See [Using it with AI](docs/ai.md) for the prompt and the repair loop.
132
132
 
133
133
  ## Using it with Claude (skill)
134
134
 
@@ -23382,6 +23382,80 @@ async function callMcpTool(name, args = {}, opts = {}) {
23382
23382
  return name === "render_wireframe" ? renderWireframeTool(parsed.data) : shareWireframeTool(parsed.data, opts);
23383
23383
  }
23384
23384
 
23385
+ // src/dom.ts
23386
+ var DEFAULT_SELECTOR = ".tsquare, .language-tsquare, [data-lang='tsquare']";
23387
+ var DONE = "data-tsquare";
23388
+ var STYLES = `:where(.tsquare-board){margin:1em 0}
23389
+ :where(.tsquare-board) img{display:block;max-width:100%;height:auto}
23390
+ :where(.tsquare-problems){margin:1em 0;padding:.5em .75em;border-left:3px solid #d33;font-size:.9em}
23391
+ :where(.tsquare-problems) ul{margin:.25em 0 0;padding-left:1.25em;font-family:ui-monospace,Menlo,monospace}`;
23392
+ async function renderAll(opts = {}) {
23393
+ const root = opts.root ?? document;
23394
+ const doc = root.ownerDocument ?? root;
23395
+ const result = { rendered: 0, invalid: 0 };
23396
+ const blocks = [];
23397
+ for (const el2 of root.querySelectorAll(opts.selector ?? DEFAULT_SELECTOR)) {
23398
+ const target = el2.tagName === "CODE" ? el2.closest("pre") ?? el2 : el2;
23399
+ if (target.hasAttribute(DONE) || target.closest(`[${DONE}]`)) continue;
23400
+ if (blocks.some((b) => b.target.contains(target) || target.contains(b.target))) continue;
23401
+ blocks.push({ target, text: el2.tagName === "CODE" ? el2 : el2.querySelector("code") ?? el2.querySelector("pre") ?? el2 });
23402
+ }
23403
+ if (blocks.length) addStyles(doc);
23404
+ for (const { target, text: text2 } of blocks) {
23405
+ const source = text2.textContent ?? "";
23406
+ const replacement = await renderBlock(doc, source);
23407
+ replacement.setAttribute(DONE, "");
23408
+ target.replaceWith(replacement);
23409
+ if (replacement.classList.contains("tsquare-board")) result.rendered++;
23410
+ else result.invalid++;
23411
+ }
23412
+ return result;
23413
+ }
23414
+ async function renderBlock(doc, source) {
23415
+ const { spec, issues } = compileWireframe(source);
23416
+ if (!spec || issues.length) return problems2(doc, issues.map((i2) => `line ${i2.line}: ${i2.message}`));
23417
+ try {
23418
+ const svg = await renderWireframeSvg(spec, { skipValidation: true });
23419
+ const { width, height } = boardLayout(spec);
23420
+ const title = spec.elements[spec.root]?.props?.title;
23421
+ const figure = doc.createElement("figure");
23422
+ figure.className = "tsquare-board";
23423
+ const img = doc.createElement("img");
23424
+ img.src = svgDataUrl(svg);
23425
+ img.width = width;
23426
+ img.height = height;
23427
+ img.alt = title ? `Wireframe: ${title}` : "Wireframe";
23428
+ figure.append(img);
23429
+ return figure;
23430
+ } catch (err2) {
23431
+ return problems2(doc, [`tsquare couldn't render this board: ${err2 instanceof Error ? err2.message : String(err2)}`]);
23432
+ }
23433
+ }
23434
+ function svgDataUrl(svg) {
23435
+ const bytes = new TextEncoder().encode(svg);
23436
+ let binary = "";
23437
+ for (let i2 = 0; i2 < bytes.length; i2 += 32768) binary += String.fromCharCode(...bytes.subarray(i2, i2 + 32768));
23438
+ return `data:image/svg+xml;base64,${btoa(binary)}`;
23439
+ }
23440
+ function problems2(doc, lines) {
23441
+ const box = doc.createElement("div");
23442
+ box.className = "tsquare-problems";
23443
+ box.setAttribute("role", "note");
23444
+ const title = doc.createElement("strong");
23445
+ title.textContent = lines.length === 1 ? "1 problem in this wireframe" : `${lines.length} problems in this wireframe`;
23446
+ const list = doc.createElement("ul");
23447
+ for (const line of lines) list.append(Object.assign(doc.createElement("li"), { textContent: line }));
23448
+ box.append(title, list);
23449
+ return box;
23450
+ }
23451
+ function addStyles(doc) {
23452
+ if (doc.getElementById("tsquare-styles")) return;
23453
+ const style = doc.createElement("style");
23454
+ style.id = "tsquare-styles";
23455
+ style.textContent = STYLES;
23456
+ doc.head.append(style);
23457
+ }
23458
+
23385
23459
  // src/playground/language-data.ts
23386
23460
  function unwrap4(t) {
23387
23461
  let cur = t;
@@ -23435,6 +23509,7 @@ function languageData() {
23435
23509
  }
23436
23510
  export {
23437
23511
  DEFAULT_LINKS,
23512
+ DEFAULT_SELECTOR,
23438
23513
  DEVICES,
23439
23514
  LINK_KINDS,
23440
23515
  MAX_SHARED_TEXT,
@@ -23456,6 +23531,7 @@ export {
23456
23531
  mcpTools,
23457
23532
  parseWireframeText,
23458
23533
  registry,
23534
+ renderAll,
23459
23535
  renderWireframe,
23460
23536
  renderWireframePng,
23461
23537
  renderWireframeSvg,
package/dist/dom.d.ts ADDED
@@ -0,0 +1,14 @@
1
+ export declare const DEFAULT_SELECTOR = ".tsquare, .language-tsquare, [data-lang='tsquare']";
2
+ export interface RenderAllOptions {
3
+ /** Where to look (default: the whole document). */
4
+ root?: ParentNode;
5
+ /** Which elements hold wireframe text (default: DEFAULT_SELECTOR). */
6
+ selector?: string;
7
+ }
8
+ export interface RenderAllResult {
9
+ /** Boards drawn. */
10
+ rendered: number;
11
+ /** Blocks whose text has problems (listed in place of the board). */
12
+ invalid: number;
13
+ }
14
+ export declare function renderAll(opts?: RenderAllOptions): Promise<RenderAllResult>;
package/dist/index.d.ts CHANGED
@@ -5,6 +5,7 @@ export { printWireframeText as formatWireframe } from "./print.js";
5
5
  export { encodeWireframe, decodeWireframe, MAX_SHARED_TEXT } from "./share.js";
6
6
  export { mcpTools, MCP_INSTRUCTIONS, LINK_KINDS, DEFAULT_LINKS, type LinkKind, wireframeGuide, renderWireframeTool, shareWireframeTool, callMcpTool, type ToolResult, type ToolContent, type ToolOptions, type McpToolName } from "./mcp.js";
7
7
  export { upgradeWireframe, upgradeSpec, RENAMED_PROPS, type UpgradeOptions } from "./upgrade.js";
8
+ export { renderAll, DEFAULT_SELECTOR, type RenderAllOptions, type RenderAllResult } from "./dom.js";
8
9
  export { renderWireframeSvg, renderWireframePng, checkSpec, boardSize, boardLayout, type BoardItem, type RenderWireframeOptions } from "./render.js";
9
10
  export { parseWireframeText, type TextIssue, type ParseResult } from "./text.js";
10
11
  export { catalog, componentDefinitions } from "./catalog.js";
package/dist/index.js CHANGED
@@ -166,8 +166,83 @@ async function callMcpTool(name, args = {}, opts = {}) {
166
166
  if (!parsed.success) return { content: [text(`Invalid arguments: ${z.prettifyError(parsed.error)}`)], isError: true };
167
167
  return name === "render_wireframe" ? renderWireframeTool(parsed.data) : shareWireframeTool(parsed.data, opts);
168
168
  }
169
+
170
+ // src/dom.ts
171
+ var DEFAULT_SELECTOR = ".tsquare, .language-tsquare, [data-lang='tsquare']";
172
+ var DONE = "data-tsquare";
173
+ var STYLES = `:where(.tsquare-board){margin:1em 0}
174
+ :where(.tsquare-board) img{display:block;max-width:100%;height:auto}
175
+ :where(.tsquare-problems){margin:1em 0;padding:.5em .75em;border-left:3px solid #d33;font-size:.9em}
176
+ :where(.tsquare-problems) ul{margin:.25em 0 0;padding-left:1.25em;font-family:ui-monospace,Menlo,monospace}`;
177
+ async function renderAll(opts = {}) {
178
+ const root = opts.root ?? document;
179
+ const doc = root.ownerDocument ?? root;
180
+ const result = { rendered: 0, invalid: 0 };
181
+ const blocks = [];
182
+ for (const el of root.querySelectorAll(opts.selector ?? DEFAULT_SELECTOR)) {
183
+ const target = el.tagName === "CODE" ? el.closest("pre") ?? el : el;
184
+ if (target.hasAttribute(DONE) || target.closest(`[${DONE}]`)) continue;
185
+ if (blocks.some((b) => b.target.contains(target) || target.contains(b.target))) continue;
186
+ blocks.push({ target, text: el.tagName === "CODE" ? el : el.querySelector("code") ?? el.querySelector("pre") ?? el });
187
+ }
188
+ if (blocks.length) addStyles(doc);
189
+ for (const { target, text: text2 } of blocks) {
190
+ const source = text2.textContent ?? "";
191
+ const replacement = await renderBlock(doc, source);
192
+ replacement.setAttribute(DONE, "");
193
+ target.replaceWith(replacement);
194
+ if (replacement.classList.contains("tsquare-board")) result.rendered++;
195
+ else result.invalid++;
196
+ }
197
+ return result;
198
+ }
199
+ async function renderBlock(doc, source) {
200
+ const { spec, issues } = compileWireframe(source);
201
+ if (!spec || issues.length) return problems2(doc, issues.map((i) => `line ${i.line}: ${i.message}`));
202
+ try {
203
+ const svg = await renderWireframeSvg(spec, { skipValidation: true });
204
+ const { width, height } = boardLayout(spec);
205
+ const title = spec.elements[spec.root]?.props?.title;
206
+ const figure = doc.createElement("figure");
207
+ figure.className = "tsquare-board";
208
+ const img = doc.createElement("img");
209
+ img.src = svgDataUrl(svg);
210
+ img.width = width;
211
+ img.height = height;
212
+ img.alt = title ? `Wireframe: ${title}` : "Wireframe";
213
+ figure.append(img);
214
+ return figure;
215
+ } catch (err) {
216
+ return problems2(doc, [`tsquare couldn't render this board: ${err instanceof Error ? err.message : String(err)}`]);
217
+ }
218
+ }
219
+ function svgDataUrl(svg) {
220
+ const bytes = new TextEncoder().encode(svg);
221
+ let binary = "";
222
+ for (let i = 0; i < bytes.length; i += 32768) binary += String.fromCharCode(...bytes.subarray(i, i + 32768));
223
+ return `data:image/svg+xml;base64,${btoa(binary)}`;
224
+ }
225
+ function problems2(doc, lines) {
226
+ const box = doc.createElement("div");
227
+ box.className = "tsquare-problems";
228
+ box.setAttribute("role", "note");
229
+ const title = doc.createElement("strong");
230
+ title.textContent = lines.length === 1 ? "1 problem in this wireframe" : `${lines.length} problems in this wireframe`;
231
+ const list = doc.createElement("ul");
232
+ for (const line of lines) list.append(Object.assign(doc.createElement("li"), { textContent: line }));
233
+ box.append(title, list);
234
+ return box;
235
+ }
236
+ function addStyles(doc) {
237
+ if (doc.getElementById("tsquare-styles")) return;
238
+ const style = doc.createElement("style");
239
+ style.id = "tsquare-styles";
240
+ style.textContent = STYLES;
241
+ doc.head.append(style);
242
+ }
169
243
  export {
170
244
  DEFAULT_LINKS,
245
+ DEFAULT_SELECTOR,
171
246
  DEVICES,
172
247
  LINK_KINDS,
173
248
  MAX_SHARED_TEXT,
@@ -189,6 +264,7 @@ export {
189
264
  mcpTools,
190
265
  parseWireframeText,
191
266
  registry,
267
+ renderAll,
192
268
  renderWireframe,
193
269
  renderWireframePng,
194
270
  renderWireframeSvg,