3/7 Mark plugins: contribute a chart kind without forking (§24 v0) - #304
Closed
Conversation
The dossier has listed "no extensibility story" as an open risk since the audit, and it was the one dimension where XY lost outright rather than traded: Matplotlib has custom Artists, Bokeh has custom models, and XY had a hardcoded dict of nineteen appliers. This ships the half of §24 that can be shipped honestly. A plugin declares columns, a `calc` over them, and a `build` that returns built-in `Mark` objects. `build` never receives the `Figure`, the trace list, or the column store — it returns marks, and `_plugin_applier` runs them through the same appliers, the same axis assignment, and the same post-processing as marks written by hand. That containment is the design, not a safety rail bolted on. Because a plugin's output is ordinary traces, it inherits decimation on large inputs, picking, hover, the a11y summary, and all three export paths without writing a line for any of them — and it cannot draw anything the engine could not already draw. §24's third requirement, hover/a11y descriptors, turns out to need nothing at all for the same reason. The custom-shader half of §24 stays deferred, and the argument above is exactly why: a plugin carrying its own GLSL inherits none of that and has to re-earn all of it. Deferring is the position, not the backlog. Composition is one level deep. Plugins compose built-ins, not each other, which keeps the registry a lookup instead of a dependency graph with cycles. The registry also refuses to shadow a built-in kind and refuses to silently replace another plugin — two libraries registering "candlestick" is a conflict their user needs to see, not a race that import order settles. Also records the dividing line in the chart-kind contract: a kind that needs a new GPU primitive is a core kind and pays the six-touch-point checklist; a kind that only composes existing ones is plugin territory and pays nothing.
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
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.
Stacked on #303. This is the one that changes the answer rather than the documentation.
Why this is the unlock
The dossier has carried "no extensibility story" as an open risk since the audit, and it is the one dimension where xy loses outright rather than trades: Matplotlib has custom Artists, Bokeh has custom models, xy had a hardcoded dict of nineteen appliers. Every other comparison row is a trade; this one was a zero.
The design, and why it is a design rather than a limitation
buildreturns built-inMarkobjects. It never receives theFigure, the trace list, or the column store.That containment is the whole point. Because a plugin's output is ordinary traces, it inherits — for free, and with no way to get them wrong — decimation on large inputs (§28), picking, hover, the a11y summary (§20), the wire protocol's f32 discipline (§29), and all three export paths including the two with no browser. §24's third requirement, hover/a11y descriptors, turns out to need nothing at all for the same reason.
The custom-shader half of §24 stays deferred, and the argument above is exactly why. A plugin carrying its own GLSL inherits none of that and has to re-earn all of it. That is a position, not a backlog item.
Composition is one level deep — plugins compose built-ins, not each other — which keeps the registry a lookup instead of a dependency graph with cycles.
Registry rules
Refuses to shadow a built-in kind, and refuses to silently replace another plugin: two libraries registering
"candlestick"is a conflict their user needs to see, not a race that import order settles.Also
Records the dividing line in
spec/api/chart-kind-contract.md: a kind needing a new GPU primitive is a core kind and pays the six-touch-point checklist; a kind that only composes existing ones is plugin territory and pays nothing.12 new tests, including one asserting the context object exposes exactly five fields and no route to the engine.
uv run pytest2338 passed.Generated by Claude Code