feat: interactive timeline plugin - #43
Merged
Merged
Conversation
Renders a fenced ```timeline block written in YAML as an interactive project timeline: a date picker per named anchor date, plus a chain of named offset dates -- calendar or business days, with optional exclusion dates for holidays and blackouts -- drawn as a Mermaid gantt diagram that recomputes live. All date math runs client-side, mirroring the calculator plugin: the build validates the YAML and emits a form plus a JSON spec, and a site-wide vanilla-JS evaluator re-derives every date on change. Registered as a Markdown preprocessor at order 15 so it runs after snippets and before table-reader. Applied from timeline.patch; the stray NetDocs.7z archive in that patch was dropped as build output that does not belong in the tree. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The timeline plugin arrived with a near-verbatim copy of CalculatorPlugin's ~45-line fence scanner. Fence handling is subtle -- a block has to be matched to its own closing fence so an outer ```` example containing an inner ```calc is copied through rather than half-rendered -- so extract it as FencedBlocks and have both plugins call it, instead of keeping two copies in step by hand. That also fixes a latent bug in both: each pre-scanned the source with a case-sensitive IndexOf but compared the fence info word case-insensitively, so a ```CALC or ```TIMELINE fence was silently skipped and rendered as a plain code block. The shared pre-scan is case-insensitive, so it agrees with the comparison that follows. Also fix an OverflowException in timeline: the offset regex bounds `expr` to digits but not to a magnitude, so `start + 99999999999999` threw and failed the whole build, where every other malformed field degrades to a warning. And escape the exclusion date interpolated into a data- attribute, which was the one value reaching innerHTML unescaped. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The client evaluator stops after 100000 steps and returns whatever date it had reached, so `start + 100000` and `start + 2000000000` both resolved to 2300-10-20 and rendered as if that were the answer. Verified by running the evaluator's date math directly under node. Reject anything past that budget at build time, alongside the overflow case, so an absurd offset is a warning rather than a silently wrong timeline. The limit is named next to the guard it mirrors so the two cannot drift apart. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The extracted scanner had no direct coverage -- only a handful of assertions reaching it through the two plugins. Add tests for the shapes it has to handle: tilde fences, longer-than-three fences, indented fences, attributes after the info word, a fence that is never closed, several blocks on one page, and content around a block. Two of those failed on first run, for a reason worth keeping: the fixture had `inputs` but no `outputs`, so the block rendered an error box -- and the older assertions looked for "nd-calc", which the error box also carries. They now assert on the rendered form and on the absence of an error box, so a failed render cannot pass as a successful one. Control ids came from a fresh GUID per block per build, so every page holding a calc or timeline block differed byte-for-byte on every run: OutputWriter rewrote it and the watch daemon republished it, defeating the incremental diff both exist to produce. Measured on a 16-page corpus, 11 pages rewrote on a no-op rebuild; with ids derived from page, block index and control name instead, that is 0. Ids stay unique across identical blocks on one page. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
FencedBlocks was only ever reached through whichever plugin happened to call it, so the scanning contract itself was pinned by nothing: the plugin tests can only drive it with markdown their own YAML accepts, which leaves the fence rules -- the actual reason this code is shared -- untested. Expose it to the test project via InternalsVisibleTo rather than making it public: it is an implementation detail of the fence-replacing plugins, not part of the plugin API that external assemblies build against. The 27 tests pin what the plugins cannot reach: CommonMark fence pairing (a closing run must be at least as long as the opener; tildes are not closed by backticks), unclosed fences running to end of document, exact-match info words so ```calculator is not treated as ```calc, the body handed to the render delegate excluding both fence lines, block indexes counting only matched blocks, and element id stability. Verified by mutation: dropping the closing-fence length rule fails exactly ShorterRunDoesNotCloseTheFence, and freezing the block index fails exactly BlockIndexCountsOnlyMatchedBlocks. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Adding the offset limit left the docs behind the code: the expressions section described offsets as an unbounded whole number of days, with no mention that one past the evaluator's budget is now dropped. It documents the exclusion-range cap the same way, so this follows that shape. The plugin was also missing from the "no MkDocs equivalent" list in netdocs-vs-mkdocs, which already calls out the calculator plugin -- timeline is the same kind of thing and belongs beside it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
9 tasks
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.
What & why
Adds a
timelineplugin: a fenced```timelineblock written in YAML becomes an interactive project timeline — a date picker per named anchor date, plus a chain of named offset dates (calendar or business days, with optional exclusions for holidays and blackouts) drawn as a Mermaid gantt diagram that recomputes live.All date math runs client-side, mirroring
calculator: the build validates the YAML and emits a form plus a JSON spec, and a site-wide vanilla-JS evaluator re-derives every date on change. No build-time date math, no server round-trip. It registers as a Markdown preprocessor at order 15, so it runs after snippets and before table-reader.