- scripts/lint.py: reject raw rectangles, absolute coordinates, hue-budget and callout/group/formula-order violations at the source level - tests/invalid/ + tests/lint-invalid/: negative fixtures proving the package and linter reject bad input; test.sh now runs both directions - references/fallback.md: degraded path when no LaTeX is available - tests/group-callout.tex: exercise \stgroup and \stcallout - agents/openai.yaml: agent config - Docs and .sty updated to match
121 lines
5.2 KiB
Markdown
121 lines
5.2 KiB
Markdown
# Layout invariants
|
|
|
|
Everything on the canvas is a bounding box: tensors, full offset stacks, brackets,
|
|
operators, arrow labels, annotations, symbols, shape labels, stage headings, the meaning
|
|
box, the signature. **Tangency counts as collision.**
|
|
|
|
## Place by cursor, not by coordinate
|
|
|
|
The invariants below are *statements about the finished figure*; the flow layout in
|
|
`api.md` is how you get them without checking each one by hand. Use it by default:
|
|
`\ststage` / `\strow` … `\strowend` / `\stcol` … `\stcolend`, with an empty coordinate
|
|
argument on every face.
|
|
|
|
The failure it removes is specific. With hand-written offsets, each gap is a magic number
|
|
tuned against the *current* content, so the day a label grows by two characters it silently
|
|
lands on the next tensor — the figure still compiles and still looks clean. With the
|
|
cursor, every object reserves its own width, so growing one object can only push the rest
|
|
apart. And because a band declares its height, an object that does not fit becomes a
|
|
`Package supertensor Warning`, which `build.sh` turns into a failed build.
|
|
|
|
Hand placement remains available for the cases the cursor cannot express. When you use it,
|
|
wrap the node in `\sttrack` so the bounding box and the vertical cursor still see it.
|
|
|
|
## Gutters
|
|
|
|
Define one base gutter `g ≥ 1 em`; that is what `\stgutter` (6 mm) is. Unrelated boxes stay
|
|
at least `g` apart; stage bands at least `1.5g` (`\strowgap`, `\stblockgap`). Set them once
|
|
at the top of the figure rather than per call — a per-call `gap=` is for stating a
|
|
*relationship* (`gap=0pt` = these shards tile), not for nudging.
|
|
|
|
Overlap is allowed only inside one declared composite:
|
|
|
|
- tiles inside their own face,
|
|
- shards tiling a parent,
|
|
- outline sheets in one `\ststack`,
|
|
- a bracket around its own tensor,
|
|
- a `\stgroup` outline around its own members,
|
|
- a connector endpoint touching its source/target border.
|
|
|
|
Every other intersection or occlusion is forbidden.
|
|
|
|
## Lanes
|
|
|
|
Reserve separate vertical lanes and never put anything else in them:
|
|
|
|
```
|
|
stage heading
|
|
(connector annotations)
|
|
tensor / operator row
|
|
symbols <- \stcaption arg 2
|
|
shapes <- \stcaption arg 3
|
|
```
|
|
|
|
Faces of different heights would otherwise hang their captions at different depths. Fix it
|
|
with a shared baseline — `\strowend` arms one automatically:
|
|
|
|
```tex
|
|
\strow{rowA}{T}
|
|
...
|
|
\strowend
|
|
\stcaption{Q}{$\mathbf Q^{(i)}$}{$h\times T\times d_h$}
|
|
```
|
|
|
|
Under absolute placement, arm it yourself with `\stlane{rowA}` … `\stnolane`. Either way
|
|
every `\stcaption` inside hangs from the bottom of `rowA`, so symbols and shapes form two
|
|
flat lanes.
|
|
|
|
Stage headings share one left rail. `\ststage` puts them there: the rail is a single stored
|
|
`x` (`\stleftrail` to move it), and the `y` is derived from the lowest ink drawn so far, so
|
|
a heading can neither drift right nor collide with the row above it.
|
|
|
|
Explanatory prose belongs in the stage subtitle, the bottom box, above its own connector,
|
|
or on a `\stcallout` card hanging off the right edge of a band. Never drop a floating
|
|
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
|
|
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`.
|
|
|
|
## Composites
|
|
|
|
`\stcol` and `\stgroup` are the two sub-flows, and both exist so that a *relationship* can
|
|
be stated rather than arranged for. A group wraps its members from the inside, which is
|
|
what makes an arrow attach to the outline rather than end inside it — a connector whose
|
|
endpoint is a member but which crosses the group border is the ordinary version of "a
|
|
connector may not cross a box that is not its endpoint".
|
|
|
|
## Layers
|
|
|
|
The package declares three: `stbg` (connectors), `main` (tensors, operators), `stfg`
|
|
(text). `\starrow` and `\starrowlabel` route on `stbg` automatically, so a connector can
|
|
never cover a face. Two consequences you still own:
|
|
|
|
- A connector may not cross a box that is not one of its endpoints. Move the row, don't
|
|
route over.
|
|
- A label's white underlay may cover only its own connector — never a tensor, never
|
|
another label. This is the single most common failure after a first draft, and `\stlink`
|
|
is the fix: it makes the label itself a flow object and draws the arrow to whatever
|
|
lands on either side of it, so the label cannot be wider than its connector.
|
|
`\starrowlabel` between two hand-placed nodes still has the old failure mode.
|
|
|
|
## Stacks
|
|
|
|
`\ststack` includes its offset sheets in the bounding box, so neighbours can be spaced
|
|
against the real extent. Back sheets are outline-only and must carry no semantic content
|
|
of their own. If individual slices need to be read, use separate panels instead of overlap.
|
|
|
|
## When it does not fit
|
|
|
|
In this order:
|
|
|
|
1. shorten or remove secondary annotation,
|
|
2. widen the natural crop,
|
|
3. increase row spacing,
|
|
4. move the whole stage to another row.
|
|
|
|
Never solve crowding by shrinking below the type hierarchy, closing the gutter, or covering
|
|
another object.
|