✨ needflow: portable link and type styling, with the first deprecations - #1785
Open
chrisjsewell wants to merge 8 commits into
Open
chrisjsewell wants to merge 8 commits into
chrisjsewell wants to merge 8 commits into
Conversation
added 8 commits
August 20, 2026 19:05
`needs_links[]` gains the neutral `line`, `part_line`, `arrow`, `color` and
`part_color` keys, and `needs_types[]` gains `shape`: each says what is meant
rather than naming one engine's syntax, and each engine writes it in its own.
`color` is finally honoured -- an identical TODO sat in both emitters -- and is
unset by default rather than `#000000`, so a link type can now ask for black and
be given black, while one that names no colour keeps the engine's own edge colour
and draws byte-identically to before.
The four PlantUML-token keys (`style`, `style_part`, `style_start`, `style_end`)
stay valid indefinitely as aliases, folding into the neutral keys where those are
unset. The fold is per key, not per link type: `style` is a compound of a colour
and a line keyword, so a half-migrated link type keeps the half it has not moved
yet. The notice is usage gated -- it names exactly the deprecated keys a project
wrote, and says nothing about one that writes none.
Out-of-enum values for the new keys warn once against `conf.py` and hand back to
the deprecated spelling, rather than reaching a lookup table as a `KeyError`.
`_PLANTUML_ARROWS["both"]` is `("<-", "->")`, not `("<", "->")`: the line style is
written between the two tokens, so `<[dashed]->` is a syntax error where
`<-[dashed]->` renders. A pair that only ever concatenated to a bare arrow would
have passed unnoticed until a link type set a line or a colour as well.
Four cases, carved from the umbrella and passing unaltered against this implementation on both engines: `link-line-arrow-color` (the three neutral link properties composing into one construct), `link-part-styling` (`part_line` and `part_color` drawn for the part edge and NOT for the ordinary one beside it), `shape-enum-sample` (three shapes, one of which PlantUML has no form for) and `arrow-unsupported` (`cross`, likewise). The seam grows with them: `TYPE_KEYS` gains `shape`, `LINK_KEYS` gains the whole portable link set, and the observable-degradation table gains `shape-unmapped` and `arrow-unsupported`. Both are now mapped, so the probes that used them as examples of vocabulary with no surface here move to what is still unmapped. The format itself grows too, which is why `corpus_version` steps to 3: a need may carry `parts`, and a link type `part_line`/`part_color`. All 23 existing cases keep their checksums byte for byte -- only the README hash moves.
`needs_types[].shape` and the five neutral `needs_links` keys get their sections in `configuration.rst`, with the ten-member shape enum, the older-PlantUML `hexagon` caveat, and `deprecated::`/`versionadded::` markers on the four PlantUML-token keys and on the two sections that describe them. The arrow migration table is spelled out, and says what a migrating project most needs to know: today's default graphviz arrow head is `open`, not `normal`. The default `style_end` of `->` has always rendered as an open "V" there, so a project reaching for the obvious-looking member would silently change every diagram it renders with graphviz. `docs/ubproject.toml` moves to the neutral keys, so this project's own documentation no longer builds on a deprecated spelling; `[needs.links.triggers]` and its siblings are exactly the configurations that needed `part_color` in order to migrate at all. The changelog carries the feature and the deprecation. The PR number is a GUESS (1783) -- confirm before merging.
… color The umbrella's own docstring says "``part_color`` falls back to ``color`` when it is unset, exactly as ``part_line`` falls back to ``line``", but only the colour half did it: an unset `part_line` handed straight back to the deprecated `style_part`, whose dataclass default is `dotted`. A link type that had fully migrated -- `line = "dashed"` and no legacy key anywhere -- therefore drew its part edges dotted, from a default it had never written, and no rule a reader could guess explained why the colour and the line behaved differently. The part keys are overrides: unset takes the ordinary counterpart, and only when neither neutral key is set does the deprecated pair decide. A purely legacy link type is untouched, so the byte-preservation gate is unaffected -- verified by re-diffing the legacy probe on both engines. Also adds the mirror of the half-migration case: a neutral `color` beside a `style` of `dotted,#FF0000` supersedes the colour token and leaves `dotted` drawing. Mutating the strip either way (drop the string wholesale, or emit it untouched) turns that test red.
Not a doctree key -- `NeedsFlowType` gains nothing on this branch. What moved is the positional layout of `LinkDisplayConfig`, which lives on the environment as part of `env._needs_schema` and is therefore pickled. A frozen `slots=True` dataclass pickles POSITIONALLY: `__getstate__` returns a list of field values in declaration order and `__setstate__` `zip`s it back on. `part_color` was inserted between `color` and `style` rather than appended, so a stale entry loads with every later field holding its neighbour's value -- `part_color` holding a line keyword like `dashed,#00AA00` -- and with the trailing four never set. Four plausible-looking wrong values and four AttributeErrors, none of which raise where the damage is. Nothing reads it today: `Builder.read()` emits `env-before-read-docs` unconditionally and `create_schema` replaces the schema there before any reader. The bump is taken anyway, because the failure mode is a silent misread rather than a crash and the only thing preventing it is an unversioned ordering invariant -- a handler added later on `builder-inited`, `env-get-outdated` or `env-purge-doc` would read the shifted values and get no error for the first four fields. Proved reachable with exactly such a handler: at 7 the stale env is reused and the handler reads the shifted object; at 8 Sphinx reports "build environment version not current", discards it, and re-reads. The re-read emits byte-identical diagram source. Appending instead of inserting would not have been enough: `zip` stops at the shorter sequence either way, so a stale `display.line` raises whatever the order.
Two contracts this slice fixed had no corpus case, and the corpus is how ubCode inherits them at re-sync -- a sphinx-needs unit test is invisible to it. `link-part-fallback` pins the half of the part-key contract that `link-part-styling` structurally cannot: that case sets `part_line` and `part_color` explicitly, so it would pass whatever an UNSET one did. The new case leaves both unset and asserts the part edge takes `line`/`color`, which the README already specified and nothing checked. `link-arrow-both` pins `arrow: both` beside a line and a colour -- the member whose umbrella token pair was invalid PlantUML, and invalid in exactly this shape, since the line style is written between the two arrow tokens. Only a unit test caught that, so ubCode could have inherited the broken pair unseen. The corpus harness runs plantuml through the suite fixture, so a source that cannot render surfaces as a warning outside the degradation registry. `corpus_version` stays 3: version 3 has not been published, so these ride it (the slice-2 pre-publication precedent). All 27 existing case checksums are byte identical; the manifest gains two entries and the README hash moves for the two new rows in its required-cases list. Regeneration through the committed single path is idempotent across two successive runs.
…tput
The row said `arrow: normal` replaces `style_end = "->>"`, which is right about
what the configuration asks for and wrong about what base draws. The graphviz
translation of the deprecated keys reads only the first and last character of
`style_start + style_end`, so `->>` has always collapsed to the same `vee` as `->`
-- measured directly: both resolve to `[('arrowhead', 'vee')]`.
A reader following the table therefore sees their graphviz arrowheads change. That
is arguably a fix, since they asked for a solid head and the first-and-last cheat
gave them an open one, but it is a change, and the `.. important::` block already
goes to trouble to warn about exactly this class of surprise for the `->` default
while saying nothing about `->>`. Row annotated and the note extended.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #1785 +/- ##
==========================================
+ Coverage 86.87% 90.96% +4.08%
==========================================
Files 56 77 +21
Lines 6532 11732 +5200
==========================================
+ Hits 5675 10672 +4997
- Misses 857 1060 +203
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fourth slice of the #1770 split (after #1780, #1781, #1782): link and type styling, and the
split's first deprecations. Pairs with the ubCode mirror PR (cross-linked there) so the two tools
keep one styling vocabulary.
Config
needs_links[].line/part_line/arrow/color/part_color— engine-neutraldrawing keys:
lineissolid/dashed/dotted/thick/invisible(withboldandhiddenaccepted as the words PlantUML and Graphviz users already write);arrowisnormal/none/open/circle/cross/both; the twopart_*keys style edges that end on aneed part and each falls back to its ordinary counterpart when unset.
coloris finallyhonoured — an identical
TODOsat in both emitters — and an explicit#000000draws black:unset is spelled by absence (or
""), never by a colour. The built-inlinkstype's"color": "#000000"default is removed in the same commit, because it was a stand-in for"nothing" and would otherwise have turned every default project's edges black.
needs_types[].shape— a ten-member neutral enum (rectangle,rounded,circle,ellipse,diamond,hexagon,cylinder,document,folder,box3d); the sixteen legacyPlantUML
stylekeywords are accepted as aliases. PlantUML cannot drawdiamondand warnsonce per project (tier 2); Graphviz draws all ten. The
hexagonnote documents thePlantUML ≥ 1.2020.13 requirement (verified: fails at 1.2020.02, renders at 1.2022.5).
style/style_partfold in per key, not per type: a half-migrated link type(neutral
lineset, no neutral colour) keeps its legacy colour, and a colour-migrated one hasonly the colour token stripped from its legacy string. Both directions are separately
mutation-fenced.
The first deprecations — honoured indefinitely
needs_links[].style,style_part,style_start,style_end. Usage-gated: the notice firesonce per link type, names exactly the keys present, and a fully-migrated project is silent
(the silence fence is itself mutation-tested). Four test fixtures migrated to the neutral keys;
doc_needs_filter_func_allow_dirty_filterdeliberately keeps the legacy spelling as thedeprecation regression fixture. No warning-fence allow-lists were needed anywhere — the
suite's whole-project empty-warnings fences are untouched.
Two bugs fixed against the reviewed source branch
_PLANTUML_ARROWS["both"]emitted<[dashed]->, which PlantUML rejects outright; the pair isnow
<-/->. A render matrix of all six arrows beside a line and colour passes 60/60 atthis tip versus 1/10 on the source branch.
part_linedid not fall back toline, contradicting the source branch's own docstring anddocs — a fully-migrated link type drew its part edges
dottedfrom a legacy default it neverwrote.
Environment version
ENV_DATA_VERSIONgoes 7 → 8, and the story is worth reading: the first build of this sliceargued no bump was needed because slots dataclasses "pickle by name". Review disproved the
premise — frozen slots dataclasses pickle positionally, and
part_coloris insertedmid-struct, so a stale
environment.pickleholds silently shifted values (measured:part_color = 'dashed,#00AA00') until the schema is rebuilt. No build shape reads the staleobject today (probed across five shapes), but a silent-corruption failure mode guarded only by an
unversioned ordering invariant is exactly what the constant is for. Reproduced in both
directions: at 7 the stale environment is reused and a probe handler reads the shifted object; at
8 Sphinx discards and re-reads, byte-identical output.
Conformance corpus
corpus_version2 → 3, 23 → 29 cases:link-line-arrow-color,link-part-styling,shape-enum-sample(with theshape-unmappedtier-2 degradation),arrow-unsupported, plus twofrom the review round —
link-part-fallback(the fallback contract this slice fixed) andlink-arrow-both(the exact composition that was invalid on the source branch). All pre-existingcase checksums are character-for-character unchanged, and regeneration is idempotent. The README
format grows:
needs[].parts,links[].part_line/part_color, and the colour contract statedoutright (
"#000000" is a colour and not "unset") — the line that also settled a cross-tooldivergence in ubCode's favour of this spelling.
Docs
Configuration reference for the five link keys and
shape,.. deprecated:: 8.4.0markers onthe legacy sections, and an arrow migration table whose
normalrow now says what it maps ratherthan over-promising rendered output (measured: base Graphviz renders both
->>and->asvee, soarrow: openis the byte-identical migration).Tests and review
31 assertions recorded red before implementation; the mutation battery covers the per-key folding
(both directions — two mutations are caught only by the review round's mirror test), the
deprecation usage gate, the arrow fix, and both fallbacks. Full suite: 0 new failures against the
22 pre-existing environmental ones; docs warning set identical to master. Adversarial review
reproduced the byte-preservation probes, the arrow matrix, and the ENV mechanism independently.
Byte-preservation: a probe using every legacy spelling at once is byte-identical to master on
both engines; the single exception class is a link type that explicitly sets
color, whichmoves exactly one line — the feature.
Follow-up (slice 5, last of the split)
:styles:+needs_flow_styles,:engine_config:, the remaining deprecations (:config:,:scale:,:highlight:,:border_color:,needs_flow_link_types), thecolor-normalisationcorpus case, the mermaid-reserved engine value, and moving the engine membership check into
validate_flow_config.