Skip to content

Ship generated types, fix Legend scale prop, publish as @dave-hillier/replot - #138

Merged
dave-hillier merged 2 commits into
mainfrom
worktree-fix-135-136
Jul 29, 2026
Merged

Ship generated types, fix Legend scale prop, publish as @dave-hillier/replot#138
dave-hillier merged 2 commits into
mainfrom
worktree-fix-135-136

Conversation

@dave-hillier

Copy link
Copy Markdown
Owner

Closes #135, closes #136, closes #137.

Found while building a Vite + React consumer app against replot; all three are things that only show up from outside the repo.

#135 — generated declarations

tsconfig.build.json emits declaration-only output into dist/types, wired into bundle (and so prepublishOnly) via a new build:types script. scripts/copy-declarations.mjs overlays the 35 hand-written .d.ts on top, because tsc infers much weaker declarations from the plain .js sources — it deliberately skips src/react/index.d.ts, the export * from "./index.js" stub at the heart of the bug.

The types conditions now point at emitted files, so a consumer's compiler never opens replot's implementation.

Measured against a stock strict Vite template app: 263 errors before, 0 after, with tsc --listFiles confirming only dist/types/** enters the consumer's program. Verified against a real packed tarball — 144 declarations, no relative path escaping dist.

#136<Legend scale="color">

LegendProps was LegendScales, while LegendDisplay read props.scale through an as any. Adding scale as a plain intersection turned out to bless a shape that crashes, so it is now a discriminated union:

export type LegendProps =
  | (LegendScales & {scale?: undefined})
  | (LegendOptions & {scale: "color" | "opacity" | "symbol"});

LegendOptions is exactly the "no scale specs" prop set, so the plot-scoped branch keeps legend="ramp", label, columns, fill etc. Mixing the two forms previously leaked the scale string into <Swatches> through the spread and threw swatches legend requires ordinal or threshold color scale. The runtime guard is kept for untyped JS callers.

Also: an unresolvable legend now renders nothing and no longer forces figure mode, matching imperative plot.legend() returning undefined.

#137 — publishing

Published name is @dave-hillier/replot; the bare replot is taken on npm by an unrelated dormant package. Version 0.1.0-alpha.0 with publishConfig.tag: "next", so an alpha cannot accidentally become latest.

LICENSE keeps Observable's copyright and adds a derivative-work line — the port is derived from Observable Plot and that attribution stays.

Two real bugs came out of adding publint and arethetypeswrong:

  • main, module and exports["."].default all pointed at ./src/index.js, which does not exist — the file is src/index.ts. The root entry was broken for any consumer not going through tsx or the docs alias.
  • jsdelivr, unpkg and exports["."].umd pointed at dist/plot.umd.min.js, while the build writes dist/replot.umd.min.js.

Both validators now pass (publint --strict clean, attw --pack --profile esm-only green) and run as part of yarn test.

Needs a decision from you

  • .github/workflows/publish.yml is deleted. It published on release: published using secrets.NPM_TOKEN. The new release.yml publishes on a v* tag via OIDC trusted publishing with no token, and keeping both would leave two publish paths. Say the word and it comes back.
  • The CHANGELOG is still upstream Plot's 0.6.x history, so 0.1.0-alpha.0 reads as a step backwards. Left alone deliberately.
  • yarn install is still needed to write publint and @arethetypeswrong/cli into yarn.lock; the agents worked against a shared node_modules symlink and deliberately did not mutate it. The scripts use npx --yes so they work either way.

Verification

  • yarn test:mocha 1563 passing, 2 pending, 0 failing (up from 1560)
  • yarn test:tsc, yarn test:lint, yarn test:prettier, yarn docs:build all clean
  • npm pack → 326 files / 640 kB, extracted and inspected: correct scoped name, dist/types/** present, both UMD files present, no tests or docs shipped
  • Tarball installed into a fresh npm create vite --template react-ts app: tsc -b exit 0, vite build succeeds, dev server serves — no optimizeDeps workaround needed

Manual steps after merge

  1. yarn install to update yarn.lock.
  2. First publish must be manual (npm login && npm publish) — trusted publishing can only be configured against a package that already exists.
  3. npmjs.com → package → Settings → Trusted publishing → GitHub Actions, dave-hillier/replot, workflow release.yml.
  4. Tag v0.1.0-alpha.1 and confirm the workflow publishes without NPM_TOKEN; then delete the NPM_TOKEN secret.

../replot-vite-example is untouched; it needs the dependency name and its from "replot/react" imports updating once this lands.

🤖 Generated with Claude Code

dave-hillier and others added 2 commits July 29, 2026 08:45
…/replot

Closes #135, #136, #137.

#135 — emit real declarations instead of resolving types to sources.
tsconfig.build.json emits declaration-only output into dist/types, and
scripts/copy-declarations.mjs overlays the 35 hand-written .d.ts that tsc
would otherwise infer weakly from the plain .js sources. The exports
"types" conditions now point there, so a consumer's compiler never opens
replot's implementation. Measured against a strict Vite template app:
263 errors before, 0 after, with tsc --listFiles confirming only
dist/types enters the program.

#136 — LegendProps was missing the plot-scoped scale prop, which
LegendDisplay read through an as-any cast. It is now a discriminated
union, so the plot-scoped and standalone forms cannot be mixed: that
combination leaked the scale string into <Swatches> via the spread and
threw at render. The runtime guard is kept for untyped callers, and an
unresolvable legend now renders nothing and no longer forces figure
mode, matching imperative plot.legend().

#137 — publish as @dave-hillier/replot; the bare name is taken on npm by
an unrelated package. Metadata moves off Observable's while keeping the
upstream ISC copyright in LICENSE. publint caught that main, module and
the root default condition all pointed at src/index.js, which does not
exist — the file is src/index.ts, so the root entry was broken for any
consumer not going through tsx or the docs alias. The UMD paths pointed
at dist/plot.umd.min.js while the build writes dist/replot.umd.min.js.
Both fixed, with publint and arethetypeswrong wired into yarn test.

Verified by packing a real tarball and installing it into a fresh Vite
React app: tsc -b and vite build both pass, with no optimizeDeps
workaround needed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
CI runs yarn --frozen-lockfile, which fails until the two new
devDependencies are resolved. Generated in a scratch copy so the
shared node_modules was left alone.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@dave-hillier
dave-hillier marked this pull request as ready for review July 29, 2026 18:42
@dave-hillier
dave-hillier merged commit a37738f into main Jul 29, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant