brane 0.1.1 → 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/AGENTS.md +367 -0
- package/CHANGELOG.md +66 -0
- package/CONTRIBUTING.md +101 -0
- package/DESIGN.md +405 -0
- package/Dockerfile +61 -0
- package/LICENSE +656 -17
- package/README.md +174 -2
- package/SECURITY.md +213 -0
- package/dist/absorb/brane-id.js +11 -0
- package/dist/absorb/chatgpt.js +161 -0
- package/dist/absorb/claude-code.js +504 -0
- package/dist/absorb/index.js +543 -0
- package/dist/absorb/organelle.js +345 -0
- package/dist/absorb/run.js +520 -0
- package/dist/absorb/sources.js +168 -0
- package/dist/absorb/types.js +3 -0
- package/dist/agent-env.js +45 -0
- package/dist/agents.js +1168 -0
- package/dist/claude-live-session.js +124 -0
- package/dist/cli.js +50 -0
- package/dist/codex-tool-log.js +144 -0
- package/dist/commands/absorb.js +291 -0
- package/dist/commands/attach.js +31 -0
- package/dist/commands/certify.js +62 -0
- package/dist/commands/config.js +133 -0
- package/dist/commands/cost.js +126 -0
- package/dist/commands/daemon.js +350 -0
- package/dist/commands/doctor.js +262 -0
- package/dist/commands/fold.js +95 -0
- package/dist/commands/list.js +89 -0
- package/dist/commands/mcp.js +84 -0
- package/dist/commands/meld.js +360 -0
- package/dist/commands/memory.js +215 -0
- package/dist/commands/revive.js +74 -0
- package/dist/commands/run.js +85 -0
- package/dist/commands/spawn.js +1101 -0
- package/dist/commands/token.js +154 -0
- package/dist/copilot-tool-log.js +145 -0
- package/dist/cost/pricing.js +116 -0
- package/dist/cost/reprice.js +136 -0
- package/dist/crush-tool-log.js +293 -0
- package/dist/daemon/agent-scope.js +116 -0
- package/dist/daemon/awaiting.js +54 -0
- package/dist/daemon/cert-manager.js +349 -0
- package/dist/daemon/client.js +262 -0
- package/dist/daemon/gated-routes.js +112 -0
- package/dist/daemon/keep-awake.js +103 -0
- package/dist/daemon/local-socket.js +145 -0
- package/dist/daemon/presence-gate.js +206 -0
- package/dist/daemon/server.js +6353 -0
- package/dist/daemon/signal-source.js +35 -0
- package/dist/daemon/tailscale-rebind.js +58 -0
- package/dist/daemon/token-ring.js +263 -0
- package/dist/daemon/token-routes.js +190 -0
- package/dist/db/events.js +152 -0
- package/dist/db/mentions.js +179 -0
- package/dist/db/schema.js +643 -0
- package/dist/db/tokens.js +624 -0
- package/dist/db/tool-uses.js +647 -0
- package/dist/diagram/index.js +85 -0
- package/dist/diagram/label.js +455 -0
- package/dist/diagram/layout.js +476 -0
- package/dist/diagram/links.js +50 -0
- package/dist/diagram/metrics.js +152 -0
- package/dist/diagram/parse.js +487 -0
- package/dist/diagram/svg.js +389 -0
- package/dist/diagram/types.js +9 -0
- package/dist/export.js +629 -0
- package/dist/files/archive-types.js +2 -0
- package/dist/files/archive.js +182 -0
- package/dist/files/browse.js +327 -0
- package/dist/files/bytes.js +162 -0
- package/dist/files/codecs.js +33 -0
- package/dist/files/find.js +212 -0
- package/dist/files/tar.js +223 -0
- package/dist/files/zip.js +209 -0
- package/dist/folds.js +72 -0
- package/dist/grammars/ebnf.js +92 -0
- package/dist/highlight.js +250 -0
- package/dist/ids.js +24 -0
- package/dist/markdown-diagnostics.js +233 -0
- package/dist/math.js +168 -0
- package/dist/mcp/server.js +886 -0
- package/dist/memory/embedder.js +145 -0
- package/dist/memory/embedding-worker.js +71 -0
- package/dist/memory/embeddings.js +91 -0
- package/dist/memory/index.js +232 -0
- package/dist/memory/storage.js +587 -0
- package/dist/memory/wiki.js +688 -0
- package/dist/memory/wordpiece.js +247 -0
- package/dist/mentions-scan.js +40 -0
- package/dist/presence.js +259 -0
- package/dist/session-affinity.js +185 -0
- package/dist/shared/api-request.js +124 -0
- package/dist/shared/app-origin.js +26 -0
- package/dist/shared/block-glyphs.js +114 -0
- package/dist/shared/diagram-css.js +17 -0
- package/dist/shared/export-selection.js +101 -0
- package/dist/shared/find-in-text.js +110 -0
- package/dist/shared/glow-hues.js +225 -0
- package/dist/shared/hex-dump.js +62 -0
- package/dist/shared/hex-pages.js +52 -0
- package/dist/shared/hit-drum.js +79 -0
- package/dist/shared/known-nids.js +58 -0
- package/dist/shared/list-scroll-keys.js +56 -0
- package/dist/shared/markdown-lists.js +72 -0
- package/dist/shared/markdown-math.js +485 -0
- package/dist/shared/markdown-mentions.js +79 -0
- package/dist/shared/markdown-nids.js +41 -0
- package/dist/shared/markdown-protocols.js +53 -0
- package/dist/shared/markdown-refs.js +123 -0
- package/dist/shared/membrane-invasion.js +342 -0
- package/dist/shared/membrane-motion.js +83 -0
- package/dist/shared/membrane-palette.js +253 -0
- package/dist/shared/membrane-terrain.js +212 -0
- package/dist/shared/message-selection.js +123 -0
- package/dist/shared/mo-glyph.js +57 -0
- package/dist/shared/narrative-format.js +237 -0
- package/dist/shared/narrative-menu.js +84 -0
- package/dist/shared/narrative-sort.js +47 -0
- package/dist/shared/narrative-status.js +52 -0
- package/dist/shared/no-pan.js +85 -0
- package/dist/shared/pane-layout.js +50 -0
- package/dist/shared/payload-inline.js +83 -0
- package/dist/shared/speaker-color.js +61 -0
- package/dist/shared/token-rotation.js +14 -0
- package/dist/shared/view-url.js +216 -0
- package/dist/shared/wiki-links.js +70 -0
- package/dist/shared/wiki-window.js +4 -0
- package/dist/tailscale.js +55 -0
- package/dist/test/absorb-claude.test.js +892 -0
- package/dist/test/agent-scope.test.js +193 -0
- package/dist/test/agents.test.js +55 -0
- package/dist/test/api-request.test.js +122 -0
- package/dist/test/app-host-literal.test.js +53 -0
- package/dist/test/awaiting-glow.test.js +54 -0
- package/dist/test/awaiting.test.js +122 -0
- package/dist/test/bind-token.test.js +88 -0
- package/dist/test/block-glyphs.test.js +68 -0
- package/dist/test/cert-manager.test.js +262 -0
- package/dist/test/claude-live-session.test.js +110 -0
- package/dist/test/codex-tool-log.test.js +568 -0
- package/dist/test/config.test.js +71 -0
- package/dist/test/copilot-tool-log.test.js +93 -0
- package/dist/test/cors-preflight.test.js +56 -0
- package/dist/test/cost-linger.test.js +31 -0
- package/dist/test/cost-pricing.test.js +72 -0
- package/dist/test/cost-reprice.test.js +139 -0
- package/dist/test/cost-sessions.test.js +301 -0
- package/dist/test/crush-tool-log.test.js +289 -0
- package/dist/test/daemon-client.test.js +109 -0
- package/dist/test/daemon-socket.test.js +293 -0
- package/dist/test/diagram-arrows.test.js +205 -0
- package/dist/test/diagram-export.test.js +210 -0
- package/dist/test/diagram-labels.test.js +313 -0
- package/dist/test/diagram-parse.test.js +191 -0
- package/dist/test/diagram-render.test.js +175 -0
- package/dist/test/diagram-review.test.js +288 -0
- package/dist/test/diagram-self-loop-labels.test.js +303 -0
- package/dist/test/embedder.test.js +84 -0
- package/dist/test/events.test.js +123 -0
- package/dist/test/export-selection.test.js +102 -0
- package/dist/test/export.test.js +505 -0
- package/dist/test/files-browse.test.js +393 -0
- package/dist/test/files-find.test.js +272 -0
- package/dist/test/find-in-text.test.js +86 -0
- package/dist/test/first-paint-viewport.test.js +59 -0
- package/dist/test/fixtures/embedding-golden.js +23 -0
- package/dist/test/fixtures/wordpiece-golden.js +167 -0
- package/dist/test/glow-hues.test.js +202 -0
- package/dist/test/harness-cost.test.js +423 -0
- package/dist/test/helpers.js +21 -0
- package/dist/test/hex-pages.test.js +68 -0
- package/dist/test/highlight.test.js +69 -0
- package/dist/test/hit-drum.test.js +71 -0
- package/dist/test/hook-config.test.js +299 -0
- package/dist/test/json-gzip.test.js +70 -0
- package/dist/test/key-routing.test.js +58 -0
- package/dist/test/known-nids.test.js +106 -0
- package/dist/test/link-preview.test.js +67 -0
- package/dist/test/list-scroll-keys.test.js +66 -0
- package/dist/test/listen-own-words.test.js +147 -0
- package/dist/test/listen-timeout.test.js +33 -0
- package/dist/test/markdown-diagnostics.test.js +121 -0
- package/dist/test/markdown-lists.test.js +182 -0
- package/dist/test/markdown-math.test.js +256 -0
- package/dist/test/markdown-nids.test.js +146 -0
- package/dist/test/markdown-protocols.test.js +139 -0
- package/dist/test/mcp-bridge-cancel.test.js +98 -0
- package/dist/test/mcp-bridge-reconnect.test.js +121 -0
- package/dist/test/mcp-narrative.test.js +193 -0
- package/dist/test/mcp.test.js +548 -0
- package/dist/test/meld.test.js +143 -0
- package/dist/test/membrane-invasion.test.js +482 -0
- package/dist/test/membrane-motion.test.js +101 -0
- package/dist/test/membrane-palette.test.js +166 -0
- package/dist/test/membrane-terrain.test.js +170 -0
- package/dist/test/memory-log.test.js +202 -0
- package/dist/test/mentions-e2e.test.js +261 -0
- package/dist/test/mentions-ledger.test.js +265 -0
- package/dist/test/mentions-listen.test.js +265 -0
- package/dist/test/mentions.test.js +238 -0
- package/dist/test/message-selection.test.js +114 -0
- package/dist/test/mo-glyph.test.js +22 -0
- package/dist/test/model-resolution.test.js +166 -0
- package/dist/test/narrate-validate.test.js +77 -0
- package/dist/test/narrate-wait.test.js +160 -0
- package/dist/test/narrative-format.test.js +219 -0
- package/dist/test/narrative-menu.test.js +150 -0
- package/dist/test/narrative-sort.test.js +75 -0
- package/dist/test/no-pan.test.js +60 -0
- package/dist/test/pane-layout.test.js +153 -0
- package/dist/test/paths.test.js +37 -0
- package/dist/test/payload-inline.test.js +91 -0
- package/dist/test/precache-fonts.test.js +72 -0
- package/dist/test/presence-gate.test.js +440 -0
- package/dist/test/presence.test.js +160 -0
- package/dist/test/read-events.test.js +220 -0
- package/dist/test/remote-meld.test.js +128 -0
- package/dist/test/revive-cli.test.js +93 -0
- package/dist/test/search-floor.test.js +206 -0
- package/dist/test/search-fusion.test.js +168 -0
- package/dist/test/search.test.js +205 -0
- package/dist/test/security.test.js +48 -0
- package/dist/test/selection-wiring.test.js +61 -0
- package/dist/test/session-affinity.test.js +295 -0
- package/dist/test/signal-source.test.js +32 -0
- package/dist/test/socket-account.test.js +268 -0
- package/dist/test/spawn-toml.test.js +72 -0
- package/dist/test/speaker-color.test.js +90 -0
- package/dist/test/tailscale-rebind.test.js +79 -0
- package/dist/test/terminal-cursor.test.js +61 -0
- package/dist/test/terminal-resync.test.js +23 -0
- package/dist/test/terminal-size.test.js +36 -0
- package/dist/test/thin-cli.test.js +382 -0
- package/dist/test/token-bootstrap.test.js +272 -0
- package/dist/test/token-devices.test.js +646 -0
- package/dist/test/token-lapse.test.js +94 -0
- package/dist/test/token-ring.test.js +188 -0
- package/dist/test/token-rotate-cli.test.js +58 -0
- package/dist/test/token-routes.test.js +95 -0
- package/dist/test/tool-uses.test.js +1006 -0
- package/dist/test/usage-ledger.test.js +126 -0
- package/dist/test/view-url.test.js +627 -0
- package/dist/test/wiki-links.test.js +57 -0
- package/dist/test/wiki-sources.test.js +85 -0
- package/dist/test/wiki-window.test.js +31 -0
- package/dist/test/wiki.test.js +399 -0
- package/dist/test/wordpiece.test.js +68 -0
- package/dist/test/wrangler-route.test.js +27 -0
- package/dist/tls.js +148 -0
- package/dist/tmux.js +213 -0
- package/dist/user-agent.js +46 -0
- package/docker-entrypoint.sh +154 -0
- package/docs/README.md +3 -0
- package/docs/absorb.md +231 -0
- package/docs/cost-ledger.md +125 -0
- package/docs/daemon-api.md +189 -0
- package/docs/diagrams.md +163 -0
- package/docs/doctor.md +36 -0
- package/docs/export-scope.md +69 -0
- package/docs/files-view.md +133 -0
- package/docs/link-previews.md +38 -0
- package/docs/math.md +32 -0
- package/docs/mcp.md +69 -0
- package/docs/memory-search.md +75 -0
- package/docs/mentions.md +101 -0
- package/docs/multi-brane.md +14 -0
- package/docs/passkeys.md +165 -0
- package/docs/presence.md +202 -0
- package/docs/releasing.md +104 -0
- package/docs/rendering-warnings.md +22 -0
- package/docs/request-layer.md +72 -0
- package/docs/selection-store.md +50 -0
- package/docs/session-affinity.md +61 -0
- package/docs/terminal-mirroring.md +243 -0
- package/docs/token-rotation.md +431 -0
- package/docs/view-urls.md +134 -0
- package/docs/web-app.md +23 -0
- package/docs/wiki.md +43 -0
- package/expo/dist/_expo/static/js/web/entry-e94247f119fdcd3b424a1764233a41d2.js +1099 -0
- package/expo/dist/apple-touch-icon.png +0 -0
- package/expo/dist/favicon.svg +3 -0
- package/expo/dist/index.html +177 -0
- package/expo/dist/katex/fonts/KaTeX_AMS-Regular.woff2 +0 -0
- package/expo/dist/katex/fonts/KaTeX_Caligraphic-Bold.woff2 +0 -0
- package/expo/dist/katex/fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
- package/expo/dist/katex/fonts/KaTeX_Fraktur-Bold.woff2 +0 -0
- package/expo/dist/katex/fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
- package/expo/dist/katex/fonts/KaTeX_Main-Bold.woff2 +0 -0
- package/expo/dist/katex/fonts/KaTeX_Main-BoldItalic.woff2 +0 -0
- package/expo/dist/katex/fonts/KaTeX_Main-Italic.woff2 +0 -0
- package/expo/dist/katex/fonts/KaTeX_Main-Regular.woff2 +0 -0
- package/expo/dist/katex/fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
- package/expo/dist/katex/fonts/KaTeX_Math-Italic.woff2 +0 -0
- package/expo/dist/katex/fonts/KaTeX_SansSerif-Bold.woff2 +0 -0
- package/expo/dist/katex/fonts/KaTeX_SansSerif-Italic.woff2 +0 -0
- package/expo/dist/katex/fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
- package/expo/dist/katex/fonts/KaTeX_Script-Regular.woff2 +0 -0
- package/expo/dist/katex/fonts/KaTeX_Size1-Regular.woff2 +0 -0
- package/expo/dist/katex/fonts/KaTeX_Size2-Regular.woff2 +0 -0
- package/expo/dist/katex/fonts/KaTeX_Size3-Regular.woff2 +0 -0
- package/expo/dist/katex/fonts/KaTeX_Size4-Regular.woff2 +0 -0
- package/expo/dist/katex/fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
- package/expo/dist/katex/katex.min.css +1 -0
- package/expo/dist/manifest.json +16 -0
- package/expo/dist/metadata.json +1 -0
- package/expo/dist/og/membrane.v1.png +0 -0
- package/expo/dist/pdf.min.mjs +28 -0
- package/expo/dist/pdf.worker.min.mjs +28 -0
- package/expo/dist/sw.js +154 -0
- package/package.json +79 -27
- package/protocols/LIVE_loop.md +407 -0
- package/protocols/bebrief.md +41 -0
- package/protocols/digest.md +231 -0
- package/protocols/fly.md +266 -0
- package/protocols/idle.md +81 -0
- package/protocols/keeper.md +829 -0
- package/protocols/markdown.md +80 -0
- package/protocols/memory.md +419 -0
- package/protocols/mentions.md +93 -0
- package/protocols/payloads.md +53 -0
- package/protocols/prelude.md +91 -0
- package/protocols/ramify.md +105 -0
- package/protocols/review.md +356 -0
- package/protocols/rrr.md +232 -0
- package/protocols/scribe.md +429 -0
- package/protocols/summarize.md +203 -0
- package/protocols/twin.md +174 -0
- package/protocols/wiki.md +438 -0
- package/protocols/worker.md +38 -0
- package/wiki/shared/append-only-event-log.md +148 -0
- package/wiki/shared/index.md +53 -0
- package/wiki/shared/owning-your-narratives.md +161 -0
- package/wiki/shared/premature-ontologies.md +435 -0
- package/wiki/shared/sedimentary-superintelligence.md +128 -0
- package/wiki/shared/sequestering-the-narrative.md +143 -0
- package/wiki/shared/why-wikis-work.md +216 -0
- package/.travis.yml +0 -7
- package/lib/brane.cjs.js +0 -12
- package/lib/brane.cjs.js.map +0 -1
- package/lib/brane.d.ts +0 -11
- package/lib/brane.js +0 -6
- package/lib/brane.js.map +0 -1
- package/lib/tests.d.ts +0 -1
- package/lib/tests.js +0 -8
- package/lib/tests.js.map +0 -1
- package/rollup.config.js +0 -21
- package/scripts/test.sh +0 -8
- package/src/brane.ts +0 -21
- package/src/tests.ts +0 -8
- package/tsconfig.json +0 -16
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/** ```mermaid source → SVG, or a refusal.
|
|
2
|
+
*
|
|
3
|
+
* The one entry point, and deliberately the *same* one for both targets: the
|
|
4
|
+
* app fetches it through `POST /api/diagram`, the HTML export calls it
|
|
5
|
+
* directly in its fence loop. That is the "one renderer, two targets" rule
|
|
6
|
+
* the KaTeX path already follows — an export is a snapshot of the live view
|
|
7
|
+
* rather than a lookalike of it.
|
|
8
|
+
*
|
|
9
|
+
* Unlike KaTeX it runs only on the daemon. Inline math is ubiquitous and
|
|
10
|
+
* light, so it is worth bundling; diagrams are rare and heavy, like the
|
|
11
|
+
* syntax highlighting that already lives behind `POST /api/highlight`.
|
|
12
|
+
*
|
|
13
|
+
* `null` is the fallback signal, and both call sites already handle it: the
|
|
14
|
+
* export substitutes only non-null results, and the app's code view renders
|
|
15
|
+
* plain when its fetch comes back empty. So a refused diagram is the
|
|
16
|
+
* highlighted code block a mermaid fence renders as today — no new path.
|
|
17
|
+
*/
|
|
18
|
+
import { createHash } from "node:crypto";
|
|
19
|
+
import { parseDiagram } from "./parse.js";
|
|
20
|
+
import { layoutDiagram } from "./layout.js";
|
|
21
|
+
import { renderSvg } from "./svg.js";
|
|
22
|
+
export { parseLabel, wrapRuns, MARKDOWN_WRAP_WIDTH } from "./label.js";
|
|
23
|
+
export { parseDiagram } from "./parse.js";
|
|
24
|
+
export { layoutDiagram, projectAttachment, faceOffset } from "./layout.js";
|
|
25
|
+
export { basisPath, renderSvg, esc } from "./svg.js";
|
|
26
|
+
export { textWidth, charWidth, runWidth, measureNode, measureEdgeLabel } from "./metrics.js";
|
|
27
|
+
/** A source larger than this is not a diagram anyone will read, and layout is
|
|
28
|
+
* synchronous on the daemon's only thread. */
|
|
29
|
+
export const MAX_SOURCE_BYTES = 24_000;
|
|
30
|
+
/** Likewise: dagre is O(V·E)-ish and a pathological graph must not stall the
|
|
31
|
+
* daemon. Both bounds refuse rather than truncate. */
|
|
32
|
+
export const MAX_NODES = 300;
|
|
33
|
+
export const MAX_EDGES = 600;
|
|
34
|
+
/** Parse once, and check the bounds against the result. `diagnoseDiagram` and
|
|
35
|
+
* `renderDiagram` both need exactly this, and calling the former from the
|
|
36
|
+
* latter parsed the same source twice on the rendering path. */
|
|
37
|
+
function parseWithin(source) {
|
|
38
|
+
if (source.length > MAX_SOURCE_BYTES) {
|
|
39
|
+
return { ok: false, refusal: { kind: "unsupported", message: `diagram source is over ${MAX_SOURCE_BYTES} bytes` } };
|
|
40
|
+
}
|
|
41
|
+
const parsed = parseDiagram(source);
|
|
42
|
+
if (!parsed.ok)
|
|
43
|
+
return { ok: false, refusal: parsed.refusal };
|
|
44
|
+
if (parsed.ir.nodes.length > MAX_NODES) {
|
|
45
|
+
return { ok: false, refusal: { kind: "unsupported", message: `diagram has more than ${MAX_NODES} nodes` } };
|
|
46
|
+
}
|
|
47
|
+
if (parsed.ir.edges.length > MAX_EDGES) {
|
|
48
|
+
return { ok: false, refusal: { kind: "unsupported", message: `diagram has more than ${MAX_EDGES} edges` } };
|
|
49
|
+
}
|
|
50
|
+
return { ok: true, ir: parsed.ir };
|
|
51
|
+
}
|
|
52
|
+
export function diagnoseDiagram(source) {
|
|
53
|
+
return inspectDiagram(source).refusal;
|
|
54
|
+
}
|
|
55
|
+
/** Everything `narrate` reports about one fence, from one parse: whether it
|
|
56
|
+
* draws at all, and if it does, which label markup draws as typed. */
|
|
57
|
+
export function inspectDiagram(source) {
|
|
58
|
+
const checked = parseWithin(source);
|
|
59
|
+
return checked.ok
|
|
60
|
+
? { refusal: null, literal: checked.ir.literal }
|
|
61
|
+
: { refusal: checked.refusal, literal: [] };
|
|
62
|
+
}
|
|
63
|
+
export function renderDiagram(source, opts = {}) {
|
|
64
|
+
const parsed = parseWithin(source);
|
|
65
|
+
if (!parsed.ok)
|
|
66
|
+
return null;
|
|
67
|
+
try {
|
|
68
|
+
const placed = layoutDiagram(parsed.ir);
|
|
69
|
+
if (!Number.isFinite(placed.width) || !Number.isFinite(placed.height) ||
|
|
70
|
+
placed.width <= 0 || placed.height <= 0) {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
const idSuffix = createHash("sha256").update(source).digest("hex").slice(0, 8);
|
|
74
|
+
return {
|
|
75
|
+
svg: renderSvg(placed, { hrefFor: opts.hrefFor, idSuffix, edgeHighlight: opts.edgeHighlight }),
|
|
76
|
+
labels: parsed.ir.nodes.map((n) => n.lines.join(" ")),
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
// dagre throws on graph shapes it cannot rank. A thrown layout is a
|
|
81
|
+
// fallback, never a 500 — the reader still gets the source.
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,455 @@
|
|
|
1
|
+
/** Label text → styled lines.
|
|
2
|
+
*
|
|
3
|
+
* Mermaid has two label grammars, and agents write both:
|
|
4
|
+
*
|
|
5
|
+
* - **Plain** (`A[text]`, `A["text"]`). Mermaid's HTML labels honour a few
|
|
6
|
+
* tags in here — `<br/>`, `<b>`, `<i>` and their kin — and nothing else:
|
|
7
|
+
* `**x**` in a plain label is two asterisks on GitHub too.
|
|
8
|
+
* - **Markdown strings** (``A["`text`"]``, mermaid ≥10). The backticks inside
|
|
9
|
+
* the quotes are the marker, not over-quoting: `**bold**` and `*italic*`
|
|
10
|
+
* render, a newline in the source is a line break, and mermaid wraps long
|
|
11
|
+
* lines to a fixed width.
|
|
12
|
+
*
|
|
13
|
+
* Before this module brane read only `<br/>` from either, so ``"`**Plan**`"``
|
|
14
|
+
* drew its backticks and asterisks as text (claude-cdouqlgt:19 on another
|
|
15
|
+
* daemon, where an agent tried three spellings of bold and got none).
|
|
16
|
+
*
|
|
17
|
+
* **Nothing here builds markup.** A label becomes runs of plain text with
|
|
18
|
+
* flags, and `svg.ts` escapes each run into a `<tspan>` whose attributes it
|
|
19
|
+
* writes itself. A `<script>` in a label is text, exactly as before.
|
|
20
|
+
*
|
|
21
|
+
* **One parse, never a second scanner.** A markdown string goes through
|
|
22
|
+
* `fromMarkdown`, the parser `markdown-diagnostics.ts` already uses, so what
|
|
23
|
+
* counts as bold here is what counts as bold everywhere else in brane. The
|
|
24
|
+
* plain grammar is a fixed set of tags, so a linear scanner over that set
|
|
25
|
+
* is its whole definition.
|
|
26
|
+
*
|
|
27
|
+
* **Tables are brane's own.** A node's markdown string may hold a GFM table,
|
|
28
|
+
* which mermaid shows as "Unsupported markdown". Brane draws it as a grid,
|
|
29
|
+
* as static SVG, so the HTML export carries no logic for it. In an edge
|
|
30
|
+
* label or a subgraph title there is no box for a grid, so there a table
|
|
31
|
+
* shows as its source and is reported.
|
|
32
|
+
*
|
|
33
|
+
* **What still shows as source is reported.** Each label returns `literal`:
|
|
34
|
+
* markup that will be drawn as the characters it is made of. `narrate`
|
|
35
|
+
* turns those into `diagram_label_literal` warnings, so an agent that writes
|
|
36
|
+
* `**x**` in a plain label learns to write a markdown string, instead of
|
|
37
|
+
* getting a clean receipt for a diagram showing asterisks.
|
|
38
|
+
*/
|
|
39
|
+
import { fromMarkdown } from "mdast-util-from-markdown";
|
|
40
|
+
import { gfmTable } from "micromark-extension-gfm-table";
|
|
41
|
+
import { gfmTableFromMarkdown } from "mdast-util-gfm-table";
|
|
42
|
+
import { FONT_SIZE, runWidth } from "./metrics.js";
|
|
43
|
+
export const isTable = (l) => !Array.isArray(l);
|
|
44
|
+
/** Line slots a label line takes up. */
|
|
45
|
+
export const slotsOf = (l) => (isTable(l) ? 1 + l.rows.length : 1);
|
|
46
|
+
/** A table as plain lines, for the places that can only hold text. */
|
|
47
|
+
export function tableText(t) {
|
|
48
|
+
return [t.header, ...t.rows].map((cells) => cells.map(lineText).join(" | "));
|
|
49
|
+
}
|
|
50
|
+
/** Label lines with every table flattened to text lines: for edge labels and
|
|
51
|
+
* subgraph titles, where a table is never drawn as one. */
|
|
52
|
+
export function textLines(lines) {
|
|
53
|
+
return lines.flatMap((l) => (isTable(l) ? tableText(l).map((text) => [{ text }]) : [l]));
|
|
54
|
+
}
|
|
55
|
+
/** Mermaid's `flowchart.wrappingWidth` (200px) at mermaid's 16px, scaled to
|
|
56
|
+
* brane's font size. Markdown strings wrap to it; plain labels never wrap,
|
|
57
|
+
* also as mermaid does. Scaled because it is the *em* width that decides
|
|
58
|
+
* where a line breaks: 200px at 14px would keep words together that GitHub
|
|
59
|
+
* splits (`planner picks the operations` is one line at 200px/14px and two
|
|
60
|
+
* on GitHub). */
|
|
61
|
+
export const MARKDOWN_WRAP_WIDTH = (200 * FONT_SIZE) / 16;
|
|
62
|
+
const TAGS = {
|
|
63
|
+
b: "bold", strong: "bold",
|
|
64
|
+
i: "italic", em: "italic",
|
|
65
|
+
code: "code",
|
|
66
|
+
s: "strike", del: "strike", strike: "strike",
|
|
67
|
+
u: "underline",
|
|
68
|
+
};
|
|
69
|
+
/** Every tag-shaped token: `<b>`, `</em>`, `<br/>`, `<br />`, `<foo bar>`. */
|
|
70
|
+
const TAG = /<(\/?)([A-Za-z][A-Za-z0-9]*)\b[^<>]*?(\/?)>/g;
|
|
71
|
+
/** A markdown string: ``"`…`"``, quotes and backticks wrapping the whole. */
|
|
72
|
+
const MD_STRING = /^"`([\s\S]*)`"$/;
|
|
73
|
+
/** `**x**`, `__x__` and `` `x` `` in a plain label are drawn as typed, here
|
|
74
|
+
* and in mermaid. Worth saying, because the fix is one character pair away. */
|
|
75
|
+
const MARKDOWN_IN_PLAIN = /\*\*[^*\s][^*]*\*\*|__[^_\s][^_]*__|`[^`\s][^`]*`/;
|
|
76
|
+
export function parseLabel(raw, opts = {}) {
|
|
77
|
+
const t = raw.trim();
|
|
78
|
+
const md = MD_STRING.exec(t);
|
|
79
|
+
return md ? parseMarkdownLabel(md[1], opts) : parsePlainLabel(t);
|
|
80
|
+
}
|
|
81
|
+
function plainLines(runs) {
|
|
82
|
+
return runs.flatMap((l) => (isTable(l) ? tableText(l) : [lineText(l)]));
|
|
83
|
+
}
|
|
84
|
+
/** A label that is just text — the node id, for one written without a label. */
|
|
85
|
+
export function plainLabel(text) {
|
|
86
|
+
return { lines: [text], runs: [[{ text }]], literal: [] };
|
|
87
|
+
}
|
|
88
|
+
// ── Plain labels ─────────────────────────────────────────────────────────────
|
|
89
|
+
function parsePlainLabel(raw) {
|
|
90
|
+
// One pair of wrapping quotes is label quoting, not text. The old splitter
|
|
91
|
+
// stripped a backtick pair as well; kept, since dropping it would put
|
|
92
|
+
// backticks into diagrams that drew without them yesterday.
|
|
93
|
+
const s = raw.replace(/^(["'`])([\s\S]*)\1$/, "$2");
|
|
94
|
+
const literal = [];
|
|
95
|
+
const b = new LineBuilder();
|
|
96
|
+
const style = {};
|
|
97
|
+
const depth = {};
|
|
98
|
+
const unknown = new Set();
|
|
99
|
+
let at = 0;
|
|
100
|
+
for (const m of s.matchAll(TAG)) {
|
|
101
|
+
b.text(decodeEntities(s.slice(at, m.index)), style);
|
|
102
|
+
at = m.index + m[0].length;
|
|
103
|
+
const [, close, nameRaw, selfClose] = m;
|
|
104
|
+
const name = nameRaw.toLowerCase();
|
|
105
|
+
if (name === "br") {
|
|
106
|
+
b.newline();
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
const flag = TAGS[name];
|
|
110
|
+
if (!flag || selfClose) {
|
|
111
|
+
b.text(m[0], style);
|
|
112
|
+
// Once per tag name: `<span>…</span>` is one mistake, not two.
|
|
113
|
+
if (!unknown.has(name)) {
|
|
114
|
+
unknown.add(name);
|
|
115
|
+
literal.push(`\`<${name}>\` is not a tag diagrams draw, so it shows as text`);
|
|
116
|
+
}
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
// Counted, so `<b><b>x</b> y</b>` keeps `y` bold, and a stray `</b>`
|
|
120
|
+
// closes nothing rather than going negative.
|
|
121
|
+
const d = (depth[flag] ?? 0) + (close ? -1 : 1);
|
|
122
|
+
depth[flag] = Math.max(0, d);
|
|
123
|
+
if (depth[flag])
|
|
124
|
+
style[flag] = true;
|
|
125
|
+
else
|
|
126
|
+
delete style[flag];
|
|
127
|
+
}
|
|
128
|
+
b.text(decodeEntities(s.slice(at)), style);
|
|
129
|
+
const out = b.finish(/* dropBlank */ true);
|
|
130
|
+
// Only text outside `<code>`: inside it, `**` is meant literally, and saying
|
|
131
|
+
// otherwise was a false warning.
|
|
132
|
+
const prose = out.runs.map((l) => (isTable(l) ? "" : l.filter((r) => !r.code).map((r) => r.text).join(""))).join("\n");
|
|
133
|
+
const md = MARKDOWN_IN_PLAIN.exec(prose);
|
|
134
|
+
if (md) {
|
|
135
|
+
literal.push(`\`${md[0]}\` shows as typed in a plain label. To style it, make the label a markdown string: backticks just inside the quotes, as in \`A["\`**bold**\`"]\``);
|
|
136
|
+
}
|
|
137
|
+
return { ...out, literal };
|
|
138
|
+
}
|
|
139
|
+
// ── Markdown strings ─────────────────────────────────────────────────────────
|
|
140
|
+
function parseMarkdownLabel(inner, opts) {
|
|
141
|
+
// Continuation lines carry the diagram's indentation, which in markdown
|
|
142
|
+
// would read as an indented code block. Mermaid dedents; trimming each line
|
|
143
|
+
// is the same thing for text that is never meant to be indented.
|
|
144
|
+
const src = inner.split("\n").map((l) => l.trim()).join("\n");
|
|
145
|
+
const literal = [];
|
|
146
|
+
// Swapped while a table cell is read, so the inline walk below fills the
|
|
147
|
+
// cell rather than the label.
|
|
148
|
+
let b = new LineBuilder();
|
|
149
|
+
let tree;
|
|
150
|
+
try {
|
|
151
|
+
// The table extension is loaded whether or not tables are drawn: without
|
|
152
|
+
// it a table parses as paragraphs of pipes, which draw as typed and could
|
|
153
|
+
// not be reported, since nothing would know they were a table.
|
|
154
|
+
tree = fromMarkdown(src, { extensions: [gfmTable()], mdastExtensions: [gfmTableFromMarkdown()] });
|
|
155
|
+
}
|
|
156
|
+
catch {
|
|
157
|
+
b.text(src, {});
|
|
158
|
+
return { ...b.finish(true), literal: ["the markdown string could not be parsed, so it shows as typed"] };
|
|
159
|
+
}
|
|
160
|
+
const sourceOf = (node) => {
|
|
161
|
+
const s = node?.position?.start?.offset, e = node?.position?.end?.offset;
|
|
162
|
+
return typeof s === "number" && typeof e === "number" ? src.slice(s, e) : "";
|
|
163
|
+
};
|
|
164
|
+
const tagStyle = {};
|
|
165
|
+
const inline = (node, style) => {
|
|
166
|
+
const eff = { ...style, ...tagStyle };
|
|
167
|
+
switch (node.type) {
|
|
168
|
+
case "text":
|
|
169
|
+
b.text(node.value, eff);
|
|
170
|
+
return;
|
|
171
|
+
case "break":
|
|
172
|
+
b.newline();
|
|
173
|
+
return;
|
|
174
|
+
case "strong":
|
|
175
|
+
for (const c of node.children)
|
|
176
|
+
inline(c, { ...style, bold: true });
|
|
177
|
+
return;
|
|
178
|
+
case "emphasis":
|
|
179
|
+
for (const c of node.children)
|
|
180
|
+
inline(c, { ...style, italic: true });
|
|
181
|
+
return;
|
|
182
|
+
case "inlineCode":
|
|
183
|
+
b.text(node.value, { ...eff, code: true });
|
|
184
|
+
return;
|
|
185
|
+
case "html": {
|
|
186
|
+
// Inline HTML arrives one tag per node. The tags plain labels honour
|
|
187
|
+
// mean the same here; anything else is shown and reported.
|
|
188
|
+
const m = /^<(\/?)([A-Za-z][A-Za-z0-9]*)\b[^<>]*?(\/?)>$/.exec(node.value);
|
|
189
|
+
const name = m?.[2].toLowerCase();
|
|
190
|
+
if (name === "br") {
|
|
191
|
+
b.newline();
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
const flag = name ? TAGS[name] : undefined;
|
|
195
|
+
if (m && flag && !m[3]) {
|
|
196
|
+
if (m[1])
|
|
197
|
+
delete tagStyle[flag];
|
|
198
|
+
else
|
|
199
|
+
tagStyle[flag] = true;
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
b.text(node.value, eff);
|
|
203
|
+
literal.push(`\`${clip(node.value)}\` is not something diagrams draw, so it shows as text`);
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
case "link":
|
|
207
|
+
case "linkReference":
|
|
208
|
+
for (const c of node.children ?? [])
|
|
209
|
+
inline(c, style);
|
|
210
|
+
literal.push(`links in diagram labels are drawn as their text and are not followed`);
|
|
211
|
+
return;
|
|
212
|
+
default: {
|
|
213
|
+
const text = sourceOf(node);
|
|
214
|
+
if (text) {
|
|
215
|
+
b.text(text, eff);
|
|
216
|
+
literal.push(`\`${clip(text)}\` is markdown diagrams do not draw, so it shows as typed`);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
const block = (node, prefix) => {
|
|
222
|
+
switch (node.type) {
|
|
223
|
+
case "root":
|
|
224
|
+
case "blockquote":
|
|
225
|
+
node.children.forEach((c) => block(c, prefix));
|
|
226
|
+
return;
|
|
227
|
+
case "paragraph":
|
|
228
|
+
case "heading":
|
|
229
|
+
b.startBlock();
|
|
230
|
+
if (prefix)
|
|
231
|
+
b.text(prefix, {});
|
|
232
|
+
for (const c of node.children)
|
|
233
|
+
inline(c, node.type === "heading" ? { bold: true } : {});
|
|
234
|
+
return;
|
|
235
|
+
case "list":
|
|
236
|
+
node.children.forEach((item, i) => {
|
|
237
|
+
const marker = node.ordered ? `${(node.start ?? 1) + i}. ` : "• ";
|
|
238
|
+
(item.children ?? []).forEach((c, j) => block(c, j === 0 ? marker : ""));
|
|
239
|
+
});
|
|
240
|
+
return;
|
|
241
|
+
case "table": {
|
|
242
|
+
if (!opts.tables) {
|
|
243
|
+
literalBlock(node, "tables are drawn only in node labels, so this one");
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
const cellRuns = (cell, header) => {
|
|
247
|
+
const outer = b;
|
|
248
|
+
b = new LineBuilder();
|
|
249
|
+
for (const c of cell.children ?? [])
|
|
250
|
+
inline(c, header ? { bold: true } : {});
|
|
251
|
+
// A cell is one line: a `<br>` inside one becomes a space.
|
|
252
|
+
const line = textLines(b.finish(true).runs).reduce((acc, l, i) => (i ? [...acc, { text: " " }, ...l] : l), []);
|
|
253
|
+
b = outer;
|
|
254
|
+
return line;
|
|
255
|
+
};
|
|
256
|
+
const [head, ...body] = node.children ?? [];
|
|
257
|
+
const width = head?.children?.length ?? 0;
|
|
258
|
+
// GFM pads short rows with empty cells and drops extra ones.
|
|
259
|
+
const row = (r, header) => Array.from({ length: width }, (_, i) => (r.children?.[i] ? cellRuns(r.children[i], header) : []));
|
|
260
|
+
b.startBlock();
|
|
261
|
+
b.table({
|
|
262
|
+
table: true,
|
|
263
|
+
align: Array.from({ length: width }, (_, i) => node.align?.[i] ?? null),
|
|
264
|
+
header: row(head, true),
|
|
265
|
+
rows: body.map((r) => row(r, false)),
|
|
266
|
+
});
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
case "code":
|
|
270
|
+
b.startBlock();
|
|
271
|
+
node.value.split("\n").forEach((l, i) => {
|
|
272
|
+
if (i)
|
|
273
|
+
b.newline();
|
|
274
|
+
b.text(l, { code: true });
|
|
275
|
+
});
|
|
276
|
+
return;
|
|
277
|
+
default:
|
|
278
|
+
literalBlock(node);
|
|
279
|
+
}
|
|
280
|
+
};
|
|
281
|
+
const literalBlock = (node, what) => {
|
|
282
|
+
const text = sourceOf(node);
|
|
283
|
+
if (!text)
|
|
284
|
+
return;
|
|
285
|
+
b.startBlock();
|
|
286
|
+
b.text(text, {});
|
|
287
|
+
literal.push(what
|
|
288
|
+
? `${what} shows as typed`
|
|
289
|
+
: `\`${clip(text)}\` is markdown diagrams do not draw, so it shows as typed`);
|
|
290
|
+
};
|
|
291
|
+
block(tree, "");
|
|
292
|
+
const out = b.finish(/* dropBlank */ true);
|
|
293
|
+
const runs = out.runs.flatMap((line) => (isTable(line) ? [line] : wrapRuns(line, MARKDOWN_WRAP_WIDTH)));
|
|
294
|
+
return { lines: plainLines(runs), runs, literal };
|
|
295
|
+
}
|
|
296
|
+
// ── Lines ────────────────────────────────────────────────────────────────────
|
|
297
|
+
/** Accumulates runs into lines, merging adjacent runs of the same style and
|
|
298
|
+
* splitting text on its own newlines (a markdown soft break is a `\n` inside
|
|
299
|
+
* a text node, and in a markdown string it is a line break). */
|
|
300
|
+
class LineBuilder {
|
|
301
|
+
lines = [[]];
|
|
302
|
+
blocks = 0;
|
|
303
|
+
text(s, style) {
|
|
304
|
+
if (!s)
|
|
305
|
+
return;
|
|
306
|
+
s.split("\n").forEach((part, i) => {
|
|
307
|
+
if (i)
|
|
308
|
+
this.newline();
|
|
309
|
+
if (part)
|
|
310
|
+
this.push({ text: part, ...clean(style) });
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
newline() { this.lines.push([]); }
|
|
314
|
+
/** A new paragraph, heading or list item starts on its own line. */
|
|
315
|
+
startBlock() {
|
|
316
|
+
if (this.blocks++ > 0)
|
|
317
|
+
this.newline();
|
|
318
|
+
}
|
|
319
|
+
/** A table takes the current line if it is empty, and nothing after it
|
|
320
|
+
* shares its slots. */
|
|
321
|
+
table(t) {
|
|
322
|
+
const cur = this.lines[this.lines.length - 1];
|
|
323
|
+
if (!isTable(cur) && cur.length === 0)
|
|
324
|
+
this.lines[this.lines.length - 1] = t;
|
|
325
|
+
else
|
|
326
|
+
this.lines.push(t);
|
|
327
|
+
this.newline();
|
|
328
|
+
}
|
|
329
|
+
push(run) {
|
|
330
|
+
let line = this.lines[this.lines.length - 1];
|
|
331
|
+
if (isTable(line)) {
|
|
332
|
+
line = [];
|
|
333
|
+
this.lines.push(line);
|
|
334
|
+
}
|
|
335
|
+
const last = line[line.length - 1];
|
|
336
|
+
if (last && sameStyle(last, run))
|
|
337
|
+
last.text += run.text;
|
|
338
|
+
else
|
|
339
|
+
line.push(run);
|
|
340
|
+
}
|
|
341
|
+
finish(dropBlank) {
|
|
342
|
+
let runs = this.lines.map((l) => (isTable(l) ? l : trimLine(l)));
|
|
343
|
+
// `A<br/><br/>B` drew as two lines before, not three; an empty label
|
|
344
|
+
// still draws as one empty line.
|
|
345
|
+
if (dropBlank) {
|
|
346
|
+
const kept = runs.filter((l) => isTable(l) || l.length > 0);
|
|
347
|
+
runs = kept.length ? kept : [[]];
|
|
348
|
+
}
|
|
349
|
+
return { lines: plainLines(runs), runs };
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
const FLAGS = ["bold", "italic", "code", "strike", "underline"];
|
|
353
|
+
function clean(style) {
|
|
354
|
+
const out = {};
|
|
355
|
+
for (const f of FLAGS)
|
|
356
|
+
if (style[f])
|
|
357
|
+
out[f] = true;
|
|
358
|
+
return out;
|
|
359
|
+
}
|
|
360
|
+
function sameStyle(a, b) {
|
|
361
|
+
return FLAGS.every((f) => !!a[f] === !!b[f]);
|
|
362
|
+
}
|
|
363
|
+
/** Leading and trailing whitespace of the *line*, not of each run: the space
|
|
364
|
+
* in `**Plan** mixin` belongs to the second run and must stay. */
|
|
365
|
+
function trimLine(line) {
|
|
366
|
+
const out = line.map((r) => ({ ...r }));
|
|
367
|
+
while (out.length && !(out[0].text = out[0].text.replace(/^\s+/, "")))
|
|
368
|
+
out.shift();
|
|
369
|
+
while (out.length && !(out[out.length - 1].text = out[out.length - 1].text.replace(/\s+$/, "")))
|
|
370
|
+
out.pop();
|
|
371
|
+
return out;
|
|
372
|
+
}
|
|
373
|
+
export function lineText(line) {
|
|
374
|
+
return line.map((r) => r.text).join("");
|
|
375
|
+
}
|
|
376
|
+
/** Greedy word wrap of one line of runs to `max` px.
|
|
377
|
+
*
|
|
378
|
+
* Words are split at spaces and never broken, so a word longer than the
|
|
379
|
+
* width overflows as it does in mermaid. A run boundary inside a word
|
|
380
|
+
* (`**Plan**s`) is not a break opportunity. */
|
|
381
|
+
export function wrapRuns(line, max, fontSize = FONT_SIZE) {
|
|
382
|
+
const pieces = [];
|
|
383
|
+
for (const r of line) {
|
|
384
|
+
for (const m of r.text.matchAll(/\s+|\S+/g)) {
|
|
385
|
+
pieces.push({ run: { ...r, text: m[0] }, space: /^\s/.test(m[0]) });
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
const words = [];
|
|
389
|
+
for (const p of pieces) {
|
|
390
|
+
const prev = words[words.length - 1];
|
|
391
|
+
if (!prev || p.space || prev[0].space)
|
|
392
|
+
words.push([p]);
|
|
393
|
+
else
|
|
394
|
+
prev.push(p);
|
|
395
|
+
}
|
|
396
|
+
const width = (ps) => ps.reduce((w, p) => w + runWidth(p.run, fontSize), 0);
|
|
397
|
+
const out = [[]];
|
|
398
|
+
let w = 0;
|
|
399
|
+
for (const word of words) {
|
|
400
|
+
const cur = out[out.length - 1];
|
|
401
|
+
if (word[0].space) {
|
|
402
|
+
if (cur.length) {
|
|
403
|
+
cur.push(...word);
|
|
404
|
+
w += width(word);
|
|
405
|
+
}
|
|
406
|
+
continue;
|
|
407
|
+
}
|
|
408
|
+
const ww = width(word);
|
|
409
|
+
if (cur.length && w + ww > max) {
|
|
410
|
+
if (cur[cur.length - 1].space)
|
|
411
|
+
cur.pop();
|
|
412
|
+
out.push([...word]);
|
|
413
|
+
w = ww;
|
|
414
|
+
}
|
|
415
|
+
else {
|
|
416
|
+
cur.push(...word);
|
|
417
|
+
w += ww;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
return out.map((ps) => {
|
|
421
|
+
const merged = [];
|
|
422
|
+
for (const p of ps) {
|
|
423
|
+
const last = merged[merged.length - 1];
|
|
424
|
+
if (last && sameStyle(last, p.run))
|
|
425
|
+
last.text += p.run.text;
|
|
426
|
+
else
|
|
427
|
+
merged.push({ ...p.run });
|
|
428
|
+
}
|
|
429
|
+
return trimLine(merged);
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
function clip(s, max = 40) {
|
|
433
|
+
const one = s.replace(/\s+/g, " ");
|
|
434
|
+
return one.length > max ? one.slice(0, max - 1) + "…" : one;
|
|
435
|
+
}
|
|
436
|
+
/** The character references a label is likely to hold. Mermaid's HTML labels
|
|
437
|
+
* decode them, so `A[x < y]` reads `x < y` there; brane drew the entity.
|
|
438
|
+
* Decoded after tags are scanned, so a decoded `<` is never read as one. */
|
|
439
|
+
const NAMED = {
|
|
440
|
+
amp: "&", lt: "<", gt: ">", quot: '"', apos: "'", nbsp: "\u00a0",
|
|
441
|
+
ndash: "–", mdash: "—", hellip: "…", rarr: "→", larr: "←", harr: "↔",
|
|
442
|
+
le: "≤", ge: "≥", ne: "≠", times: "×", middot: "·", bull: "•", deg: "°",
|
|
443
|
+
};
|
|
444
|
+
function decodeEntities(s) {
|
|
445
|
+
if (!s.includes("&"))
|
|
446
|
+
return s;
|
|
447
|
+
return s.replace(/&(#x[0-9a-f]{1,6}|#[0-9]{1,7}|[a-z]{2,8});/gi, (whole, ref) => {
|
|
448
|
+
if (ref[0] === "#") {
|
|
449
|
+
const cp = ref[1] === "x" || ref[1] === "X" ? parseInt(ref.slice(2), 16) : parseInt(ref.slice(1), 10);
|
|
450
|
+
return cp > 0 && cp <= 0x10ffff && !(cp >= 0xd800 && cp <= 0xdfff) ? String.fromCodePoint(cp) : whole;
|
|
451
|
+
}
|
|
452
|
+
return NAMED[ref.toLowerCase()] ?? whole;
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
//# sourceMappingURL=label.js.map
|