🐛 needgantt: address tasks by id and fix the start_date month - #1778
Merged
Conversation
Two defects in the PlantUML source `process_needgantt` generates. 1. Tasks are declared as `[<title>] as [<id>]`, which binds every later `[...]` reference to the id, but the completion and colour accumulators addressed tasks by their title. PlantUML does not reject an unbound reference: it silently declares a second, zero length task of that name. So every need carrying a type colour or a completion value was drawn twice, and the colour and completion landed on the phantom bar rather than the real one. Both accumulators now use the id, as the constraint lines already did. 2. `start_date` was reformatted through `MONTH_NAMES`, a 0-based table indexed with the 1-based month number, so the chart started one month later than asked for and a December date raised `IndexError` and aborted the build. The generated statement is now the ISO date, which PlantUML also accepts and which renders the identical chart. `MONTH_NAMES` had no other user and is removed. Also drops the needgantt docs' claim that task elements are linked to their need for `svg` output; no such link has ever been generated.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #1778 +/- ##
==========================================
+ Coverage 86.87% 90.60% +3.72%
==========================================
Files 56 76 +20
Lines 6532 11334 +4802
==========================================
+ Hits 5675 10269 +4594
- Misses 857 1065 +208
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:
|
ubmarco
approved these changes
Aug 18, 2026
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.
Two defects in the PlantUML source that
process_needganttgenerates. Both were foundwhile implementing needgantt rendering in ubCode, by
rendering the byte-exact text this function emits and comparing it against what the
directive's documentation says it draws.
Both defects have the same shape: the generated source is wrong, but PlantUML does not
say so — one is silently accepted and mis-drawn, the other blows up in Python before
PlantUML ever sees it.
1. Every coloured or completed task is drawn twice — and the real bar gets neither the colour nor the completion
Tasks are declared as
[<title>] as [<id>], which binds every later[...]reference tothe id. The constraint lines already used the id. The completion and colour
accumulators used the title.
PlantUML does not reject an unbound reference — it silently declares a new, zero length
task of that name. Minimal reproduction:
→ 2 task rows. Addressing the same statement by
[ID1]→ 1 row.Since
needs_typesentries carry a colour by default, this fired for essentially everyneed in every chart. A three-need chart with one completion value rendered as six
bars, and the damage was not merely cosmetic: the colour and the completion attached to
the phantom bar, leaving the real one in PlantUML's default grey.
Before / after, from the generated SVG (3 needs, 2 types, one
:completion: 80)Both accumulators now emit the id, like the constraint lines always did.
2.
:start_date:names the wrong month, and December ends the buildutils.pydefined a 0-basedMONTH_NAMESstarting"January";needgantt.pyindexed itwith the 1-based
int(start_date.strftime("%m")).:start_date: 2020-03-25→Project starts the 25th of April 2020— one month late.:start_date: 2020-12-25→IndexError: list index out of range, surfacing asExtensionError: Handler … for event 'doctree-resolved' threw an exception. Thebuild dies; there is no way to put a December date on a gantt chart.
The generated statement is now the ISO date, which PlantUML also accepts:
I went for the ISO form rather than
MONTH_NAMES[start_date.month - 1]because:an index to get wrong, and
MONTH_NAMEShad exactly one definition and one use, no__all__entry and no mention in the docs, so it is deleted;the
<?plantuml-src …?>processing instruction, on PlantUML 1.2026.6 and 1.2022.5alike, so this does not raise the effective PlantUML floor;
:start_date:is already validated as%Y-%m-%d, soechoing it through is the smallest possible transformation, and it is locale
independent (unlike
%B);the old code appended a literal
th, so2020-01-01was writtenthe 01th of February 2020.Why the test suite never caught defect 1
tests/doc_test/utils/plantuml.jaris PlantUML 1.2022.5, and every test app ispointed at it (
tests/conftest.py:288). The phantom-task behaviour is newer than that:master(title-addressed)1.2022.5 still resolved a
[title]reference back to the aliased task; a current PlantUMLcreates a second task instead. The
<rect>lists for the two sources at 1.2022.5 arebyte-identical, so the fix is safe in both directions: users on an old PlantUML see no
change at all, users on a current one get the chart they always should have had.
Changed output
This changes the generated PlantUML for any chart with a type colour, a completion value
or a
:start_date:— i.e. almost all of them. The rendered result is either identical(old PlantUML) or strictly more correct (current PlantUML); nothing that rendered
correctly before renders differently now. Both changelog entries are marked
(changed output).
Tests
tests/test_needgantt.pyis new — there was no needgantt test module. It follows the twopatterns already in the suite: inline
filessource projects (tests/test_needflow.py)and a
doctree-resolvedcollector for the generated PlantUML(
tests/test_filter.py::_capture_diagrams, whose docstring makes the case for assertingon the generated source rather than the rendered image).
test_tasks_are_addressed_by_idasserts the general invariant — no statement mayreference a task that is not a declared alias — which is precisely the property whose
violation PlantUML answers with a phantom bar. It covers the milestone path and the
already-correct constraint lines too.
test_start_date_is_the_given_dateis parametrised over January, March and December,the last being the crash boundary.
Both fixes were mutated back out individually to confirm each test fails for its own fix
and no other (
1 failed, 3 passedand3 failed, 1 passedrespectively).Docs
The page claimed:
There is no
calculate_linkcall and no[[…]]emission anywhere inneedgantt.py(unlike
needflow/_plantuml.py, which does link). Task bars have never been hyperlinked;the only link a chart produces is its caption, pointing at the generated image file. The
sentence is removed rather than implemented, as adding task links would be a feature
rather than a fix.
A third defect, left alone — float completion values
Not fixed here, to keep this PR to the two defects above, but worth recording since it is
the same class and it is currently visible in this repo's own documentation.
completionis never rounded, whiledurationthree lines above it is, under the comment"float durations not supported by plantuml". A
number-typed completion field thereforeemits
[…] is 90.0% completed, which PlantUML 1.2026.6 rejects (Syntax Error? (Assumed diagram type: gantt)) though 1.2022.5 accepted it — the same version gate asdefect 1.
This is not hypothetical:
docs/directives/needgantt.rst'scompletion_optionexampleuses
:completion_option: amount, anddocs/ubproject.toml:104declaresamountasschema.type = "number". A docs build on a current PlantUML consequently emitsdocs/directives/needgantt.rst:296: WARNING: error while running plantuml— onmasteras well as on this branch (same warning, two lines further down), so this PR neither
causes nor fixes it. Happy to add the
round()here or in a follow-up, whichever youprefer.
Verification
-m "not jstest and not benchmark"): identicalFAILEDset on this branchand on
mastercontent in the same tree — 20 pre-existing local failures either way,unrelated to needgantt (needuml, report_dead_links, needarch, …), and all passing when
run in isolation. The delta is exactly the 4 new tests.
ruff check,ruff format,mypyclean.-nW --keep-going): 1 warning on this branch, the same 1 warning onmaster— the pre-existing float-completion one above.