fix(runner): re-pin the authoring app to Handsontable 18.1.0 and derive the picker fallback (DEV-2735) - #298
Merged
Conversation
Contributor
…ve the picker fallback (DEV-2735) The starter buckets re-pin themselves from npm on a weekly cron; the authoring app's own pins did not, and nothing failed when they drifted. All four sites still said 18.0.0 months after 18.1.0 became `dist-tags.latest` — while bucket 18 and the twelve baked Tier-2 container recipes had already moved. They did not self-correct because `theme-presets-version.test.mjs` only compared the `handsontable` pin and `BUNDLED_VERSION` to each other, so the two drifted together and stayed "consistent" while both went stale. Bump the pin, `BUNDLED_VERSION` and the lockfile to 18.1.0, and stop hand-maintaining the other two. `writeCatalogIndex` now lifts each release bucket's `hotVersion` into a `bucketVersions` map in `catalog.json`, and `catalog.ts` derives `VERSION_OPTIONS` / `DEFAULT_VERSION` from it via a new `stableBucketVersions` helper — so the weekly re-pin moves the picker's fallback too, and every version it offers has a bucket behind it. That also fixes the fifth site the ticket did not list: `FullMode` seeds `DEFAULT_VERSION` independently of the main view. Release buckets only. The `next` bucket is a nightly, so putting it in the index would make a committed file — and every open PR that touches it — go stale daily, to carry a value the picker filters back out anyway. Filtered by version shape rather than by the bucket key, so a future prerelease bucket cannot leak in either. For the two that must stay literal — `BUNDLED_VERSION` cannot import `package.json`, because an import reaching outside `theme/` turns both theme harnesses into silent skips — a third assertion checks the pin against the bucket for its own major. Offline, against committed state: no pipeline test touches the registry, and `docs/TESTING.md` rules out an env gate to hide one. Deliberately per-major rather than "newest bucket", so the week HT 19 ships does not turn a red suite into a demand for a major bump. Note the operational consequence: from the next release onward the automated `chore/starter-example-buckets` PR arrives red, because that PR carries the new `catalog.json`. Bumping the pin into it is the intended fix, not loosening the test. Also refresh the token-count prose — `tokens/main` is 285 keys at 18.1.0, up from 279 at 18.0.0; `colors/*`, `density` and `sizing` are unchanged between the two, so the byte-identical claim still holds. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
demtario
force-pushed
the
fix/DEV-2735-authoring-ht-pins
branch
from
September 2, 2026 11:19
703d2a6 to
21cfec9
Compare
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.
Fixes DEV-2735. Found while doing DEV-2727.
The starter buckets re-pin themselves from npm on a weekly cron; the authoring app's own pins do not, and nothing failed when they drifted. All four sites still said 18.0.0 months after 18.1.0 became
dist-tags.latest— while bucket18, the18.1docs bucket and the twelve baked Tier-2 container recipes had already moved.They did not self-correct because
pipeline/theme-presets-version.test.mjsonly compared thehandsontablepin andBUNDLED_VERSIONto each other. The two drift together and stay "consistent" while both go stale, which is exactly what happened: both were set once by feature work (0e46c1572,f35323151) and never bumped since.What was stale, and what it cost
apps/authoring/package.json:1818.0.0src/theme/presets.ts:76BUNDLED_VERSION18.0.0presetsFor.ts:110short-circuitsrc/catalog.ts:26VERSION_OPTIONS["18.0.0","17.1.0","17.0.1"]/api/versionsanswerssrc/catalog.ts:27DEFAULT_VERSION18.0.0The picker one is worse than "until the API answers":
App.tsx's versions-fetch fails open, so a registry hiccup or an API deploy leaves a visitor on that list for the whole session — offered no current release at all.A fifth site the ticket did not list:
App.tsx:621.FullModeseedsDEFAULT_VERSIONindependently of the main view (App.tsx:1006), so/d/:idhad its own copy of the same staleness.The fix
Bump the pin,
BUNDLED_VERSIONand the lockfile to 18.1.0 — and stop hand-maintaining the other two.writeCatalogIndexnow lifts each bucket'shotVersioninto abucketVersionsmap incatalog.json, andcatalog.tsderives both constants from it through a newstableBucketVersionshelper in the runtime package. The weekly re-pin already regeneratescatalog.json(import-starters.ymlrunsimport.mjs --indexin its publish job), so the picker's fallback now moves with the buckets, with no workflow change and no human step.That also makes the fallback strictly better than the list it replaces: every version it offers has a bucket behind it, so switching to one cannot land on a version whose
<bucket>/<framework>.jsondoes not exist. Today it derives["18.1.0", "17.1.0", "16.2.0", "15.3.0"]— one bucket-backed release per major, where the old list held two 17.x and no 18.1.0.Release buckets only. The map first included
nexttoo, on the reasoning that it should describe what is on disk and the consumer could filter. The first CI run disproved that:nextis a nightly, so a committed file carrying it goes stale daily, and any open PR touchingcatalog.jsongoes red with it — to carry a value the picker filters back out anyway (it surfaces nightlies through its ownnextcontrol). It is now filtered at generation time, by version shape rather than by the bucket key, so a future prerelease bucket cannot leak in either.stableBucketVersionskeeps its own shape filter as belt-and-braces, since it is the last step before a version reaches the picker.The helper lives in
packages/runtime/src/version.ts, not next to its caller, becauseapps/authoring/src/catalog.tsuses bare attribute-less JSON imports and is Vite-only by design — node cannot load it, so the logic would have been untestable there.What stops the next drift
The remaining two sites cannot be derived.
BUNDLED_VERSIONin particular must stay a literal:presetsFor.ts:10-15records that nothing intheme/may import outsidetheme/, because an import the harness cannot resolve turns all 20 cases oftheme-wiring.test.mjsandtheme-typecheck.test.mjsinto skips that read as a green run. Importingpackage.jsonthere would trade this bug for a worse one.So they get a third assertion instead: the pin must equal the
hotVersionof the bucket matching the pin's own major.Offline, against committed state, rather than the ticket's suggested live-registry check. No
pipeline/*.test.mjstouches the network — eleven of them stubglobalThis.fetchspecifically to keep it that way — anddocs/TESTING.mdrules out an env gate to hide a red test behind. The bucket manifests are cron-refreshed truth already in the repo, so the check costs nothing.Deliberately per-major, not "must equal the newest bucket". A highest-bucket rule would go red the week HT 19 ships and demand the app move major —
THEME_API_MIN_MAJOR, preset shape, wrapper peers — which is a different and much riskier job that must not be forced by a red suite. Within its own major it still catches the reported drift. There is a comment saying not to tighten it.Operational consequence, worth knowing before it happens
From the next Handsontable release onward, the automated
chore/starter-example-bucketsPR arrives red, because that PR is what carries the newcatalog.json.ci.ymlruns onpull_request, so itsunitjob fails until someone edits the pin,BUNDLED_VERSIONand the lockfile into it.That is the intended notification channel — a release forces the app forward — not a surprise. Bumping the pin into that PR is the fix; loosening the test is not. (
check-test-presence.mjsis unaffected: generatedpublic/is excluded as source.)I considered auto-bumping it inside the publish job instead, and did not: it would need
pnpm updaterunning under that job'scontents: write+pull-requests: writepermissions to make a two-line edit that a human should see.This already happened once, on this PR.
chore(runner): update versioned starter buckets(#297) merged while the PR was open and moved thenextnightly, so the merge commit had master's manifests against mycatalog.json— and the new consistency assertion caught it rather than letting an inconsistent index merge. Rebased, and thenextentry is gone for the reason above, which removes the daily-churn half of that failure mode for good.Verification
Raw
pnpmthroughout —rtk's filters have fabricated pass summaries on this repo before.pnpm testtheme/pnpm build+pnpm typecheckcheck-compiler-chunkpnpm e2e(deterministic)Both new checks were verified to fail without the fix, not just pass with it:
the handsontable pin (18.0.0) trails bucket 18 (18.1.0) — bump apps/authoring/package.json, BUNDLED_VERSION and the lockfile togetherbucketVersions.18back to 18.0.0 and rebuilding makese2e/version-fallback.spec.tsfail ongetByText('Handsontable 18.1.0')not foundTwo notes on how the numbers were obtained, because both nearly went wrong:
The master e2e baseline needed
VITE_DEV_USER=. A localapps/authoring/.env.localshort-circuitscurrentUser(), so 8 anonymous/auth specs fail in that tree — nothing to do with this change. With the override the baseline is a clean 240/151/0.A preview server from a deleted worktree was squatting port 4173. It had been started with
--port 4183 --strictPort, but pnpm ate both flags, so it bound 4173 andreuseExistingServer: !CIwould have adopted it — the whole suite silently run against a two-day-old build of another branch. Killed it, and ran withCI=1so a busy port fails loudly instead.Incidental
pnpm update handsontable --lockfile-onlyalso dragged in an unrelated@codemirror/commands6.10.4 → 6.11.0. Reverted and usedinstall --lockfile-only: the changed spec forces re-resolution on its own, so the lock diff is 5 lines,handsontableonly.stableBucketVersionsreturns[string, ...string[]]rather thanstring[].DEFAULT_VERSION = VERSION_OPTIONS[0]was two real TS errors under the app'snoUncheckedIndexedAccess; the tuple encodes the throw-on-empty guarantee instead of hiding it behind a!. It throws rather than returning[]on a map with nothing usable in it — an empty dropdown and a version-less first visit is the same silent failure class this PR removes.Token-count prose refreshed:
tokens/mainis 285 keys at 18.1.0, up from 279 at 18.0.0.colors/*,densityandsizingare byte-identical between the two (checked against jsDelivr), so the existing "stable either way" claim still holds.Out of scope
runner/e2e/docs-frameworks.spec.ts:18-19still hardcodes&v=18.0.0— tracked separately per the ticket.pipeline/theme-ramp.test.mjs:120says "38 of the 279 tokens". The 279 is now stale, but it pairs with a DEV-2497 measurement I could not reproduce (18.1.0 has 12 primary-referencing tokens intokens/main, not 38), and guessing at prose in an unrelated test seemed worse than leaving it.🤖 Generated with Claude Code
Note
Low Risk
Low risk: dependency and catalog-index changes with strong offline/e2e guards; main operational note is future weekly bucket PRs may fail CI until the authoring pin is bumped within the same major.
Overview
Re-pins the authoring app to Handsontable 18.1.0 (
package.json, lockfile,BUNDLED_VERSION) so the Style panel’s bundled theme presets match the current 18.x bucket.Version picker fallback no longer uses a hand-maintained list that had lagged npm
latest.writeCatalogIndexnow writesbucketVersionsincatalog.json(each release bucket’s manifesthotVersion;nextnightly omitted).VERSION_OPTIONSandDEFAULT_VERSIONcome from new runtime helperstableBucketVersions— stable exact releases only, newest first, every choice backed by a starter bucket — so the weekly bucket re-pin updates the fallback automatically.Adds pipeline smoke/unit coverage for
bucketVersionsandstableBucketVersions, a per-major check that the app pin matchescatalog.bucketVersions[major], and Playwright coverage that the UI shows the newest bucket version when/api/versionsfails.Reviewed by Cursor Bugbot for commit 21cfec9. Bugbot is set up for automated code reviews on this repo. Configure here.