tsquare 0.12.2 → 0.12.4
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 +12 -0
- package/README.md +27 -4
- package/dist/browser/index.js +21 -6
- package/dist/chunks/{chunk-WRN5ADPR.js → chunk-BSE346UI.js} +21 -6
- package/dist/chunks/{chunk-NYVFYSZ6.js → chunk-XWGHLFRX.js} +1 -1
- package/dist/cli.js +1 -1
- package/dist/index.js +2 -2
- package/dist/playground/index.js +2 -2
- package/dist/tsquare.min.js +33 -33
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.12.4
|
|
4
|
+
|
|
5
|
+
The language hasn't changed. Boards whose screen names fit render exactly as in 0.12.3 (checked byte for byte on 438 boards); boards with a screen name wider than its screen are fixed:
|
|
6
|
+
|
|
7
|
+
- **A screen name wider than its screen ends in "…"** instead of widening its column. The board's layout sizes each column by its screen, so a long name pushed the screens after it right of where the layout put them, and the last screen was cut off at the board's right edge. Only names that might not fit are truncated, judged by an upper bound on each character's width in the font, so every other name renders as before.
|
|
8
|
+
|
|
9
|
+
## 0.12.3
|
|
10
|
+
|
|
11
|
+
The language hasn't changed. Screens whose content fits render exactly as in 0.12.2 (the same pixels; the SVG's internal mask ids differ). Screens whose content is taller than the screen are fixed:
|
|
12
|
+
|
|
13
|
+
- **Content taller than its screen no longer pushes things out of it.** The screen's body grew with its content, so a toast or a tab bar ended up below the frame and wasn't drawn, a modal was centered too low, and a bottom drawer started too low with its end cut off. The content is now cut off at the bottom of the screen, as it already looked, and bars and overlays are placed in the visible screen. Of 438 boards in the examples and eval outputs, 434 render the same pixels; one modal moves up 18px to the middle of its screen, and three gain 4 to 8 pixels of the frame's bottom border that overflowing content used to cover.
|
|
14
|
+
|
|
3
15
|
## 0.12.2
|
|
4
16
|
|
|
5
17
|
The language and the renderer haven't changed: every wireframe renders exactly as in 0.12.1, and render URLs give the same images.
|
package/README.md
CHANGED
|
@@ -52,10 +52,29 @@ A wireframe can be embedded anywhere an image works (Notion, GitHub, docs sites)
|
|
|
52
52
|
|
|
53
53
|
The playground's **Copy link** menu makes these; [Embedding](docs/embedding.md) has the steps for Notion, GitHub and your own site. The text is compressed into the URL, not encrypted, so anyone with the link can read it; see [privacy](https://tsquare.dev/privacy). `encodeWireframe` and `decodeWireframe` in the library produce and read the same links.
|
|
54
54
|
|
|
55
|
-
##
|
|
55
|
+
## Use it with
|
|
56
|
+
|
|
57
|
+
**With AI**
|
|
58
|
+
|
|
59
|
+
- **Claude:** the [tsquare skill](https://tsquare.dev/docs/ai#the-claude-skill) teaches Claude the language and how to check its own wireframes.
|
|
60
|
+
- **Any model:** the [system prompt](https://tsquare.dev/docs/ai#the-prompt), or the hosted [MCP server](https://tsquare.dev/docs/ai#connect-over-mcp) for Claude, ChatGPT, Cursor, VS Code and other tools that take a remote MCP URL.
|
|
61
|
+
|
|
62
|
+
**In your docs**
|
|
63
|
+
|
|
64
|
+
- **Docusaurus, Astro, MDX:** [`remark-tsquare`](https://tsquare.dev/docs/markdown-plugins#remark-tsquare) draws ```` ```tsquare ```` code blocks when the site is built.
|
|
65
|
+
- **VitePress, Eleventy:** [`markdown-it-tsquare`](https://tsquare.dev/docs/markdown-plugins#markdown-it-tsquare), the same for sites built on markdown-it.
|
|
66
|
+
- **Any web page:** [one script tag](https://tsquare.dev/docs/embedding#pages-written-in-markdown) draws the code blocks in the reader's browser, with no build step.
|
|
67
|
+
- **Notion, GitHub and anywhere an image works:** [image links](#image-links).
|
|
68
|
+
|
|
69
|
+
**In your app**
|
|
70
|
+
|
|
71
|
+
- **React:** [`@tsquare-js/react`](https://tsquare.dev/docs/react) shows boards, a zoomable viewer, an editor and a playground, including boards in a model's reply while it's still streaming in.
|
|
72
|
+
- **Node or the browser:** the [library](#library) itself.
|
|
73
|
+
|
|
74
|
+
**In your editor and notes**
|
|
56
75
|
|
|
57
76
|
- **VS Code** (and Cursor, VSCodium, Windsurf): the [tsquare extension](https://marketplace.visualstudio.com/items?itemName=tsquare-js.tsquare-vscode) gives `.tsq` files a live preview, problems as you type, highlighting and autocomplete. Also on [Open VSX](https://open-vsx.org/extension/tsquare-js/tsquare-vscode).
|
|
58
|
-
- **Obsidian
|
|
77
|
+
- **Obsidian:** the [tsquare plugin](https://community.obsidian.md/plugins/tsquare) draws ```` ```tsquare ```` code blocks in your notes, on desktop and mobile. See [the Obsidian page](https://tsquare.dev/docs/obsidian).
|
|
59
78
|
|
|
60
79
|
## The language
|
|
61
80
|
|
|
@@ -117,10 +136,14 @@ tsquare prompt # system prompt for models
|
|
|
117
136
|
tsquare playground [--port 4321] # live editor, component reference, prompt
|
|
118
137
|
```
|
|
119
138
|
|
|
120
|
-
Use `-` to read from stdin. In this repo, run them through npm: `npm run check -- file.tsq`. Files use the `.tsq` extension; the CLI reads any text file.
|
|
139
|
+
Run them with `npx tsquare …` (nothing to install), or install the `tsquare` command with `npm install -g tsquare`. Use `-` to read from stdin. In this repo, run them through npm: `npm run check -- file.tsq`. Files use the `.tsq` extension; the CLI reads any text file.
|
|
121
140
|
|
|
122
141
|
## Library
|
|
123
142
|
|
|
143
|
+
```bash
|
|
144
|
+
npm install tsquare
|
|
145
|
+
```
|
|
146
|
+
|
|
124
147
|
```ts
|
|
125
148
|
import { renderWireframe, compileWireframe, formatIssues } from "tsquare";
|
|
126
149
|
|
|
@@ -128,7 +151,7 @@ const { issues } = compileWireframe(source); // problems by line, empt
|
|
|
128
151
|
const png = await renderWireframe(source, { format: "png", scale: 2 });
|
|
129
152
|
```
|
|
130
153
|
|
|
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 }`). Call `destroy()` when you remove it. `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.
|
|
154
|
+
`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 }`). Call `destroy()` when you remove it. `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.12/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
155
|
|
|
133
156
|
### The editor: `tsquare/editor`
|
|
134
157
|
|
package/dist/browser/index.js
CHANGED
|
@@ -20789,6 +20789,16 @@ function BrowserBar() {
|
|
|
20789
20789
|
}
|
|
20790
20790
|
);
|
|
20791
20791
|
}
|
|
20792
|
+
function mayOverflow(name, width) {
|
|
20793
|
+
let w2 = 0;
|
|
20794
|
+
for (const ch of name) {
|
|
20795
|
+
if (ch.charCodeAt(0) >= 128 || /[MWmw@%&]/.test(ch)) w2 += 16.5;
|
|
20796
|
+
else if (/[A-Z]/.test(ch)) w2 += 12.5;
|
|
20797
|
+
else if (/[ .,:;'!|il1jft()[\]{}"`-]/.test(ch)) w2 += 8.5;
|
|
20798
|
+
else w2 += 11;
|
|
20799
|
+
}
|
|
20800
|
+
return w2 > width;
|
|
20801
|
+
}
|
|
20792
20802
|
function Screen({ element, children }) {
|
|
20793
20803
|
const p = element.props;
|
|
20794
20804
|
const s = screenSize(p);
|
|
@@ -20804,7 +20814,12 @@ function Screen({ element, children }) {
|
|
|
20804
20814
|
else body.push(c2);
|
|
20805
20815
|
}
|
|
20806
20816
|
return /* @__PURE__ */ jsxs(Box, { style: { flexDirection: "column", flexShrink: 0 }, children: [
|
|
20807
|
-
|
|
20817
|
+
mayOverflow(p.name ?? "", s.width) ? (
|
|
20818
|
+
// the board's layout sizes each column by its screen, so a name that may be wider than the
|
|
20819
|
+
// screen gets the screen's width and ends in "…" instead of widening the column. Only then:
|
|
20820
|
+
// the clip changes how the text is antialiased, and names that fit should render as before.
|
|
20821
|
+
/* @__PURE__ */ jsx3(Box, { style: { width: s.width, height: LABEL_H, fontSize: 16, fontWeight: 600, color: theme.text }, children: /* @__PURE__ */ jsx3("div", { style: { overflow: "hidden", whiteSpace: "nowrap", textOverflow: "ellipsis" }, children: p.name }) })
|
|
20822
|
+
) : /* @__PURE__ */ jsx3(Box, { style: { height: LABEL_H, fontSize: 16, fontWeight: 600, color: theme.text }, children: p.name ?? "" }),
|
|
20808
20823
|
/* @__PURE__ */ jsxs(
|
|
20809
20824
|
Box,
|
|
20810
20825
|
{
|
|
@@ -20823,21 +20838,21 @@ function Screen({ element, children }) {
|
|
|
20823
20838
|
children: [
|
|
20824
20839
|
s.chrome && s.device === "phone" ? /* @__PURE__ */ jsx3(StatusBar, {}) : null,
|
|
20825
20840
|
s.chrome && s.device === "desktop" ? /* @__PURE__ */ jsx3(BrowserBar, {}) : null,
|
|
20826
|
-
/* @__PURE__ */ jsxs(Box, { style: { position: "relative", flexGrow: 1, flexDirection: "column", overflow: "hidden" }, children: [
|
|
20841
|
+
/* @__PURE__ */ jsxs(Box, { style: { position: "relative", flexGrow: 1, flexBasis: "0px", flexDirection: "column", overflow: "hidden" }, children: [
|
|
20827
20842
|
top,
|
|
20828
|
-
/* @__PURE__ */ jsx3(
|
|
20843
|
+
/* @__PURE__ */ jsx3(Box, { style: { flexGrow: 1, flexBasis: "0px", flexDirection: "column", overflow: "hidden" }, children: /* @__PURE__ */ jsx3(
|
|
20829
20844
|
Box,
|
|
20830
20845
|
{
|
|
20831
20846
|
style: {
|
|
20832
20847
|
flexGrow: 1,
|
|
20848
|
+
flexShrink: 0,
|
|
20833
20849
|
flexDirection: "column",
|
|
20834
20850
|
padding: s.padding,
|
|
20835
|
-
gap: p.gap ?? 14
|
|
20836
|
-
overflow: "hidden"
|
|
20851
|
+
gap: p.gap ?? 14
|
|
20837
20852
|
},
|
|
20838
20853
|
children: withDir(body, "column")
|
|
20839
20854
|
}
|
|
20840
|
-
),
|
|
20855
|
+
) }),
|
|
20841
20856
|
bottom,
|
|
20842
20857
|
overlays.map((o2) => isValidElement(o2) ? cloneElement(o2, { aboveTabBar: bottom.length > 0 }) : o2),
|
|
20843
20858
|
p.__mark ? /* @__PURE__ */ jsx3(Marker, { color: p.__mark }) : null
|
|
@@ -836,6 +836,16 @@ function BrowserBar() {
|
|
|
836
836
|
}
|
|
837
837
|
);
|
|
838
838
|
}
|
|
839
|
+
function mayOverflow(name, width) {
|
|
840
|
+
let w = 0;
|
|
841
|
+
for (const ch of name) {
|
|
842
|
+
if (ch.charCodeAt(0) >= 128 || /[MWmw@%&]/.test(ch)) w += 16.5;
|
|
843
|
+
else if (/[A-Z]/.test(ch)) w += 12.5;
|
|
844
|
+
else if (/[ .,:;'!|il1jft()[\]{}"`-]/.test(ch)) w += 8.5;
|
|
845
|
+
else w += 11;
|
|
846
|
+
}
|
|
847
|
+
return w > width;
|
|
848
|
+
}
|
|
839
849
|
function Screen({ element, children }) {
|
|
840
850
|
const p = element.props;
|
|
841
851
|
const s = screenSize(p);
|
|
@@ -851,7 +861,12 @@ function Screen({ element, children }) {
|
|
|
851
861
|
else body.push(c);
|
|
852
862
|
}
|
|
853
863
|
return /* @__PURE__ */ jsxs(Box, { style: { flexDirection: "column", flexShrink: 0 }, children: [
|
|
854
|
-
|
|
864
|
+
mayOverflow(p.name ?? "", s.width) ? (
|
|
865
|
+
// the board's layout sizes each column by its screen, so a name that may be wider than the
|
|
866
|
+
// screen gets the screen's width and ends in "…" instead of widening the column. Only then:
|
|
867
|
+
// the clip changes how the text is antialiased, and names that fit should render as before.
|
|
868
|
+
/* @__PURE__ */ jsx(Box, { style: { width: s.width, height: LABEL_H, fontSize: 16, fontWeight: 600, color: theme.text }, children: /* @__PURE__ */ jsx("div", { style: { overflow: "hidden", whiteSpace: "nowrap", textOverflow: "ellipsis" }, children: p.name }) })
|
|
869
|
+
) : /* @__PURE__ */ jsx(Box, { style: { height: LABEL_H, fontSize: 16, fontWeight: 600, color: theme.text }, children: p.name ?? "" }),
|
|
855
870
|
/* @__PURE__ */ jsxs(
|
|
856
871
|
Box,
|
|
857
872
|
{
|
|
@@ -870,21 +885,21 @@ function Screen({ element, children }) {
|
|
|
870
885
|
children: [
|
|
871
886
|
s.chrome && s.device === "phone" ? /* @__PURE__ */ jsx(StatusBar, {}) : null,
|
|
872
887
|
s.chrome && s.device === "desktop" ? /* @__PURE__ */ jsx(BrowserBar, {}) : null,
|
|
873
|
-
/* @__PURE__ */ jsxs(Box, { style: { position: "relative", flexGrow: 1, flexDirection: "column", overflow: "hidden" }, children: [
|
|
888
|
+
/* @__PURE__ */ jsxs(Box, { style: { position: "relative", flexGrow: 1, flexBasis: "0px", flexDirection: "column", overflow: "hidden" }, children: [
|
|
874
889
|
top,
|
|
875
|
-
/* @__PURE__ */ jsx(
|
|
890
|
+
/* @__PURE__ */ jsx(Box, { style: { flexGrow: 1, flexBasis: "0px", flexDirection: "column", overflow: "hidden" }, children: /* @__PURE__ */ jsx(
|
|
876
891
|
Box,
|
|
877
892
|
{
|
|
878
893
|
style: {
|
|
879
894
|
flexGrow: 1,
|
|
895
|
+
flexShrink: 0,
|
|
880
896
|
flexDirection: "column",
|
|
881
897
|
padding: s.padding,
|
|
882
|
-
gap: p.gap ?? 14
|
|
883
|
-
overflow: "hidden"
|
|
898
|
+
gap: p.gap ?? 14
|
|
884
899
|
},
|
|
885
900
|
children: withDir(body, "column")
|
|
886
901
|
}
|
|
887
|
-
),
|
|
902
|
+
) }),
|
|
888
903
|
bottom,
|
|
889
904
|
overlays.map((o2) => isValidElement(o2) ? cloneElement(o2, { aboveTabBar: bottom.length > 0 }) : o2),
|
|
890
905
|
p.__mark ? /* @__PURE__ */ jsx(Marker, { color: p.__mark }) : null
|
package/dist/cli.js
CHANGED
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
languageData
|
|
3
|
-
} from "./chunks/chunk-
|
|
3
|
+
} from "./chunks/chunk-XWGHLFRX.js";
|
|
4
4
|
import {
|
|
5
5
|
DEVICES,
|
|
6
6
|
RENAMED_PROPS,
|
|
@@ -24,7 +24,7 @@ import {
|
|
|
24
24
|
upgradeSpec,
|
|
25
25
|
upgradeWireframe,
|
|
26
26
|
wireframePrompt
|
|
27
|
-
} from "./chunks/chunk-
|
|
27
|
+
} from "./chunks/chunk-BSE346UI.js";
|
|
28
28
|
|
|
29
29
|
// src/deflate.ts
|
|
30
30
|
import { deflateRawSync, inflateRawSync } from "node:zlib";
|
package/dist/playground/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
languageData
|
|
3
|
-
} from "../chunks/chunk-
|
|
3
|
+
} from "../chunks/chunk-XWGHLFRX.js";
|
|
4
4
|
import {
|
|
5
5
|
boardLayout,
|
|
6
6
|
compileWireframe,
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
renderWireframePng,
|
|
10
10
|
renderWireframeSvg,
|
|
11
11
|
wireframePrompt
|
|
12
|
-
} from "../chunks/chunk-
|
|
12
|
+
} from "../chunks/chunk-BSE346UI.js";
|
|
13
13
|
|
|
14
14
|
// src/playground/index.ts
|
|
15
15
|
import { createServer } from "node:http";
|