Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 16 additions & 13 deletions .github/workflows/impl-review.yml
Original file line number Diff line number Diff line change
Expand Up @@ -256,10 +256,13 @@ jobs:
# it, and a PR branched off an older main does not carry them.
# The adaptability checker and its record builder join them for the
# same reason: a PR head must not choose the rules it is checked by.
# So does the JavaScript header writer (impl_header.py), which
# "Update metadata and implementation header" imports.
mkdir -p "$RUNNER_TEMP/regen-tools"
cp automation/scripts/regen_gate.py automation/scripts/close_issue_if_complete.py \
automation/scripts/review_provenance.py automation/scripts/regen_writeback.py \
automation/scripts/adaptability_check.py automation/scripts/adapt_record.py \
automation/scripts/impl_header.py \
"$RUNNER_TEMP/regen-tools/"
# The reviewer's step-10 self-check (`check-feedback`) runs this
# same workflow-ref copy of the gate.
Expand Down Expand Up @@ -1792,6 +1795,7 @@ jobs:
python3 - <<'EOF'
import os
import re
import sys

impl_file = os.environ["IMPL_FILE"]
language = os.environ["LANGUAGE"]
Expand Down Expand Up @@ -1866,19 +1870,18 @@ jobs:
f"// Library: {library} {lib_version} | {RUNTIME_LABEL} {lang_version}\n"
f"// Quality: {score}/100 | {date_info}"
)
# Match a leading run of `//`-comment lines (the existing header).
# The negative lookahead `(?!#)` excludes `//#`-prefixed lines —
# those are anyplot directives (e.g. `//# anyplot-orientation:
# square`) the harness reads, NOT header lines, so they must
# survive the rewrite. The run therefore stops at the first
# directive; if a directive is the first line of the file the
# regex falls through and the canonical header is prepended above
# it. (#6947 prepend-fallback.)
pattern = r"\A(?://(?!#)[^\n]*\n)+"
if re.match(pattern, content):
new_content = re.sub(pattern, new_header + "\n", content, count=1)
else:
new_content = new_header + "\n" + content
# `//#` lines (e.g. `//# anyplot-orientation: square`) are
# directives the harness reads, not header lines. The writer
# puts the header on line 1, moves a directive that sat above
# the old header directly below the new one, and drops a stale
# second `// anyplot.ai` block (the old prepend fallback left one
# below the directive). Tested in
# tests/unit/automation/scripts/test_impl_header.py; this is the
# workflow-ref copy "Checkout PR code" saved.
sys.path.insert(0, os.path.join(os.environ["RUNNER_TEMP"], "regen-tools"))
from impl_header import replace_js_header

new_content = replace_js_header(content, new_header)
else:
# Python: triple-quoted module docstring at the top of the file.
title_safe = title.replace('"""', '\\"\\"\\"')
Expand Down
3 changes: 2 additions & 1 deletion automation/js-render/render.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@
// Canvas — HARD RULE (the impl-review.yml gate rejects > 16 px drift):
// landscape 3200 × 1800 (1600 × 900 CSS × deviceScaleFactor 2) — default
// square 2400 × 2400 (1200 × 1200 CSS × deviceScaleFactor 2)
// Orientation is picked per snippet via a top-of-file directive
// Orientation is picked per snippet via a directive line, read anywhere in the
// file (by convention directly below the `// anyplot.ai` header)
// //# anyplot-orientation: square
// or the `--square` flag. Exact pixels come from Playwright `deviceScaleFactor`
// + a fixed-size mount node, not from anything the snippet does.
Expand Down
8 changes: 8 additions & 0 deletions automation/scripts/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,14 @@ resolved model id from the claude-code-action execution file (first
one, never the alias. `impl-review.yml` writes both into the metadata's
`review` block (an empty value removes the key) and the gate record (`n/a`).

### `impl_header.py`
JavaScript header writer (stdlib). `impl-review.yml`'s "Update metadata and
implementation header" imports `replace_js_header` from the copy that "Checkout
PR code" saves at the workflow's ref. It writes the four-line `// anyplot.ai`
header on line 1, moves `//#` directive lines that sat above the old header
directly below the new one, and drops a stale second header block among the
leading lines, so a file always comes out with exactly one header.

### `spec_characteristics_lint.py`
Lint for the closing `## What a good version looks like` section of
`plots/*/specification.md`. Standard library only; it imports the parser from
Expand Down
113 changes: 113 additions & 0 deletions automation/scripts/impl_header.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
#!/usr/bin/env python3
"""Write the ``//`` header of a JavaScript implementation (impl-review).

impl-review's "Update metadata and implementation header" step stores the
review's score in a four-line comment header at the top of every
implementation file. For JavaScript the header is::

// anyplot.ai
// {spec_id}: {title}
// Library: {library} {version} | JavaScript {node version}
// Quality: {score}/100 | Created: {date}

A JavaScript file can also carry ``//#`` directive lines that the render
harness reads (``//# anyplot-orientation: square``). They are not header
lines and always survive the rewrite. ``replace_js_header`` writes one shape
whatever the file looked like before:

1. the new header on line 1, as in the Python, R and Julia headers;
2. directly below it, every directive that sat above the old header, in order;
3. the rest of the file as it was, minus any further ``// anyplot.ai`` header
block among the leading lines.

A *header block* is a run of ``//`` comment lines that ends with its first
``// Quality:`` line, or the whole run when it has none. Comment lines after
the ``Quality:`` line are code comments and stay.

The leading lines are blank lines, directive lines and header blocks up to
the first other line. A ``// anyplot.ai`` block there below the header is a
stale copy: until 2026-10 the writer stopped at a directive on line 1,
prepended the new header above it and left the old header below the
directive, with its old score and date. It is dropped together with the
blank lines directly above it, so a file comes out with exactly one header
however often it is rewritten. Directives and blank lines below the header
keep their place.

``impl-review.yml`` imports this file from the copy its "Checkout PR code"
step saves at the workflow's own ref, so a PR branched off an older ``main``
gets the current writer. Stdlib only.
"""

from __future__ import annotations

import re


HEADER_START_RE = re.compile(r"//[ \t]*anyplot\.ai[ \t]*")
QUALITY_LINE_RE = re.compile(r"//[ \t]*Quality:.*")


def _body(line: str) -> str:
return line.rstrip("\r\n")


def is_directive(line: str) -> bool:
"""A ``//#`` line: an anyplot directive the render harness reads."""
return line.startswith("//#")


def _is_comment(line: str) -> bool:
return line.startswith("//") and not is_directive(line)


def _is_blank(line: str) -> bool:
return not line.strip()


def _header_block_end(lines: list[str], start: int) -> int:
"""Index after the header block at ``start``; ``start`` without one.

The block runs over ``//`` comment lines to its first ``// Quality:``
line, or over the whole run when the run has no such line.
"""
end = start
while end < len(lines) and _is_comment(lines[end]):
end += 1
if QUALITY_LINE_RE.fullmatch(_body(lines[end - 1])):
break
return end


def replace_js_header(content: str, new_header: str) -> str:
"""Return ``content`` with ``new_header`` as its only header block.

``new_header`` is the four header lines without a final newline. See the
module docstring for the shape; a file without a header gets the header
prepended, and running the function twice gives the same text.
"""
lines = content.splitlines(keepends=True)
i = 0
above: list[str] = []
while i < len(lines) and is_directive(lines[i]):
above.append(lines[i])
i += 1
i = _header_block_end(lines, i)

kept: list[str] = []
blanks: list[str] = []
while i < len(lines):
line = lines[i]
if _is_blank(line):
blanks.append(line)
i += 1
elif is_directive(line):
kept += blanks
blanks = []
kept.append(line)
i += 1
elif HEADER_START_RE.fullmatch(_body(line)):
blanks = []
i = _header_block_end(lines, i)
else:
break
return new_header + "\n" + "".join(above) + "".join(kept) + "".join(blanks) + "".join(lines[i:])
9 changes: 5 additions & 4 deletions automation/scripts/regen_gate.py
Original file line number Diff line number Diff line change
Expand Up @@ -804,10 +804,11 @@ def set_header_score(text: str, score: int) -> str:
Only the number changes: the ``Created:`` / ``Updated:`` tail stays, and
so does everything else. Whole header lines in the leading lines only,
line count unchanged; a file without a header is returned as it is. Only
the first header line is rewritten, the one ``header_score`` reads: a few
JavaScript files carry a stale second header below an ``//#`` directive
(the header writer's prepend fallback), which keeps its number, so a
write-back changes exactly one line (``regen_writeback.verify-diff``).
the first header line is rewritten, the one ``header_score`` reads, so a
write-back changes exactly one line (``regen_writeback.verify-diff``) even
in a file with a second header: until 2026-10 impl-review's JavaScript
writer left a stale one below an ``//#`` directive. ``impl_header.py`` now
drops such a block, and the catalogue was cleaned of it.
"""
if not _is_int(score) or not 0 <= score <= 100:
raise ValueError(f"score must be an integer 0-100 (got {score!r})")
Expand Down
12 changes: 12 additions & 0 deletions changelog.d/js-header-directive.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
### Fixed

- **JavaScript implementations get exactly one header.** impl-review's header
writer stopped at a `//# anyplot-orientation:` directive on line 1 and
prepended the new header above it, so the generator's header stayed below
the directive with its old score, date and library line, and the site's code
view showed both. The writer now lives in `automation/scripts/impl_header.py`:
the header goes on line 1, a directive that sat above it moves directly below
it, and a stale second header block among the leading lines is dropped. The
five JavaScript library prompts now place the directive directly below the
header. The stale block was removed from the 182 catalogue files that carried
it (139 muix, 19 Highcharts, 14 ECharts, 9 D3, 1 Chart.js). (#12299)
4 changes: 0 additions & 4 deletions plots/alluvial-opinion-flow/implementations/javascript/d3.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,6 @@
// Library: d3 7.9.0 | JavaScript 22.23.2
// Quality: 91/100 | Created: 2026-08-26
//# anyplot-orientation: landscape
// anyplot.ai
// alluvial-opinion-flow: Opinion Flow Diagram
// Library: d3 7.9.0 | JavaScript 22
// Quality: pending | Created: 2026-08-26

const t = window.ANYPLOT_TOKENS;
const { width, height } = window.ANYPLOT_SIZE;
Expand Down
5 changes: 0 additions & 5 deletions plots/arc-basic/implementations/javascript/highcharts.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,6 @@
// Library: highcharts 12.6.0 | JavaScript 22.23.2
// Quality: 89/100 | Created: 2026-08-24
//# anyplot-orientation: landscape
// anyplot.ai
// arc-basic: Basic Arc Diagram
// Library: Highcharts 12.6.0 | Node 22
// License: Highcharts — commercial license, free for non-commercial use (highcharts.com/license)
// Quality: pending | Created: 2026-08-24
const t = window.ANYPLOT_TOKENS;

// --- Data (in-memory, deterministic) ----------------------------------------
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,6 @@
// Library: muix 7.29.1 | JavaScript 22.22.3
// Quality: 90/100 | Created: 2026-06-10
//# anyplot-orientation: landscape
// anyplot.ai
// area-elevation-profile: Terrain Elevation Profile Along Transect
// Library: muix 7.29.1 | JavaScript 22
// License: @mui/x-charts — MIT (community). Pro/Premium are out of scope.
// Quality: pending | Created: 2026-06-10

import { LineChart } from "@mui/x-charts/LineChart";
import { ChartsReferenceLine } from "@mui/x-charts/ChartsReferenceLine";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,6 @@
// Library: muix 7.29.1 | JavaScript 22.23.2
// Quality: 90/100 | Created: 2026-08-20
//# anyplot-orientation: landscape
// anyplot.ai
// area-mountain-panorama: Mountain Panorama Profile with Labeled Peaks
// Library: muix 7.29.1 | JavaScript 22
// License: @mui/x-charts — MIT (community). Pro/Premium are out of scope.
// Quality: pending | Created: 2026-08-20

import { LineChart } from "@mui/x-charts/LineChart";

Expand Down
5 changes: 0 additions & 5 deletions plots/audiogram-clinical/implementations/javascript/muix.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,6 @@
// Library: muix 7.29.1 | JavaScript 22.22.3
// Quality: 91/100 | Created: 2026-06-15
//# anyplot-orientation: square
// anyplot.ai
// audiogram-clinical: Clinical Audiogram
// Library: MUI X Charts | React | Node 22
// License: @mui/x-charts — MIT (community). Pro/Premium are out of scope.
// Quality: pending | Created: 2026-06-15
import * as React from "react";
import { LineChart } from "@mui/x-charts/LineChart";
import { useDrawingArea, useYScale } from "@mui/x-charts/hooks";
Expand Down
5 changes: 0 additions & 5 deletions plots/bar-3d-categorical/implementations/javascript/muix.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,6 @@
// Library: muix 7.29.1 | JavaScript 22.23.2
// Quality: 95/100 | Created: 2026-09-04
//# anyplot-orientation: landscape
// anyplot.ai
// bar-3d-categorical: 3D Bar Chart for Categorical Comparison
// Library: MUI X Charts | React | Node 22
// License: @mui/x-charts — MIT (community). Pro/Premium are out of scope.
// Quality: 88/100 | Created: 2026-09-04
import { ChartContainer } from "@mui/x-charts/ChartContainer";
import { ChartsXAxis } from "@mui/x-charts/ChartsXAxis";
import { ChartsYAxis } from "@mui/x-charts/ChartsYAxis";
Expand Down
5 changes: 0 additions & 5 deletions plots/bar-error/implementations/javascript/muix.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,6 @@
// Library: muix 7.29.1 | JavaScript 22.23.2
// Quality: 90/100 | Created: 2026-09-02
//# anyplot-orientation: landscape
// anyplot.ai
// bar-error: Bar Chart with Error Bars
// Library: MUI X Charts | React | Node 22
// License: @mui/x-charts — MIT (community). Pro/Premium are out of scope.
// Quality: pending | Created: 2026-09-02
import { ChartContainer } from "@mui/x-charts/ChartContainer";
import { BarPlot } from "@mui/x-charts/BarChart";
import { ChartsXAxis } from "@mui/x-charts/ChartsXAxis";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,6 @@
// Library: muix 7.29.1 | JavaScript 22.22.3
// Quality: 91/100 | Created: 2026-06-14
//# anyplot-orientation: landscape
// anyplot.ai
// bar-heart-rate-zones: Time in Heart Rate Zones Bar Chart
// Library: MUI X Charts | React | Node 22
// License: @mui/x-charts — MIT (community). Pro/Premium are out of scope.
// Quality: pending | Created: 2026-06-14

import { BarChart } from "@mui/x-charts/BarChart";
import Box from "@mui/material/Box";
Expand Down
5 changes: 0 additions & 5 deletions plots/bar-pareto/implementations/javascript/muix.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,6 @@
// Library: muix 7.29.1 | JavaScript 22.22.3
// Quality: 88/100 | Created: 2026-06-20
//# anyplot-orientation: landscape
// anyplot.ai
// bar-pareto: Pareto Chart with Cumulative Line
// Library: MUI X Charts | React | Node 22
// License: @mui/x-charts — MIT (community). Pro/Premium are out of scope.
// Quality: pending | Created: 2026-06-20

import { ChartContainer } from "@mui/x-charts/ChartContainer";
import { BarPlot } from "@mui/x-charts/BarChart";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,6 @@
// Library: muix 7.29.1 | JavaScript 22.23.2
// Quality: 92/100 | Created: 2026-08-26
//# anyplot-orientation: landscape
// anyplot.ai
// bar-permutation-importance: Permutation Feature Importance Plot
// Library: MUI X Charts | React | Node 22
// License: @mui/x-charts — MIT (community). Pro/Premium are out of scope.
// Quality: pending | Created: 2026-08-26

import { ChartContainer } from "@mui/x-charts/ChartContainer";
import { BarPlot } from "@mui/x-charts/BarChart";
Expand Down
5 changes: 0 additions & 5 deletions plots/bifurcation-basic/implementations/javascript/muix.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,6 @@
// Library: muix 7.29.1 | JavaScript 22.22.3
// Quality: 91/100 | Created: 2026-06-17
//# anyplot-orientation: landscape
// anyplot.ai
// bifurcation-basic: Bifurcation Diagram for Dynamical Systems
// Library: MUI X Charts | React | Node 22
// License: @mui/x-charts — MIT (community). Pro/Premium are out of scope.
// Quality: pending | Created: 2026-06-17

import { ChartContainer } from "@mui/x-charts/ChartContainer";
import { ScatterPlot } from "@mui/x-charts/ScatterChart";
Expand Down
5 changes: 0 additions & 5 deletions plots/biplot-pca/implementations/javascript/muix.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,6 @@
// Library: muix 7.29.1 | JavaScript 22.23.2
// Quality: 88/100 | Created: 2026-09-01
//# anyplot-orientation: landscape
// anyplot.ai
// biplot-pca: PCA Biplot with Scores and Loading Vectors
// Library: MUI X Charts | React | Node 22
// License: @mui/x-charts — MIT (community). Pro/Premium are out of scope.
// Quality: pending | Created: 2026-09-01
import { ChartContainer } from "@mui/x-charts/ChartContainer";
import { ScatterPlot } from "@mui/x-charts/ScatterChart";
import { ChartsXAxis } from "@mui/x-charts/ChartsXAxis";
Expand Down
5 changes: 0 additions & 5 deletions plots/bubble-packed/implementations/javascript/muix.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,6 @@
// Library: muix 7.29.1 | JavaScript 22.23.2
// Quality: 82/100 | Created: 2026-08-24
//# anyplot-orientation: square
// anyplot.ai
// bubble-packed: Basic Packed Bubble Chart
// Library: MUI X Charts | React | Node 22
// License: @mui/x-charts — MIT (community). Pro/Premium are out of scope.
// Quality: 79/100 | Created: 2026-08-24
import { ChartContainer } from "@mui/x-charts/ChartContainer";

const t = window.ANYPLOT_TOKENS;
Expand Down
5 changes: 0 additions & 5 deletions plots/candlestick-basic/implementations/javascript/muix.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,6 @@
// Library: muix 7.29.1 | JavaScript 22.23.2
// Quality: 87/100 | Created: 2026-08-24
//# anyplot-orientation: landscape
// anyplot.ai
// candlestick-basic: Basic Candlestick Chart
// Library: MUI X Charts | React | Node 22
// License: @mui/x-charts — MIT (community). Pro/Premium are out of scope.
// Quality: pending | Created: 2026-08-24

import { ChartContainer } from "@mui/x-charts/ChartContainer";
import { ChartsXAxis } from "@mui/x-charts/ChartsXAxis";
Expand Down
Loading
Loading