Skip to content

feat: scaffold the repository from the lecture theme (Phase 0) - #12

Merged
mmcky merged 3 commits into
mainfrom
feat/scaffold
Sep 3, 2026
Merged

mmcky merged 3 commits into
mainfrom
feat/scaffold

Conversation

@mmcky

@mmcky mmcky commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Part of #3 (Phase 0).

Source

Copied from QuantEcon/quantecon-theme.mystmd at 47e37ed885a40afb4fa42dd27ad458357616f68e — the clone's main at copy time, which is the same commit issue #3 names. Decision D1 makes this repository a self-contained sibling of the lecture theme rather than a package sharing its code, so the scaffold is copied once and then owned here: a later fix in one repository does not reach the other.

What was copied

Area Files
Workflows ci.yml, preview.yml, release.yml, update-snapshots.yml
Release bundle Makefile, template.yml, template/ (package.json, server.js, README.md, LICENSE, .gitignore, thumbnail.png)
Build config package.json, tsconfig.json, .eslintrc.js, remix.config.dev.js, remix.config.prod.js, remix.env.d.ts, server.js, tailwind.config.js, .nvmrc, .npmrc, .gitignore additions
Build scripts scripts/relative-css-asset-urls.mjs (verbatim)
Remix app app/root.tsx (with the inlined critical CSS), app/entry.client.tsx, app/entry.server.tsx, app/links.ts, app/backend/loaders.server.ts, app/components/{Page,ErrorPage}.tsx, app/routes/ (index, catch-all, api.theme, myst-theme[.css], [favicon.ico], [robots.txt])
Styles styles/app.css
Test harness playwright.config.ts, tests/visual/serve.sh, tests/visual/fouc.spec.ts, tests/visual/theme.spec.ts, tests/visual/README.md, tests/visual/fixture/, tests/unit/template-stamp.test.mjs
Docs CHANGELOG.md, CONTRIBUTING.md, SECURITY.md, a Development section in README.md

What was renamed

From To
@quantecon/lecture-theme @quantecon/report-theme (root and template/package.json)
version 2.3.1 0.1.0
quantecon-theme.zip quantecon-theme-report.zip
title: QuantEcon Lecture Theme title: QuantEcon Report Theme
THEME = quantecon-theme THEME = quantecon-theme-report
.deploy/quantecon-theme, dist/quantecon-theme .deploy/quantecon-theme-report, dist/quantecon-theme-report

grep -rn over the working tree (excluding node_modules/, .deploy/, build/, public/) finds no remaining occurrence of @quantecon/lecture-theme, QuantEcon Lecture Theme, or a bare quantecon-theme zip/package name. The only hits are the literal strings inside tests/unit/template-stamp.test.mjs, which is the guard that keeps it that way, and deliberate prose/comment references to the lecture repository (quantecon-theme.mystmd) recording where something came from.

What was adapted, and why

release.yml attaches two assets. The theme zip and plugins/compliance.mjs, per the issue. The plugin does not exist until Phase 1b (#5), so a dedicated Check for the compliance plugin bundle step emits a ::warning:: naming that issue when the file is absent, and fail_on_unmatched_files: false is set explicitly with a comment saying to flip it to true once #5 lands. A release that silently dropped the plugin would leave consumers pinning a 404, so the absence is loud rather than invisible. The tag-matches-package.json and changelog-section guards are unchanged.

preview.yml falls back to the fixture. It targets QuantEcon/compliance-lecture-style's mystmd branch, which does not exist yet — I checked: git ls-remote --heads on that repository returns main only. The job probes for the branch with git ls-remote on every run and, when it is missing, copies tests/visual/fixture and builds that instead, emitting a ::notice:: that points at compliance-lecture-style#28. The day the branch appears the preview switches to real content with no change to this file.

No live-compute stack. @myst-theme/jupyter, the build:thebe step in prod:build, and all three patches/ entries (@jupyter-widgets/controls, @myst-theme/jupyter, jupyterlab-plotly) exist to make executable notebook cells work. Report sites have no code cells, so all of it is dropped — which also removes patch-package and the postinstall hook. prod:build is therefore prod:copy && build:css && remix build && relative-css-asset-urls.mjs. This is the "adapt the scripts and say what you changed" case from the brief.

test:unit has a new, real test. The lecture theme's unit suite guards that its patches applied; with no patches, that suite has nothing to assert, and node --test errors on a glob matching no files. tests/unit/template-stamp.test.mjs replaces it with a guard over the thing this repository actually stamps: the VERSION placeholder in template/package.json, the line-initial version: in template.yml, package.json/template.yml agreement, the Makefile-and-workflow zip name, and the no-leaked-lecture-names check above. It runs in ci.yml and again in release.yml before the artifact is built, exactly as the lecture theme's did.

theme.spec.ts asserts structure, not pixels. Phase 0 has no report UI: the app renders article content in the centred grid and nothing else, so screenshot baselines would lock in an empty page and be thrown away in Phase 2 (#6). Pixel baselines also cannot be seeded from here — they are platform-suffixed and CI needs -linux ones. So the visual job runs a rendering suite that is green from the first PR, and the platform-suffixed snapshotPathTemplate, the desktop-chrome/mobile-chrome projects and the whole /update-snapshots path stay wired up for the Phase 3 harness (#9) to seed with /update-new-snapshots.

FOUC guard trimmed to what exists. The grid-not-collapsed and font-stack assertions are kept verbatim, along with the control case. The lecture theme's third assertion — that the off-canvas contents sidebar is parked off-screen on first paint — is dropped with the sidebar itself; the comment says it returns with the Phase 2 shell, together with its translateX(-100%) critical-CSS rule.

Single fixture, so a single webServer. The lecture harness serves a second Thebe-disabled fixture on a second port to assert the live-compute toggle is absent. With no compute there is nothing to assert, so NO_THEBE_PORT and the second webServer entry are gone.

Every route the static export path crawls is kept. I first trimmed the app to the routes a live myst start needs, and the preview job caught that: myst build --html also fetches per-page .json, sitemap.xml, sitemap_style.xsl and objects.inv, and it fails hard when they 404. The second commit restores those four routes and their loaders. getMystSearchJson is typed as a plain JSON object rather than MystSearchIndex, whose package (@myst-theme/search) the lecture theme never declared as a direct dependency either.

Two dependency overrides not carried across. ws — both copies in this tree (7.5.13, 8.21.3) are already above their patched floors and nothing is flagged against them. sanitize-html — I tested the lecture theme's exact 2.17.5 pin here and it does not clear the package's advisories, while raising the total alert count from 69 to 71 by pulling more paths onto the forced copy, so it is omitted and deferred instead. Everything else in the table is carried across and each entry was checked to resolve in this tree. SECURITY.md records the full triage against a real npm audit run rather than the lecture theme's numbers.

template.yml author as an object. name: QuantEcon as a plain string makes mystmd try to split an organisation into given/family and warn on every build; name: {literal: QuantEcon} is the documented escape and the fixture build is now warning-free.

Verified locally

Node here is v26.8.1 while .nvmrc pins 24 (which is what all four workflows use). I used the local Node as instructed and did not install another; nothing in the run depended on the difference, but CI is the authority on the pinned version.

Check Result
npm install (no lockfile yet, so not npm ci) 1896 packages, exit 0; package-lock.json committed
npm run compile passes (needed @types/lodash.throttle added back — @myst-theme/site's sources are type-checked and import it)
npm run test:unit 6 tests, all pass
npm run prod:build passes; the asset-URL rewriter reports rewrote 74 asset URL(s) in 3 stylesheet(s), so that path is exercised too
make build-zip writes .deploy/quantecon-theme-report.zip; unzipped it contains build/, public/, server.js, package.json (name @quantecon/report-theme, VERSION0.1.0), template.yml stamped version: 0.1.0, package-lock.json, CHANGELOG.md, LICENSE, README.md, thumbnail.png, and no node_modules
Real render THEME_TEMPLATE=$PWD/.deploy/quantecon-theme-report bash tests/visual/serve.sh starts (✨ Starting QuantEcon Report Theme ✨), builds both fixture pages with no warnings, and serves 200s. Both h1s, the table, the KaTeX stylesheets, #skip-to-article and the inline critical CSS are present in the HTML
Workflow YAML all four parse (js-yaml), jobs build/visual/fouc-guard, preview, release, update-snapshots
Playwright config npx playwright test --list loads the config and enumerates 6 tests across desktop-chrome, mobile-chrome, webkit-fouc

CI has since confirmed the browser suites on the runner: Visual regression (Chromium) ran 4 tests green and FOUC guard (WebKit) passed, both against the theme built from this branch.

Locally I did not run the browser suites — no Playwright browser install, no visual or FOUC run — per the brief. Instead I checked the harness by inspection against the real served document, and one thing that check caught is worth naming: the FOUC control case's regex, /<style>[^<]*:where\(\.simple-center-grid\)[^<]*<\/style>/, matches exactly one <style> block in the served HTML, so the control genuinely strips the critical CSS. It also caught that @myst-theme/site's footnotes section reuses .simple-center-grid, so theme.spec.ts scopes its locator to main > .simple-center-grid rather than asserting a bare count of one.

Acceptance criteria

Criterion Status
npm ci && npm run compile && npm run prod:build on a clean checkout, and make build-zip produces the stamped zip Verified locally, except that the first install had to be npm install because there was no lockfile. npm ci is what CI runs against the lockfile this PR commits
All four workflows green on a PR Three of four verified on this PR. CI is green across all three jobs — Build & Typecheck, Visual regression (Chromium) (4 tests passed, with the 🎭 summary comment posted) and FOUC guard (WebKit). PR Preview is green and deployed, taking the fixture fallback path as designed. update-snapshots.yml triggers on an issue_comment and GitHub runs that workflow from the default branch, so it cannot run until this merges — that is inherent, not a gap in this PR
A throwaway tag exercises release.yml end to end and attaches both assets Not done. It also needs a ## [0.1.0] section in CHANGELOG.md, which does not exist yet — the scaffold sits under ## [Unreleased], and cutting the release moves it. The second asset cannot attach until Phase 1b (#5) builds it
FAMILY.md merged in quantecon-theme.mystmd and linked from this README Not done, deliberately. It is a separate PR in a different repository, and the lecture theme is mid-cutover with open PRs. README.md and CONTRIBUTING.md describe the conventions and say the document is still to land
Nothing imports from a path outside this repository Verified — no path dependency, no workspace link, no file: specifier

Remaining work

  • FAMILY.md in quantecon-theme.mystmd, listing the conventions the family keeps in step (release zip layout and template.yml stamping, tag-pinned plugin URLs as release assets, the visual harness with platform-suffixed baselines, the FOUC guard, asset-URL rewriting, Keep-a-Changelog format, .mystmd naming), then a link to it from this README. Not started here on purpose.
  • Seed the -linux and -darwin visual baselines once Phase 2 gives them something to show (Phase 2: report theme shell — fonts, tokens, top bar, sidebar, footer #6, Phase 3: test harness — fixture, snapshots, overflow gate, static-build smoke, preview #9).
  • Cut v0.1.0 — move the ## [Unreleased] section under a dated heading first.
  • Reconsider the @remix-run/vercel adapter in server.js: it is the only reason @vercel/node (and its high-severity advisories) is in the tree, and nothing in the published bundle uses it.

🤖 Generated with Claude Code

Copies the build, test and release machinery from
QuantEcon/quantecon-theme.mystmd at 47e37ed885a40afb4fa42dd27ad458357616f68e
and adapts it here. Decision D1 makes this repository a self-contained sibling
of the lecture theme rather than a package sharing its code, so the scaffold is
copied and then owned here.

Copied and renamed: package `@quantecon/report-theme` at 0.1.0, zip
`quantecon-theme-report.zip`, `template.yml` title "QuantEcon Report Theme",
the Makefile THEME variable, and the release asset names. No occurrence of
`@quantecon/lecture-theme` or "QuantEcon Lecture Theme" survives the copy, and
a unit test guards that.

Adapted, with the reasons in the changed files:

- `release.yml` attaches two assets per tag — the theme zip and
  `plugins/compliance.mjs`. The plugin is built in Phase 1b (#5), so a
  dedicated step reports its absence as a warning rather than failing, and
  says which issue makes it real.
- `preview.yml` targets `compliance-lecture-style`'s `mystmd` branch, probing
  for it with `git ls-remote` on every run and falling back to the local
  visual fixture until the branch exists.
- No live-compute stack: `@myst-theme/jupyter`, the Thebe asset copy in
  `prod:build` and all three `patches/` entries exist to make executable
  notebook cells work, and report sites have no code cells.
- `theme.spec.ts` asserts structure rather than pixels — there is no report UI
  to baseline until Phase 2 (#6) — so the `visual` job is green from the first
  PR. The platform-suffixed baselines and `/update-snapshots` plumbing are in
  place for the Phase 3 harness (#9).
- The FOUC guard keeps the grid and font assertions and drops the nav-panel
  one, which returns with the sidebar in Phase 2.

Verified locally on node v26.8.1 (the .nvmrc pins 24, which CI uses):
`npm install`, `npm run compile`, `npm run test:unit`, `npm run prod:build`
and `make build-zip` all succeed; the zip contains a `template.yml` stamped
0.1.0 from package.json and no node_modules; and `myst start` serves the
fixture through the built bundle with no warnings.

Part of #3

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings September 3, 2026 01:03
@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown

🎭 Visual regression results

passed  4 passed

Details

stats  4 tests across 1 suite
duration  2.4 seconds
commit  4670f8d

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Changes recommended

The Vercel server entrypoint uses ESM syntax without the repository being configured as ESM, and the template install/engine settings are inconsistent with the repo’s documented/CI expectations.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

Scaffolds quantecon-theme-report.mystmd as a self-contained sibling of the lecture theme by copying/adapting the build, test, preview, and release machinery plus a minimal Remix/MyST theme implementation so CI and consumers have a working baseline.

Changes:

  • Adds Remix theme runtime (routes, loaders, root document + critical CSS) and Tailwind styling.
  • Introduces CI/release/preview/update-snapshots GitHub Actions workflows and Playwright-based rendering/FOUC guards.
  • Adds release-bundle template (template/, template.yml, Makefile) plus unit tests guarding version/name stamping and scaffold renames.
File summaries
File Description
tsconfig.json TypeScript configuration for the scaffold
package.json Project deps/scripts/overrides/engines
server.js Vercel adapter request handler entrypoint
remix.env.d.ts Remix type references
remix.config.dev.js Dev Remix config and bundling rules
remix.config.prod.js Prod Remix config (publicPath/build dirs)
tailwind.config.js Tailwind theme extensions + font stack
styles/app.css Tailwind entry + grid/base styles
scripts/relative-css-asset-urls.mjs Rewrites built CSS asset URLs to be relative
app/root.tsx Document + critical CSS + theme session integration
app/links.ts Self-hosted KaTeX + Source Sans 3 stylesheet links
app/entry.client.tsx Client hydration entry
app/entry.server.tsx Server render entry
app/backend/loaders.server.ts Server-side loaders for MyST config/page JSON
app/components/Page.tsx Page renderer (grid + MyST AST render)
app/components/ErrorPage.tsx Error boundary page rendering
app/routes/_index.tsx Index route loader/meta/links
app/routes/$.tsx Catch-all page route loader/meta
app/routes/api.theme.tsx Theme toggle API action passthrough
app/routes/myst-theme[.css].ts Theme CSS loader route
app/routes/[favicon.ico].tsx Favicon loader route
app/routes/[robots.txt].tsx Robots.txt loader route
playwright.config.ts Playwright projects + webServer wiring
tests/visual/serve.sh Fixture server script for Playwright/preview
tests/visual/README.md Visual/FOUC harness documentation
tests/visual/theme.spec.ts Chromium rendering structure assertions
tests/visual/fouc.spec.ts WebKit first-paint/FOUC guard
tests/visual/fixture/myst.yml.in Fixture MyST config template
tests/visual/fixture/intro.md Fixture landing page content
tests/visual/fixture/series.md Fixture secondary page content
tests/unit/template-stamp.test.mjs Unit tests guarding stamping/rename invariants
template.yml Theme bundle manifest for MyST
template/package.json Shipped theme runtime package manifest
template/server.js Shipped Express server entry for the bundle
template/README.md Shipped bundle README
template/LICENSE Shipped bundle license
template/.gitignore Shipped bundle gitignore
Makefile Local bundle assembly + zip build targets
README.md Repo overview + development commands
CHANGELOG.md Initial changelog scaffold (Keep a Changelog)
CONTRIBUTING.md Contribution + release process docs
SECURITY.md Security posture + audit triage/overrides rationale
.nvmrc Node version pin
.npmrc legacy-peer-deps rationale for Remix v1 pin
.gitignore Ignores for build outputs/fixtures/reports
.eslintrc.js Remix ESLint config
.github/workflows/ci.yml CI: build/typecheck/unit + Playwright suites
.github/workflows/preview.yml PR preview build/deploy to gh-pages
.github/workflows/release.yml Tag-driven release build + assets publish
.github/workflows/update-snapshots.yml PR comment-driven snapshot refresh
Review details
  • Files reviewed: 46/52 changed files
  • Comments generated: 3
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread server.js
Comment on lines +1 to +4
import { createRequestHandler } from "@remix-run/vercel";
import * as build from "@remix-run/dev/server-build";

export default createRequestHandler({ build, mode: process.env.NODE_ENV });

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The diagnosis is right and the remedy is not, so no change here. This file is never executed. The release workflow assembles the bundle by copying from template/, and the server it ships is template/server.js, which is CommonJS throughout; the root server.js exists only to satisfy @remix-run/vercel, and this repository deploys previews to GitHub Pages rather than Vercel. Converting it to CommonJS would preserve a file that does nothing while keeping the high-severity @vercel/node advisory chain it drags in, which SECURITY.md already documents along with the intention to remove the Vercel adapter with the Phase 2 shell (#6). Deleting the file and the dependency is the fix that pays, and it belongs with that work rather than with a scaffold PR whose value is fidelity to the lecture theme, where this file is byte-identical.

Comment thread template.yml
Comment thread template/package.json
The PR Preview job failed on `Failed to fetch http://localhost:3000/index.json
after 3 attempts`. Trimming the app to the routes a live `myst start` needs cut
four that only the static export path exercises, so the preview — the one job
that runs `myst build --html` — was the first thing to notice.

Restores `($project)_.($a).($b).($c).($d).$slug[.json].tsx` (per-page JSON, plus
`myst.xref.json` and `myst.search.json`), `[sitemap.xml].tsx`,
`[sitemap_style.xsl].tsx` and `[objects.inv].tsx`, with the three loaders they
call. `getMystSearchJson` is typed as a plain JSON object rather than
`MystSearchIndex`, whose package this theme does not depend on directly.

Reproduced and confirmed locally: a `myst build --html` of the fixture against
the built bundle now serves 200 for every crawled route and writes both pages,
the sitemap, `objects.inv` and the xref/search JSON, with the baseurl applied
and the stylesheet asset URLs relative.

Part of #3

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown
PR Preview Action v1.8.1
Preview removed because the pull request was closed.
2026-09-03 12:57 UTC

…TING

`template/package.json` declared `engines.node` as `>=16` while CONTRIBUTING.md
says the built theme supports Node 20 or newer and cites that very field as the
evidence. One of the two was wrong; 20 is the honest number, since the repo pins
24 in .nvmrc, requires 20 for the unit tests, and the engine the theme renders
under has a higher floor than 16 anyway.

Raised by Copilot on #12. The same mismatch exists in the lecture theme this
scaffold was copied from and is filed separately there.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@mmcky
mmcky merged commit 50dc734 into main Sep 3, 2026
4 checks passed
@mmcky
mmcky deleted the feat/scaffold branch September 3, 2026 12:56
mmcky added a commit to QuantEcon/quantecon-theme.mystmd that referenced this pull request Sep 10, 2026
…npm ci

The release bundle's package.json declared engines.node ">=16" while
CONTRIBUTING.md cites that field as evidence for a Node 20 floor; 20 is
the honest number (.nvmrc pins 24, the root package.json requires 20,
the rendering engine's floor is above 16). And template.yml ran
`npm install` although the release asset ships a lockfile (release.yml
generates it and `files` lists it), so consumer installs were neither
reproducible nor lockfile-preserving. `npm ci` fails without a lockfile,
but the only lockfile-less path is a source checkout, which is not a
usable template until `make build-theme` has run, and that target
installs on its own (#162).

The report theme carries the same two lines; its copy of the engines
floor was corrected in QuantEcon/quantecon-theme-report.mystmd#12 and
the install decision is QuantEcon/quantecon-theme-report.mystmd#13.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
mmcky added a commit to QuantEcon/quantecon-theme.mystmd that referenced this pull request Sep 10, 2026
…npm ci (#191)

* fix(bundle): declare Node >=20 and install the shipped lockfile with npm ci

The release bundle's package.json declared engines.node ">=16" while
CONTRIBUTING.md cites that field as evidence for a Node 20 floor; 20 is
the honest number (.nvmrc pins 24, the root package.json requires 20,
the rendering engine's floor is above 16). And template.yml ran
`npm install` although the release asset ships a lockfile (release.yml
generates it and `files` lists it), so consumer installs were neither
reproducible nor lockfile-preserving. `npm ci` fails without a lockfile,
but the only lockfile-less path is a source checkout, which is not a
usable template until `make build-theme` has run, and that target
installs on its own (#162).

The report theme carries the same two lines; its copy of the engines
floor was corrected in QuantEcon/quantecon-theme-report.mystmd#12 and
the install decision is QuantEcon/quantecon-theme-report.mystmd#13.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* docs(changelog): record the bundle-manifest change

Copilot review on #191: CONTRIBUTING.md requires every consumer-facing
change under Unreleased.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5.1 <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.

2 participants