Skip to content

feat(ggplot2): add R/ggplot2 library + multi-language pipeline#6944

Merged
MarkusNeusinger merged 7 commits into
mainfrom
claude/add-ggplot2-library-wtS6I
May 16, 2026
Merged

feat(ggplot2): add R/ggplot2 library + multi-language pipeline#6944
MarkusNeusinger merged 7 commits into
mainfrom
claude/add-ggplot2-library-wtS6I

Conversation

@MarkusNeusinger
Copy link
Copy Markdown
Owner

Summary

First non-Python library in the catalog — wires anyplot for R/ggplot2 end to end. Tier 2 / Phase 3 of docs/concepts/library-expansion.md: unlocks the R audience and validates the multi-language pipeline on a non-JS runtime.

After this lands:

gh workflow run impl-generate.yml -f specification_id=<spec-id> -f library=ggplot2

produces R code and renders plot-light.png / plot-dark.png through the existing review/merge pipeline.

What's added

Library + language registry

  • core/constants.py: SUPPORTED_LANGUAGES gains "r", LANGUAGES_METADATA gains the R entry (.R, runtime 4.4). SUPPORTED_LIBRARIES + LIBRARIES_METADATA gain ggplot2 (language_id: "r", version 3.5.1)
  • prompts/library/ggplot2.md: ggplot2-specific generation rules — Okabe-Ito mapping, theme-adaptive chrome tokens, ANYPLOT_THEME env handling, ragg PNG device, NOT_FEASIBLE policy for interactivity

R runtime in CI

  • .github/actions/setup-r: composite action installing R + system libs for ragg/systemfonts, restoring packages from renv.lock, and smoke-testing a ggplot2 render
  • renv.lock: pinned ggplot2 + tidyverse helpers + dataset packages (palmerpenguins, gapminder) against a Posit RSPM snapshot

Workflows (extension- and language-aware)

  • impl-generate.yml: ggplot2 added to library choices; LANGUAGE + EXT derived from LIBRARY; R setup step conditional on language; version detection via Rscript packageVersion() for R; all .py paths replaced by ${LIBRARY}${EXT}
  • impl-repair.yml: same derive-lang step; conditional R setup; conditional Python plotting deps; extension-aware paths and prompt vars
  • impl-review.yml: derives language + ext from library; staging GCS path, metadata path, and impl-file path all use the derived values
  • impl-merge.yml: branch parser derives LANGUAGE + EXT from LIBRARY; validates correct impl file path
  • bulk-generate.yml: ggplot2 added to choices and ALL_LIBRARIES
  • prompts/workflow-prompts/impl-generate-claude.md + impl-repair-claude.md: target paths and run commands made extension-aware (Python uses python, R uses Rscript)
  • prompts/plot-generator.md: role/wording generalised; R-specific datasets, reproducibility seed, docstring style, forbidden-patterns list added

Frontend

  • CodeHighlighter: registers Prism r grammar, accepts a language prop, falls back to plain text for unknown languages
  • SpecTabs + SpecPage: pipe currentImpl.language through so R snippets render with R highlighting

Tests

  • tests/unit/core/test_constants.py: ggplot2 entry, R language metadata, language_id integrity across libraries (TestSupportedLanguages is new)
  • tests/unit/api/test_debug.py + test_routers.py: library_stats length assertions switched to len(SUPPORTED_LIBRARIES) so adding a new library doesn't require a test edit
  • app/src/components/CodeHighlighter.test.tsx: covers r language + plain-text fallback

Notes / open follow-ups

  • renv.lock is committed without per-package Hash fields. renv::restore() falls back to installing the pinned Version from the RSPM snapshot, so reproducibility comes from the URL pin. Anyone who runs renv::snapshot() locally after the first successful CI run can commit the hashes for proper integrity verification.
  • After merge, run uv run python -m automation.scripts.label_manager sync once so the new library:ggplot2 / generate:ggplot2 / impl:ggplot2:{pending,done,failed} labels appear in the repo.
  • Highcharts JS migration (Phase 2 in docs/concepts/library-expansion.md) is a separate piece of work and not touched here.

Test plan

  • uv run pytest tests/unit — 1435 passed
  • uv run pytest tests/integration — 51 passed
  • uv run ruff check core/constants.py tests/unit/... — clean
  • python3 -c "import yaml; ..." — all 5 changed workflows + the composite action parse
  • python3 -c "import json; json.load(open('renv.lock'))" — parses
  • CI: ci-lint.yml, ci-tests.yml (frontend tsc + vitest run in CI)
  • Manual smoke once merged: gh workflow run impl-generate.yml -f specification_id=scatter-basic -f library=ggplot2 and verify R setup + render

https://claude.ai/code/session_01Kb7b7QZi3ohtSTbd39poFV


Generated by Claude Code

Wires anyplot for its first non-Python implementation language. Tier 2 / Phase 3 of the library-expansion roadmap — unlocks the R / academic audience and validates the multi-language pipeline.

What's added
- core/constants.py: SUPPORTED_LANGUAGES gains "r", LANGUAGES_METADATA gains the R entry (.R extension, runtime 4.4), SUPPORTED_LIBRARIES + LIBRARIES_METADATA gain ggplot2 with language_id=r
- prompts/library/ggplot2.md: ggplot2-specific generation rules — Okabe-Ito mapping, theme-adaptive chrome tokens, ANYPLOT_THEME env handling, ragg PNG device, NOT_FEASIBLE policy for interactivity
- .github/actions/setup-r: composite action installing R + system libs for ragg/systemfonts, restoring packages from renv.lock, and smoke-testing a ggplot2 render
- renv.lock: pinned ggplot2 + tidyverse helpers + dataset packages (palmerpenguins, gapminder) against a Posit RSPM snapshot
- Frontend CodeHighlighter: registers Prism r grammar, accepts a language prop, falls back to plain text for unknown languages

Workflow changes (extension- and language-aware)
- impl-generate.yml: ggplot2 added to library choices; LANGUAGE + EXT derived from LIBRARY; R setup step conditional on language; version detection via Rscript packageVersion for R libs; all .py paths replaced by ${LIBRARY}${EXT}
- impl-repair.yml: same derive_lang step; conditional R setup; conditional Python plotting deps; extension-aware paths and prompt vars
- impl-review.yml: derives language + ext from library; staging GCS path, metadata path, and impl-file path all use the derived values
- impl-merge.yml: branch parser derives LANGUAGE + EXT from LIBRARY; validates correct impl file path
- bulk-generate.yml: ggplot2 added to choices and ALL_LIBRARIES
- prompts/workflow-prompts/impl-generate-claude.md + impl-repair-claude.md: target paths and run commands made extension-aware (Python uses python, R uses Rscript)
- prompts/plot-generator.md: role/wording generalised; R-specific datasets, reproducibility seed, docstring style, forbidden-patterns list added

Tests
- tests/unit/core/test_constants.py: covers ggplot2 entry, R language metadata, language_id integrity across libraries
- tests/unit/api/test_debug.py + test_routers.py: library_stats length assertions switched to len(SUPPORTED_LIBRARIES) so adding a new library doesn't require a test edit
- app/src/components/CodeHighlighter.test.tsx: covers r language + plain-text fallback

After this lands, gh workflow run impl-generate.yml -f specification_id=<spec> -f library=ggplot2 produces R code and renders plot-light.png / plot-dark.png through the existing review/merge pipeline.
Copilot AI review requested due to automatic review settings May 16, 2026 19:56
@codecov
Copy link
Copy Markdown

codecov Bot commented May 16, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

bulk-generate.yml's ALL_LIBRARIES is now 10 (ggplot2 added in the parent commit), and any future addition would re-stale this string.
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces the first non-Python plotting library (R/ggplot2) and extends the implementation generation/review/merge pipeline to be language- and extension-aware, enabling end-to-end generation and rendering for multi-language implementations.

Changes:

  • Add R + ggplot2 to the backend registry (SUPPORTED_LANGUAGES, LIBRARIES_METADATA) and extend unit tests accordingly.
  • Update GitHub Actions workflows and Claude workflow prompts to derive {LANGUAGE} + {EXT} from {LIBRARY} and use Rscript/.R where applicable.
  • Update the frontend code viewer to syntax-highlight R code and gracefully handle unknown languages.

Reviewed changes

Copilot reviewed 19 out of 20 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
core/constants.py Adds r language metadata and ggplot2 library metadata (incl. language_id) plus extension mapping.
tests/unit/core/test_constants.py Updates constants tests for new language/library metadata and validation.
tests/unit/api/test_routers.py Makes library_stats assertions resilient by tying expected length to SUPPORTED_LIBRARIES.
tests/unit/api/test_debug.py Same as above for debug status assertions.
renv.lock Adds pinned R package set (ggplot2 + deps + datasets) for CI restores via renv.
prompts/library/ggplot2.md Introduces ggplot2-specific generation rules, theming tokens, and ragg PNG guidance.
prompts/plot-generator.md Generalizes plot generation guidance for multi-language and adds R-specific conventions/datasets.
prompts/workflow-prompts/impl-generate-claude.md Makes target paths/commands extension-aware and adds R execution instructions.
prompts/workflow-prompts/impl-repair-claude.md Same as above for repair flow (incl. R commands).
app/src/components/CodeHighlighter.tsx Registers Prism R grammar and supports a language prop with fallback behavior.
app/src/components/CodeHighlighter.test.tsx Adds tests for R highlighting selection and unknown-language fallback.
app/src/components/SpecTabs.tsx Threads language through to CodeHighlighter.
app/src/pages/SpecPage.tsx Passes implementation language (or URL/default) into SpecTabs for correct highlighting.
.github/actions/setup-r/action.yml Adds composite action to install R, system deps, restore renv, and smoke-test PNG rendering.
.github/workflows/impl-generate.yml Adds ggplot2 option; derives language/ext; conditionally sets up R; uses ${LIBRARY}${EXT} paths.
.github/workflows/impl-repair.yml Derives language/ext; conditionally sets up R and Python plotting deps; uses extension-aware paths.
.github/workflows/impl-review.yml Derives language/ext; updates staging and impl paths to include language/ext.
.github/workflows/impl-merge.yml Derives language/ext from branch library; validates impl path using ${LIBRARY}${EXT}.
.github/workflows/bulk-generate.yml Adds ggplot2 to choices and ALL_LIBRARIES.

Comment thread tests/unit/core/test_constants.py Outdated
Comment on lines +33 to +34
"""Should contain every catalog library (8 Python + ggplot2)."""
assert len(SUPPORTED_LIBRARIES) == 10
Comment thread .github/workflows/impl-generate.yml Outdated
Comment on lines +458 to +465
# Runtime version: Python for python libs, R for R libs. We still record
# a 'python_version' field in metadata for backwards compatibility; for R
# implementations it carries the R version instead (the DB / frontend treat
# it as a free-form runtime version string).
if [ "$LANGUAGE" = "r" ]; then
PYTHON_VERSION=$(Rscript -e 'cat(as.character(getRversion()))' 2>/dev/null || echo "unknown")
else
PYTHON_VERSION=$(python3 --version 2>&1 | awk '{print $2}')
Comment on lines 368 to +377
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SPEC_ID: ${{ steps.pr.outputs.specification_id }}
LIBRARY: ${{ steps.pr.outputs.library }}
LANGUAGE: python
LANGUAGE: ${{ steps.lang.outputs.language }}
EXT: ${{ steps.lang.outputs.ext }}
SCORE: ${{ steps.score.outputs.score }}
BRANCH: ${{ steps.pr.outputs.branch }}
run: |
METADATA_FILE="plots/${SPEC_ID}/metadata/${LANGUAGE}/${LIBRARY}.yaml"
IMPL_FILE="plots/${SPEC_ID}/implementations/${LANGUAGE}/${LIBRARY}.py"
IMPL_FILE="plots/${SPEC_ID}/implementations/${LANGUAGE}/${LIBRARY}${EXT}"
claude added 2 commits May 16, 2026 20:15
Three Copilot review comments resolved.

1. python_version no longer abused for R version (impl-generate.yml comment)
   - Adds a new language_version column to impls (Alembic migration 3a7e1b5c0c4f), backfilled from python_version for existing rows.
   - impl-generate.yml writes both: python_version stays the pipeline Python (3.13), language_version carries the implementation's own runtime — Python for python libs, R for ggplot2.
   - sync_to_postgres reads language_version with a fallback to python_version so legacy rows keep displaying correctly.
   - API schemas, specs / insights / mcp routers thread the new field through.
   - PlotOfTheDay frontend renders {Python|R} {language_version || python_version} so ggplot2 entries show "R 4.4.1" instead of "Python 4.4.1".

2. R header rewrite in impl-review.yml (was Python-only)
   - The "Update implementation header" step now branches on LANGUAGE: Python files still get a triple-quoted docstring, R files get a roxygen-style #' block and "R {version}" runtime label.
   - If the R file has no existing #' header (e.g. first review pass), one is prepended instead of silently no-op'ing.

3. test_constants docstring lied about library count
   - "8 Python + ggplot2" claimed 9 libs but there are 10 (9 Python + 1 R). Length assertion was also redundant with test_contains_expected_libraries, which already checks the exact set.
   - Removed the redundant length assertions; the equality check on the expected set is the single source of truth.

Test fixtures (test_routers, test_tools, PlotOfTheDay tests) updated to set the new language_version attribute so Pydantic validation passes.
…uage

Audit pass on PR #6944. Adds R/ggplot2 coverage to the surface areas the parent commits missed.

Docs (no behavioural change, only counts and wording)
- README, agentic/docs/project-guide.md, docs/concepts/vision.md, docs/concepts/library-expansion.md, docs/reference/style-guide.md, docs/reference/tagging-system.md, docs/reference/repository.md, prompts/README.md: every "9 libraries" / "nine ecosystems" line either generalised to "10 libraries (Python + R)" or rewritten so the count is derived from the canonical list.
- library-expansion.md: Phase 3 marked as shipped in the rollout table; current-state line now reads "9 Python + 1 R".

Prompts (extend Python-only assumptions)
- prompts/quality-evaluator.md: role line generalised; code path uses {ext}; static-library lists include ggplot2.
- prompts/workflow-prompts/ai-quality-review.md: implementation path uses {EXT}; static-library AR-08 check includes ggplot2.
- prompts/workflow-prompts/impl-similarity-claude.md: sibling-source paths are no longer hardcoded to python/.py; uses {language}/{library}{ext}.

Frontend
- SpecPage SEO schema.org JSON-LD: programmingLanguage now maps r -> "R" (was lowercase, which downgrades SEO).

Scripts
- scripts/evaluate-plot.py: get_plot_paths picks the right file extension per language; the CLI exits cleanly for ggplot2 (Python-only AST + Rscript runner are out of scope for the local evaluator — directs users to the CI workflow).
Copilot AI review requested due to automatic review settings May 16, 2026 20:25
The two r-lib/actions references were pinned to a SHA I fabricated. GitHub
would have rejected the workflow at first run with "unable to resolve action".

Switched to @v2 (the maintained moving tag for r-lib/actions). Dependabot
will pin it to a real SHA on its next run, consistent with the rest of the
repo's pinning policy.

Also threaded RENV_CONFIG_INSTALL_HASHES=FALSE to setup-renv so the
hand-written hash-less lockfile restores without renv refusing. The pinned
Posit RSPM snapshot URL in renv.lock provides reproducibility; once CI's
first successful run regenerates renv.lock with hashes, the flag can drop.
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 43 out of 44 changed files in this pull request and generated 2 comments.

Comment on lines 254 to 259
<Typography sx={{ fontFamily: mono, fontSize: fontSize.xxs, color: 'var(--ink-muted)', mx: 1 }}>
</Typography>
<Typography sx={{ fontFamily: mono, fontSize: fontSize.xxs, color: semanticColors.mutedText, whiteSpace: 'nowrap' }}>
{data.library_name}{data.library_version && data.library_version !== 'unknown' ? ` ${data.library_version}` : ''} · Python {data.python_version || '3.13'}
{data.library_name}{data.library_version && data.library_version !== 'unknown' ? ` ${data.library_version}` : ''} · {data.language === 'r' ? 'R' : 'Python'} {data.language_version || data.python_version || (data.language === 'r' ? '4.4' : '3.13')}
</Typography>
Comment thread scripts/evaluate-plot.py
Comment on lines 136 to 146
def get_plot_paths(spec_id: str, library: str, language: str = "python") -> dict:
"""Get all relevant paths for a plot implementation."""
plots_dir = PROJECT_ROOT / "plots" / spec_id
impl_dir = plots_dir / "implementations" / language
# File extension follows the implementation language. ggplot2 is the only
# non-Python entry today; extend this when more languages join.
ext = ".R" if language == "r" else ".py"
return {
"spec": plots_dir / "specification.md",
"impl": impl_dir / f"{library}.py",
"impl": impl_dir / f"{library}{ext}",
"metadata": plots_dir / "metadata" / language / f"{library}.yaml",
Two more review comments on commit a07b6f2:

1. PlotOfTheDay still had hardcoded `python` + `.py` in the source-link href and
   the displayed command string — only the runtime label rendering was language-
   aware. If POTD ever picks ggplot2 the GitHub link 404s and the chip lies about
   how to run the file. Now derives `ext` and `runner` from `data.language`,
   so the chip flips to `Rscript plots/.../ggplot2.R` and the GitHub URL points
   at the .R file. PlotOfTheDayTerminal had the same bug (it builds the same
   filename + GitHub URL) — fixed alongside.

2. scripts/evaluate-plot.py was duplicating the language->extension mapping with
   a local ternary and keyed the skip off `library == "ggplot2"` instead of the
   underlying language. Now imports LANGUAGE_FILE_EXTENSIONS from core.constants
   (single source of truth shared with sync_to_postgres) and skips any non-python
   library generically — automatically covers future R/JS/Julia entries without
   another edit. SUPPORTED_LIBRARIES is now derived from LIBRARIES_METADATA
   filtered by language_id=python, so it can't drift.
@MarkusNeusinger MarkusNeusinger enabled auto-merge (squash) May 16, 2026 20:39
Copilot AI review requested due to automatic review settings May 16, 2026 20:39
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 44 out of 45 changed files in this pull request and generated 5 comments.

Comment on lines +20 to +26
- name: Install R
# r-lib/actions ships every sub-action from a single repo and tags them together;
# `@v2` is the maintained moving tag. Dependabot will pin to a SHA on first run.
uses: r-lib/actions/setup-r@v2
with:
r-version: ${{ inputs.r-version }}
use-public-rspm: true
Comment on lines +48 to +52
- name: Install R packages from renv.lock
uses: r-lib/actions/setup-renv@v2
with:
working-directory: ${{ inputs.working-directory }}
env:
Comment on lines +45 to 49
// File extension follows the implementation language; ggplot2 ships as .R,
// everything else as .py.
const ext = potd.language === 'r' ? '.R' : '.py';
const displayFilename = `plots/${potd.spec_id}/${potd.library_id}${ext}`;
const implPath = specPath(potd.spec_id, potd.language, potd.library_id);
Comment thread prompts/plot-generator.md
Comment on lines +268 to 272
**Forbidden (Python):**
- Functions or classes
- `if __name__ == '__main__':`
- Type hints or docstrings (keep it simple)
- Cross-library workarounds **for plotting** (e.g., using matplotlib plotting functions inside plotnine)
Comment on lines +566 to +577
else:
# Python: triple-quoted module docstring at the top of the file.
title_safe = title.replace('"""', '\\"\\"\\"')
new_header = (
'""" anyplot.ai\n'
f"{spec_id}: {title_safe}\n"
f"Library: {library} {lib_version} | {RUNTIME_LABEL} {lang_version}\n"
f"Quality: {score}/100 | {date_info}\n"
'"""'
)
pattern = r'^""".*?"""'
new_content = re.sub(pattern, new_header, content, count=1, flags=re.DOTALL)
@MarkusNeusinger MarkusNeusinger merged commit 4da7e5b into main May 16, 2026
13 checks passed
@MarkusNeusinger MarkusNeusinger deleted the claude/add-ggplot2-library-wtS6I branch May 16, 2026 20:45
MarkusNeusinger added a commit that referenced this pull request May 16, 2026
## Summary

Three Copilot review comments arrived on #6944 just before/after it was
merged. Picking up the actionable ones in a small follow-up.

## What's fixed

**1. `PlotOfTheDayTerminal` — hardcoded `python` prompt**
(#discussion_r3253434308)
The terminal-style chip in the POTD card had a hardcoded `python` label
even though the filename now flips to `.R` for ggplot2. Output would
read `python plots/<spec>/ggplot2.R` — wrong. Now derives a `runner`
token from `potd.language` (`Rscript` for `r`, `python` otherwise)
alongside the existing `ext` switch.

**2. `prompts/plot-generator.md` — contradictory docstring rule**
(#discussion_r3253434312)
The "Forbidden (Python)" list said *"Type hints or docstrings (keep it
simple)"* while the same prompt elsewhere requires a 4-line
module-header docstring at the top of every Python impl. Contradictory
guidance can make generators omit the required header. Clarified: header
docstring is mandatory, additional docstrings are forbidden. Added the R
equivalent for roxygen `#'` blocks so the rule symmetric.

**3. `impl-review.yml` — Python header rewrite needs a prepend
fallback** (#discussion_r3253434319)
The Python branch of the header-rewrite step ran `re.sub` on a
triple-quoted docstring pattern and silently left the file unchanged if
there was no header. Mirrors the R branch behaviour now: `re.match`
first, prepend the canonical header if absent.

## Deliberately *not* in this PR

- **SHA-pinning `r-lib/actions/setup-r` and `setup-renv`**
(#discussion_r3253434287, #discussion_r3253434297). Pinning is correct
policy, but pushing a SHA I haven't verified would re-introduce the
bogus-SHA blocker that #6944 already had to fix. Dependabot will pin
them to a verified SHA on its next run, the same way every other action
ref in the repo got there.

## Test plan

- [x] `python3 -c "import yaml; ..."` on impl-review.yml — parses
- [ ] CI: ci-lint, ci-tests, frontend tsc + vitest
- [ ] One real impl-review run that exercises the Python prepend
fallback (will happen organically the next time a Python impl is
reviewed)

https://claude.ai/code/session_01Kb7b7QZi3ohtSTbd39poFV

---
_Generated by [Claude
Code](https://claude.ai/code/session_01Kb7b7QZi3ohtSTbd39poFV)_

Co-authored-by: Claude <noreply@anthropic.com>
MarkusNeusinger added a commit that referenced this pull request May 16, 2026
…anguages count (#6961)

Three R/ggplot2 rollout gaps surfaced after [PR
#6951](#6951) merged. All
show wrong data despite the DB row being correct — they were always
going to need a follow-up after the first non-Python impl landed.

## 1. Code viewer empty on `/scatter-basic/r/ggplot2`

\`ImplRepository.get_code()\` has signature \`get_code(spec_id,
library_id, language_id=\"python\")\`. The
\`/specs/{spec_id}/{library}/code\` endpoint never accepted a language
and never passed one through — so R rows (\`language_id=\"r\"\`) failed
the WHERE clause, the endpoint returned 404, and the frontend showed a
blank code panel.

\`\`\`bash
$ curl -s -o /dev/null -w \"%{http_code}\\n\"
https://anyplot.ai/api/specs/scatter-basic/matplotlib/code
200
$ curl -s -o /dev/null -w \"%{http_code}\\n\"
https://anyplot.ai/api/specs/scatter-basic/ggplot2/code
404
\`\`\`

**Fix:**
- **API:** \`get_impl_code\` accepts \`?language=\` (default
\`\"python\"\` for backwards compat). \`_build_impl_code\` and the Redis
cache key both include language so Python and R impls under the same
library_id can't collide.
- **Frontend:** \`useCodeFetch\` accepts an optional \`language\` arg,
includes it in the in-memory cache key, and only appends \`?language=\`
to the URL when it diverges from python — Python URLs and existing tests
stay unchanged.
- **SpecPage:** passes \`urlLanguage\` (already destructured from
\`useParams\`) to \`fetchCode\`/\`getCode\`, and \`impl.language\` to
the copy-to-clipboard path.

## 2. Landing strip shows \"languages: 1\"

\`NumbersStrip.tsx\` line 16 was \`{ value: '1', label: 'languages' }\`
— literally hardcoded, never wired to any stat.

**Fix:**
- **API:** add \`languages: int = 0\` to \`StatsResponse\`.
\`_refresh_stats\` and the cached \`_fetch\` factory derive it from
\`{lib.language_id for lib in libraries}\` (with a
\`len(LANGUAGES_METADATA)\` fallback when libraries are unavailable,
mirroring how libraries count already worked).
- **Frontend:** bind to \`stats.languages\` with an em-dash fallback
while loading.

## 3. ggplot2 missing from `/libraries`

\`app/src/constants/index.ts\` had:
\`\`\`ts
export const LIBRARIES = ['altair', 'bokeh', 'highcharts', 'letsplot',
'matplotlib', 'plotly', 'plotnine', 'pygal', 'seaborn'];
\`\`\`

LibrariesPage iterates this list to render cards, so ggplot2 was
silently skipped even though \`/api/libraries\` already returned it.

**Fix:** add \`'ggplot2'\` to \`LIBRARIES\` (alpha order, between bokeh
and highcharts) and \`'gg'\` to \`LIB_ABBREV\` for compact display.

## Test plan

- [x] \`useCodeFetch.test.ts\` — 12/12 green (10 existing + 2 new:
\`?language=\` is appended for non-python, separate cache keys per
language for same library_id)
- [x] \`tests/unit/api/test_stats.py\` — green, asserts \`languages ==
1\` for the mocked single-library case
- [x] \`tests/unit/api/test_schemas.py\` — green, \`languages\` field
present in \`model_dump()\`
- [x] \`tests/unit/api/test_routers.py\` — 8/8 code-endpoint tests still
pass (default-python path unchanged)
- [ ] After deploy: \`curl
https://anyplot.ai/api/specs/scatter-basic/ggplot2/code?language=r\`
returns 200 with the R source
- [ ] After deploy: \`/libraries\` shows ggplot2 card, landing strip
shows \`languages: 2\`

## Out of scope (separate issues)

- Issue #6958 — language is not shown in plot image titles
(\`scatter-basic · ggplot2 · anyplot.ai\` is ambiguous for R vs Python
libs); design decision pending
- Pre-existing TS error in \`CodeHighlighter.tsx:3\` for the R
syntax-highlighter import (\`@types/react-syntax-highlighter\` doesn't
declare \`prism/r\`) — was introduced by PR #6944, not by this PR

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
MarkusNeusinger added a commit that referenced this pull request May 17, 2026
## Why

The frontend changes from [PR
#6961](#6961) —
`languages` count on the landing strip, ggplot2 card on `/libraries`, R
code viewer on `/scatter-basic/r/ggplot2` — are merged on `main`, but
production still shows the pre-#6961 state:

```bash
$ curl -s https://anyplot.ai/assets/constants-Cv1rI8LA.js | head -c 200
…LIBRARIES=['altair','bokeh','highcharts',…]  # no ggplot2
$ curl -s https://anyplot.ai/assets/LandingPage-Bluw1eOY.js | grep -oE '.{20}languages.{20}'
…children:[{value:`1`,label:`languages`}…    # hardcoded '1'
```

All served assets carry `last-modified: Sat, 16 May 2026 20:04:15 GMT` —
about 2.5 h **before** #6961 merged. The API was redeployed (`/stats`
now returns `"languages":2,"libraries":10`), but the frontend Cloud Run
revision is stale.

## Root cause

The frontend Cloud Build (`app/cloudbuild.yaml` → `yarn build` = `tsc &&
vite build`) fails at the `tsc` step:

```
src/components/CodeHighlighter.tsx(3,15): error TS7016: Could not find a declaration
file for module 'react-syntax-highlighter/dist/esm/languages/prism/r'.
'…/react-syntax-highlighter/dist/esm/languages/prism/r.js' implicitly has an 'any' type.
```

`@types/react-syntax-highlighter` actually does ship an ambient
declaration for this exact path (line 2768 of its `index.d.ts`), but
with `moduleResolution: "bundler"` tsc resolves the import to the
sibling `.js` file first and never reaches the @types index — only `r`
trips, `python` from the line above happens to resolve through the
cached package types. Net effect: `tsc` exits non-zero, `vite build`
never runs, no new Docker layer, Cloud Run keeps serving the previous
revision.

Docker build log confirmed this:

```
ERROR: build step 0 "gcr.io/cloud-builders/docker" failed: step exited with non-zero status: 2
The command '/bin/sh -c yarn build' returned a non-zero code: 2
```

PR #6961 listed this exact TS error under "Out of scope" — it had been
latent since #6944 added the R import, and only became a deploy blocker
because #6961 was the first frontend-touching PR after the lib bumps
that affect tsc behavior.

## Fix

Add `app/src/types/react-syntax-highlighter.d.ts` declaring the missing
module locally. A project-local shim wins over `@types`, which is enough
to unblock `tsc`. The runtime import is unchanged — Prism still
registers the real R grammar via `registerLanguage('r', r)`, and a grep
over the rebuilt `CodeHighlighter-*.js` confirms the R keyword set is
bundled:

```
break|else|for|function|if|in|next|repeat|while
```

So R syntax highlighting on `/scatter-basic/r/ggplot2` works as
intended.

## Test plan

- [x] `cd app && yarn build` — green (was failing on TS7016 before)
- [x] `cd app && yarn test` — 459/459 green
- [x] New bundle hash differs from the deployed one
(`LandingPage-e58y3raq.js` vs prod `LandingPage-Bluw1eOY.js`),
confirming the redeploy will publish fresh assets
- [ ] After merge: Cloud Build for `anyplot-app` finishes green;
`https://anyplot.ai/assets/LandingPage-*.js` no longer contains the
hardcoded `{value:'1',label:'languages'}`
- [ ] After deploy: landing strip shows `languages: 2`, `/libraries`
shows the ggplot2 card, `/scatter-basic/r/ggplot2` code panel renders R
code with highlighting

## Notes

- This PR does **not** re-introduce the language count / ggplot2 /
R-code-viewer logic — those landed in #6961 and are already on main.
This only removes the build blocker so the existing fixes can actually
reach production.
- The Cloud Build "deploy-app (anyplot) ✅ Passed" check that appeared on
#6961's merge commit was from `.github/workflows/notify-deployment.yml`,
which is a no-op `echo` step that records a GitHub Deployment object —
it does not actually report the GCP Cloud Build outcome. Worth a
follow-up to either wire the real Cloud Build status back to the commit
or rename the workflow so the green check doesn't mislead.

---
_Generated by [Claude
Code](https://claude.ai/code/session_01DdvT6aZxMm5QKU4jd8afUr)_

---------

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants