Compare commits

..
5 Commits
Author SHA1 Message Date
dela 0ba01fe5e2 fix: align lint role-before-use check with siblings
supertensor lint.py now rejects role= used before \stsetrole, matching
superfig and superderive lint behavior. Previously this was only caught
at TeX compile time by \stcheckrole.
2026-08-22 10:13:49 +08:00
dela 7210f890ca fix: \stsignature derives node name from anchor (#2-sig)
Align with superfig/superderive convention. Two signatures in one
figure can now coexist without colliding on the hardcoded name.
2026-08-21 14:56:52 +08:00
dela 8ada8e57c5 docs: route architecture, rewrites, and paper notes away
Send block diagrams to superfig, rewrite figures to superderive,
and full paper notes to superpaper.
2026-08-17 10:23:11 +08:00
dela 7d62e0279a docs: note SuperPaper as the family parent
Point this toolkit at the SuperPaper parent so the two figure
repositories stay siblings under one orchestration repo.
2026-08-17 09:40:12 +08:00
dela de917a2fbd Harden \stgroup and tighten the callout budget (review follow-up)
- Bracket ink is part of the fit: \st@facebody drops -inkw/-inke extreme
  coordinates and \stface/\ststack register them with the enclosing
  group/col/row fit, so a group outline can no longer be crossed by a
  member's bracket arms
- \stlink inside \stgroup or \stcol is now a package error: sub-flow
  members never terminate a pending connector, so the arrow was dropped
  silently while the label still rendered
- \stgroup requires role= (explicit role=neutral for mixed groups) and
  must bind at least two members or one \stcol partition; a lone stack
  or face inside a group is a dirty-build warning
- lint: default budget is one \stcallout per figure; the
  allow-multiple-callouts directive relaxes it to one per band
- build.sh: clean-build hint no longer names hue budget (lint owns it)
- tests/group-callout.tex reworked: multi-member group with a bracketed
  member as a regression probe, single callout; new negative fixtures
  group-link, group-norole, group-single, callout-budget
- api.md, checklist.md, style.md, layout.md, SKILL.md updated to match
2026-08-05 17:03:14 +08:00
14 changed files with 196 additions and 56 deletions
+5
View File
@@ -4,6 +4,11 @@ A shape-aware toolkit for drawing tensor formulas: a LaTeX/TikZ macro package, a
pipeline that fails on silent corruption, worked examples, and an agent skill that ties
them together.
This repository is a **child** of SuperPaper, the family parent that
routes paper notes to the right figure toolkit. Clone SuperPaper with
`--recurse-submodules` for the whole family; use this directory alone
when you only need shape-aware tensor figures.
It exists because figures of this kind fail in a specific way — they compile, they look
clean, and they tell the reader something false. A face captioned `Kᵀ` that was never
transposed; shards that do not tile their parent; an index tensor drawn with a lightness
+2 -2
View File
@@ -1,6 +1,6 @@
---
name: supertensor
description: Create or refine clean, shape-aware figures for tensor/matrix/vector formulas or tensor code — matrix-block diagrams, entry heatmaps, row/column shard stripes, stacked 3D/4D tensors, attention, tensor/expert parallelism, broadcasting, reductions, contractions, gather/scatter and routing. Use whenever tensor shapes or axis meanings must be visually aligned with the computation. Not for plotting numeric data (loss curves, benchmark bars, scatter plots), architecture block diagrams without shapes, or generic flowcharts.
description: Create or refine clean, shape-aware figures for tensor/matrix/vector formulas or tensor code — matrix-block diagrams, entry heatmaps, row/column shard stripes, stacked 3D/4D tensors, attention, tensor/expert parallelism, broadcasting, reductions, contractions, gather/scatter and routing. Use whenever tensor shapes or axis meanings must be visually aligned with the computation, or the user runs $supertensor. Not for plotting numeric data (loss curves, benchmark bars, scatter plots), architecture block diagrams without shapes (use superfig), stepwise rewrite figures (use superderive), or full paper notes (use superpaper).
---
# supertensor
@@ -52,7 +52,7 @@ shape or convention you inferred.
| data movement, collectives, non-linear ops | arrows and nodes | `\stlink`, `\stcomm` |
| a split along the contracted axis | a vertical pair inside one band slot | `\stcol` … `\stcolend` |
| adjacent objects that are one composite (heads of `q`, shards of `W`) | an outline in their own role hue | `\stgroup` … `\stgroupend` |
| an aside about a whole band | a side card off its right edge, one per band | `\stcallout` |
| an aside about a whole band | a side card off its right edge, one per figure | `\stcallout` |
Combine grammars only when each one adds information. Known zeros stay unfilled; masks,
diagonals, sparsity and partitions must encode their exact structure.
+54 -3
View File
@@ -146,6 +146,8 @@
\newif\ifst@incol
\newif\ifst@ingroup
\newif\ifst@first
\newcount\st@gcount % members registered by the open group
\newif\ifst@ghascol % the open group contains a \stcol partition
\newcommand{\stlayoutreset}{%
\global\st@railx=0pt \global\st@ycur=0pt
@@ -176,11 +178,21 @@
\xdef\st@collist{\st@collist(#1)}\st@regall{#1}%
\else\ifst@ingroup
\xdef\st@grouplist{\st@grouplist(#1)}\st@regall{#1}%
\global\advance\st@gcount by 1
\else
\xdef\st@rowlist{\st@rowlist(#1)}\st@regall{#1}%
\st@drawpendinglink{#1}%
\gdef\st@lastnode{#1}%
\fi\fi}
% Bracket arms are drawn OUTSIDE the node box, so a fit over the node alone is
% smaller than the ink. Register the bracket's extreme coordinates with
% whatever fit will enclose the node, or a group outline (pad 1.6mm, overhang
% 2.2mm) is crossed by the bracket it claims to contain.
\newcommand{\st@regink}[1]{%
\ifst@incol \xdef\st@collist{\st@collist#1}%
\else\ifst@ingroup \xdef\st@grouplist{\st@grouplist#1}%
\else \xdef\st@rowlist{\st@rowlist#1}%
\fi\fi}
\newcommand{\st@needrow}[1]{%
\ifst@inrow\else
@@ -294,6 +306,7 @@
Fix the height in \string\stcol}%
\fi
\st@checkh{\st@colname}{\the\dimen0}%
\ifst@ingroup\global\st@ghascoltrue\fi
\st@regrow{\st@colname}}}
% A node in the flow: an operator glyph, a collective, a note. It reserves its
@@ -324,6 +337,19 @@
% right-hand one exists, so a label can never be wider than its connector.
\newcommand{\stlink}[2]{%
\st@needrow{\stlink}%
% Sub-flow members never terminate a pending connector (\st@regrow), so a
% link opened inside one would build cleanly and draw no arrow at all.
\ifst@ingroup
\PackageError{supertensor}{\string\stlink\space inside \string\stgroup}%
{A group member cannot terminate a connector, so the arrow would be
dropped silently. Close the group first; the link then attaches to the
outline itself.}%
\fi
\ifst@incol
\PackageError{supertensor}{\string\stlink\space inside \string\stcol}%
{A column item cannot terminate a connector. Close the column first;
the link then attaches to the column as a whole.}%
\fi
\xdef\st@linkprev{\st@lastnode}%
\ifblank{#2}%
{\gdef\st@linklabel{}\stgap{\stlinklen}}%
@@ -507,6 +533,7 @@
\st@flowbegin{#2}{\st@tw}{\st@h}%
\edef\st@do{\noexpand\st@facecore{#2}{\st@pos}}\st@do
\st@regrow{#2}%
\ifst@bracket\st@regink{(#2-inkw)(#2-inke)}\fi
}{\st@facecore{#2}{#3}}%
\endgroup}
@@ -587,6 +614,10 @@
\draw[black!55, line width=0.5pt]
($(#1.north east)+(1.1mm,0.6mm)$) -- ++(1.1mm,0)
-- ($(#1.south east)+(2.2mm,-0.6mm)$) -- ++(-1.1mm,0);
% The true ink extent, for \st@regink: fits over the bare node undershoot
% the bracket by 2.2mm horizontally and 0.6mm vertically.
\coordinate (#1-inkw) at ($(#1.south west)+(-2.2mm,-0.6mm)$);
\coordinate (#1-inke) at ($(#1.north east)+(2.2mm,0.6mm)$);
\fi}
% \st@tile{face}{row}{col}{level} -- one rounded tile with a white gutter.
@@ -633,7 +664,8 @@
\node[inner sep=0pt, outer sep=0pt,
fit={(#2-front) ($(#2-front.north east)+(\st@back*\st@dx,\st@back*\st@dx)$)}]
(#2) {};
\ifblank{#3}{\st@regrow{#2}}{}%
\ifblank{#3}{\st@regrow{#2}%
\ifst@bracket\st@regink{(#2-front-inkw)(#2-front-inke)}\fi}{}%
\endgroup}
% ------------------------------------------------------------- grouping ----
@@ -657,7 +689,10 @@
/st/group/.cd,
role/.store in=\st@grole,
pad/.store in=\st@gpad,
role=neutral, pad=\stgrouppad,
% No default role on purpose: a homogeneous group silently drawn in neutral
% gray violates the "outline in the composite's own hue" rule without any
% signal. A genuinely mixed group passes role=neutral explicitly.
role={}, pad=\stgrouppad,
}
\newcommand{\stgroup}[2][]{%
\st@needrow{\stgroup}%
@@ -671,6 +706,11 @@
the whole \string\stcol\space in the group instead.}%
\fi
\pgfkeys{/st/group/.cd,#1}%
\ifdefempty{\st@grole}{%
\PackageError{supertensor}{\string\stgroup\space `#2' has no role}%
{The outline is drawn in the hue of the composite it names. Pass
role=<declared role>, or role=neutral explicitly for a mixed group.}%
\def\st@grole{neutral}}{}%
\stcheckrole{\st@grole}%
% Snapshot everything the closing macro needs: the keys are re-read by the
% next face and the role ledger lookup must not be deferred.
@@ -680,6 +720,7 @@
\st@leadgap{\stgutter}%
\global\advance\st@cx by \dimexpr\st@gpad\relax
\gdef\st@grouplist{}%
\global\st@gcount=0 \global\st@ghascolfalse
\global\st@ingrouptrue\global\st@firsttrue}
\newcommand{\stgroupend}{%
@@ -693,6 +734,16 @@
fit={\st@grouplist}] (\st@gname) {};}\st@do
\end{pgfonlayer}
\global\st@firstfalse
% A group must add information the members do not already carry: at least
% two adjacent objects, or one \stcol partition. Around a single face or
% stack the outline is decoration -- the stack already reads as one thing.
\ifnum\st@gcount<2
\ifst@ghascol\else
\PackageWarning{supertensor}{Group `\st@gname' wraps a single object.
Bind at least two members or one \string\stcol\space partition;
a lone stack or face already reads as one composite}%
\fi
\fi
% No \st@checkh: the overhang is 2*pad by construction, not driven by
% content, and the members were already checked against the band. Warning
% about it would only teach authors to inflate the declared band height.
@@ -787,6 +838,6 @@
% ------------------------------------------------------------- signature ----
% Optional centered subject line, outside the meaning box, low contrast.
\newcommand{\stsignature}[2]{%
\node[below=2.2mm of #2, font=\scriptsize, text=black!45] (st-signature) {#1};}
\node[below=2.2mm of #2, font=\scriptsize, text=black!45] (#2-sig) {#1};}
\endinput
+24 -13
View File
@@ -10,8 +10,8 @@ does not need to be installed into your texmf tree.
The build first runs `scripts/lint.py`. It rejects ledger changes, untracked absolute
objects, repeated anonymous dimensions, raw TikZ rectangles, a formula placed before the
last row, an unclosed `\stgroup`, a callout anchored to anything other than a band or a
second callout on one band, and more than four active hue families per row. Intentional galleries/tests may
last row, an unclosed `\stgroup`, a callout anchored to anything other than a band, more
than one callout in the figure, and more than four active hue families per row. Intentional galleries/tests may
put `% supertensor-lint: allow-absolute, allow-missing-formula` near the top; do not add an
exemption to a deliverable merely to make it pass.
@@ -105,10 +105,12 @@ but wrap hand-placed nodes in `\sttrack` so the cursor knows about them.
role hue around whatever is placed between them, naming those objects as one composite:
```tex
\stgroup[role=q]{qg} % keys: role= (hue), pad= (default \stgrouppad)
\ststack[role=q]{qh}{}{T}{dh}{3} % "these three sheets are q"
\stgroup[role=q]{qg} % keys: role= (REQUIRED, hue), pad= (default \stgrouppad)
\stface[role=q]{q1}{}{T}{dh} % "these three heads are q"
\stface[role=q, gap=2mm]{q2}{}{T}{dh}
\stface[role=q, gap=2mm]{q3}{}{T}{dh}
\stgroupend
\stcaption{qg}{$\mathbf q$}{$B\times T\times h\times d_h$}
\stcaption{qg}{$\mathbf q$}{$T\times h\times d_h$}
```
The members go **inside** the block, and three properties follow from that:
@@ -117,12 +119,18 @@ The members go **inside** the block, and three properties follow from that:
swallow whatever sat between its members;
- the group, not its last member, terminates a pending `\stlink` and sources the next
one, so an arrow lands **on** the outline instead of ending inside it and crossing a
border that is not its endpoint;
- the padding is reserved on both sides, so the neighbour cannot land tangent to it.
border that is not its endpoint. For the same reason `\stlink` **inside** a group is a
package error: a member never terminates a connector, so the arrow would be dropped
silently;
- the padding is reserved on both sides, so the neighbour cannot land tangent to it. The
fit includes decoration ink too: a `bracket=true` member's arms stay inside the outline.
`\strowend` fits the group, so `\stcaption{qg}{...}` hangs from the caption lane below the
outline, not below the member. A group may contain a `\stcol`; it may not sit inside one,
and it may not nest. An unclosed group is a lint error.
A group must bind **at least two members, or one `\stcol` partition** — around a single
face or stack the outline is decoration, and the package warns. `role=` is required; a
genuinely mixed group passes `role=neutral` explicitly. `\strowend` fits the group, so
`\stcaption{qg}{...}` hangs from the caption lane below the outline, not below the member.
A group may contain a `\stcol`; it may not sit inside one, and it may not nest. An
unclosed group is a lint error.
## Callouts
@@ -135,9 +143,12 @@ of a **finished** band, top-aligned with it. Call it after `\strowend`:
\stcallout{n1}{5.2cm}{rowB}{下界化换来了什么}{Kimi Linear 的 $g$ 无下界……}
```
Arg 3 must be a `\strow` band name — one aside per band, and never anchored to a single
face. Both rules are lint errors, because a card beside one operand reads as a step in the
computation (`layout.md`). Inside an open band it is a package error.
Arg 3 must be a `\strow` band name, never a single face — a card beside one operand reads
as a step in the computation (`layout.md`). The budget is **one callout per figure**: a
card on every band is a dashboard, and the cards are unreadable at thumbnail size anyway.
Both rules are lint errors; `% supertensor-lint: allow-multiple-callouts` relaxes the
budget to one per band for a figure that genuinely needs it. Inside an open band a callout
is a package error.
A callout pushes the vertical cursor below its own bottom edge. A card taller than its band
therefore opens visible white space rather than colliding with the next stage — which is
+5 -4
View File
@@ -38,10 +38,11 @@ into each other, not that the figure reads correctly.
- [ ] Every connector's white label underlay covers only its own connector.
- [ ] No connector crosses a box that is not its endpoint.
- [ ] Symbols and shapes sit on two flat lanes per row; stage headings share a left rail.
- [ ] Every `\stgroup` outline names a composite the computation actually has, is captioned,
and carries its members' role hue (`neutral` only for genuinely mixed members).
- [ ] Every `\stcallout` is an aside about its whole band, not a step: one per band, no
taller than the band, and nothing in it that belongs in `\stmeaningbox`.
- [ ] Every `\stgroup` outline names a composite the computation actually has, binds at
least two members or a `\stcol` partition, is captioned, and carries its members'
role hue (`neutral` only, and explicitly, for genuinely mixed members).
- [ ] At most one `\stcallout` in the figure (exemption comment if more), an aside about
its whole band, no taller than it, nothing in it that belongs in `\stmeaningbox`.
- [ ] Top zone compact (≤2 formula lines, no shape underbraces).
- [ ] Bottom box: one column, ≤3 rows, no overflow, no shrunken type.
- [ ] If requested, signature is outside the box, one line, accurate, unclipped and subdued.
+2 -2
View File
@@ -74,8 +74,8 @@ or on a `\stcallout` card hanging off the right edge of a band. Never drop a flo
commentary card between two operands unless it is a real operation node (`st comm`).
`\stcallout` is that rule made structural: it refuses to open inside a band, the linter
rejects it unless it is anchored to a `\strow` name, and a second card on one band is an
error — side-by-side cards are a dashboard, not a figure. A card that ends up much taller
rejects it unless it is anchored to a `\strow` name, and a second card in the figure is an
error without an explicit exemption — a card per band is a dashboard, not a figure. A card that ends up much taller
than its band is telling you the same thing the overflow warning does: that text is not an
aside, it is the **Mechanism** row of `\stmeaningbox`.
+3 -3
View File
@@ -77,8 +77,8 @@ long, **remove content** — never add cards, columns or smaller type. Pass `{}`
## Side cards
`\stcallout` is the only sanctioned floating text card, and it is deliberately narrow in
scope: one per band, hung off the right edge of a *finished* band, never between two
operands. When the text outgrows the height of its band, it is not an aside — move it into
scope: one per figure by default, hung off the right edge of a *finished* band, never
between two operands. When the text outgrows the height of its band, it is not an aside — move it into
the **Mechanism** row of `\stmeaningbox` instead of widening or shrinking the card.
## Optional signature
@@ -90,7 +90,7 @@ renders only the subject. Do not append an author, handle or brand identity.
## Never
Charts or metric insets not present in the primary formula. Decorative pills, banners,
shadows, repeated separators, explanatory cards other than one `\stcallout` per band.
shadows, repeated separators, explanatory cards other than the one budgeted `\stcallout`.
## Reference image
+1 -1
View File
@@ -74,6 +74,6 @@ if [[ $status -ne 0 ]]; then
echo "==> BUILD DIRTY: fix the warnings above before delivering." >&2
else
echo "==> clean. Now do the visual audit (references/checklist.md) --"
echo " a clean build says nothing about collisions or hue budget."
echo " a clean build says nothing about collisions or thumbnail legibility."
fi
exit $status
+22 -2
View File
@@ -68,6 +68,14 @@ def lint(path: Path) -> list[str]:
dimensions = check_unique(source, DIM_RE, "axis", errors)
roles = check_unique(source, ROLE_RE, "role", errors)
for match in ROLE_KEY_RE.finditer(source):
role = match.group(1)
if role not in roles:
errors.append(
f"line {line_of(source, match.start())}: role {role!r} is used "
f"before \\stsetrole"
)
formula_positions = [m.start() for m in re.finditer(r"\\sttopformula\b", source)]
row_ends = [m.start() for m in re.finditer(r"\\strowend\b", source)]
if not formula_positions and "allow-missing-formula" not in allowed:
@@ -114,10 +122,13 @@ def lint(path: Path) -> list[str]:
)
# A callout hangs off a BAND. Anchored to a face it becomes the floating
# commentary card between two operands that layout.md forbids, and two of
# them on one band is a dashboard, not a figure.
# commentary card between two operands that layout.md forbids. The default
# budget is ONE callout for the whole figure: a card per band is already a
# dashboard, and at thumbnail size the cards are unreadable anyway. The
# allow-multiple-callouts directive relaxes this to one per band.
band_names = set(ROW_NAME_RE.findall(source))
callout_anchors: dict[str, str] = {}
first_callout: str | None = None
for match in CALLOUT_RE.finditer(source):
name, anchor = (part.strip() for part in match.groups())
line = line_of(source, match.start())
@@ -131,7 +142,16 @@ def lint(path: Path) -> list[str]:
f"line {line}: callout {name!r} is the second card on band {anchor!r} "
f"(after {callout_anchors[anchor]!r}); one aside per band"
)
elif first_callout is not None and "allow-multiple-callouts" not in allowed:
errors.append(
f"line {line}: callout {name!r} is the second card in the figure "
f"(after {first_callout!r}); the budget is one callout per figure — "
f"move the text to \\stmeaningbox, or add the "
f"allow-multiple-callouts exemption"
)
callout_anchors.setdefault(anchor, name)
if first_callout is None:
first_callout = name
for row_number, row in enumerate(ROW_RE.finditer(source), start=1):
row_roles = set(ROLE_KEY_RE.findall(row.group(1)))
+18 -26
View File
@@ -1,7 +1,9 @@
% Regression test for \stgroup and \stcallout.
% - a group over one stack (the three sheets are one composite object),
% - a group over two adjacent shards (the pair is one partitioned parent),
% - a side card hanging off the right edge of each band.
% - a group over three adjacent head faces (the trio is one composite q);
% the first face carries bracket=true, probing that bracket ink is part
% of the group's fit -- the outline must clear the bracket arms,
% - a group over a \stcol partition (two shards tiling one parent W),
% - one side card for the whole figure, hanging off a finished band.
% No absolute coordinate appears anywhere in this figure.
% ../scripts/build.sh group-callout.tex
\documentclass[border=10pt]{standalone}
@@ -9,9 +11,7 @@
\stsetrole{act}{stTeal}
\stsetrole{w}{stOrange}
\stsetrole{q}{stTeal}
\stsetrole{k}{stViolet}
\stsetrole{v}{stCoral}
\stsetrole{q}{stViolet}
\stdim{T}{7}
\stdim{d}{5}
@@ -20,27 +20,18 @@
\begin{document}
\begin{tikzpicture}
\ststage{S1}{一、分组框:把已经排好的面命名为一个复合对象}
\ststage{S1}{一、分组框:三个相邻的头合起来是一个复合对象}
\strow{R1}{T}
\stface[role=act, bracket=true]{X}{}{T}{d}
\stlink{l1}{三路投影}
\stlink{l1}{按头切分}
\stgroup[role=q]{QG}
\ststack[role=q]{Q}{}{T}{dh}{3}
\stgroupend
\stgroup[role=k]{KG}
\ststack[role=k]{K}{}{T}{dh}{3}
\stgroupend
\stgroup[role=v]{VG}
\ststack[role=v]{V}{}{T}{dh}{3}
\stface[role=q, bracket=true]{Q1}{}{T}{dh}
\stface[role=q, gap=2mm]{Q2}{}{T}{dh}
\stface[role=q, gap=2mm]{Q3}{}{T}{dh}
\stgroupend
\strowend
\stcaption{X}{$\mathbf X$}{$T\times d$}
\stcaption{QG}{$\mathbf q$}{$h\times T\times d_h$}
\stcaption{KG}{$\mathbf k$}{$h\times T\times d_h$}
\stcaption{VG}{$\mathbf v$}{$h\times T\times d_h$}
\stcallout{N1}{4.2cm}{R1}{分组框在说什么}%
{外框与里面的面同色,因为它圈的是同一个对象的多头视图,不是一个新张量。
框的内边距计入行的包围盒,所以下面的符号轨仍然从框底起算。}
\ststage{S2}{二、分组框圈住两片相邻的分片,标注它们合起来是谁}
\strow{R2}{T}
@@ -58,8 +49,10 @@
\stcaption{H}{$\mathbf H$}{$T\times d$}
\stcaption{WG}{$\mathbf W$}{$d\times T$}
\stcaption{Z}{$\mathbf Z$}{$T\times T$}
\stcallout{N2}{4.2cm}{R2}{一行一张卡}%
{同一个 band 上挂两张卡会被 lint 拒绝:并排的说明卡是仪表盘,不是图。}
\stcallout{N1}{4.2cm}{R2}{分组框在说什么}%
{外框与成员同色,因为它圈的是同一个对象的分片视图,不是一个新张量。
括号等装饰墨迹也计入外框的包围盒,所以框永远不会被成员的括号穿过;
整张图默认只允许一张这样的卡片,再多就是仪表盘。}
\sttopformula{F}{$\displaystyle \mathbf Z=\mathbf H\,\mathbf W,\qquad
\mathbf W=\begin{bmatrix}\mathbf W_a\\ \mathbf W_b\end{bmatrix}$}
@@ -69,10 +62,9 @@
{$T$ 序列长,$d$ 模型维,$d_h$ 头宽,$h$ 头数}
{}
{分组框是横向的子流:成员写在块里,所以它只能圈住相邻的对象;
两侧的内边距各占一份宽度,箭头也接在外框上而不是成员上——
否则连接线会穿过一个并非自己端点的框。
说明卡挂在 band 右侧,并把纵向游标压到自己底边以下,所以卡片过高只会撑开空白,
不会压住下一段。}
它必须绑定至少两个成员或一个 \texttt{stcol} 分片,单个对象外再画框是装饰。
连接线不能在框内起止——\texttt{stlink} 写在组里是构建错误,
闭合后箭头接在外框上,而不是穿过一个并非自己端点的边。}
\end{tikzpicture}
\end{document}
+16
View File
@@ -0,0 +1,16 @@
% \stlink inside a group: members never terminate a pending connector, so the
% arrow would be dropped silently. The package must error rather than build a
% figure whose label appears but whose arrow does not.
\documentclass[border=2pt]{standalone}
\usepackage[en]{supertensor}
\stsetrole{x}{stTeal}
\stdim{T}{4}
\begin{document}\begin{tikzpicture}
\strow{R1}{T}
\stgroup[role=x]{G}
\stface[role=x]{A}{}{T}{T}
\stlink{l1}{lost arrow}
\stface[role=x]{B}{}{T}{T}
\stgroupend
\strowend
\end{tikzpicture}\end{document}
+14
View File
@@ -0,0 +1,14 @@
% A group with no role would silently fall back to neutral gray, violating
% the "outline in the composite's own hue" rule. The package must error.
\documentclass[border=2pt]{standalone}
\usepackage[en]{supertensor}
\stsetrole{x}{stTeal}
\stdim{T}{4}
\begin{document}\begin{tikzpicture}
\strow{R1}{T}
\stgroup{G}
\stface[role=x]{A}{}{T}{T}
\stface[role=x]{B}{}{T}{T}
\stgroupend
\strowend
\end{tikzpicture}\end{document}
+13
View File
@@ -0,0 +1,13 @@
% A group around a single object is decoration: the stack already reads as
% one composite. The package must reject this with a dirty-build warning.
\documentclass[border=2pt]{standalone}
\usepackage[en]{supertensor}
\stsetrole{x}{stTeal}
\stdim{T}{4}
\begin{document}\begin{tikzpicture}
\strow{R1}{T}
\stgroup[role=x]{G}
\ststack[role=x]{Q}{}{T}{T}{3}
\stgroupend
\strowend
\end{tikzpicture}\end{document}
+17
View File
@@ -0,0 +1,17 @@
% One card per band, but a card on EVERY band, is still a dashboard. The
% default budget is one callout per figure; this must fail lint without the
% allow-multiple-callouts exemption.
\documentclass{standalone}
\usepackage[en]{supertensor}
\stdim{T}{4}
\begin{document}\begin{tikzpicture}
\strow{R1}{T}
\stface{A}{}{T}{T}
\strowend
\stcallout{N1}{3cm}{R1}{first}{one per figure}
\strow{R2}{T}
\stface{B}{}{T}{T}
\strowend
\stcallout{N2}{3cm}{R2}{second}{this one belongs in the meaning box}
\sttopformula{F}{$A,B$}
\end{tikzpicture}\end{document}