feat(blocks): NODE_ENV dev default, deco schema --check, multivariate stops at the first match
What this PR does
Three product-owner decisions (2026-10-14/15) in @decocms/blocks. The rest of the stack (#694 and up) merges this branch.
1. createCMS reads NODE_ENV again to decide dev mode (d03448c1)
- With no
devoption, acreateCMSwithsitestays on local files whenprocess.env.NODE_ENV === "development". Hosted releases are never swapped in, and drafts still load. - The read is guarded (
typeof process !== "undefined" && process.env != null && ā¦), so a Worker or browser withoutprocesscounts as production. It is spelledprocess.env.NODE_ENVso a bundler'sdefinecan replace it. - The explicit
devoption, which shipped in 8.0ā8.2, stays as an override in both directions (dev: falseforces swaps even whenNODE_ENV=development). - This is the SDK's only environment read.
noEnv.test.tsallows exactly that one line.remoteLoaderitself still reads no environment. - Conformance: four new H-5 tests (default, override, production or unset, no
process). Each was mutation-checked. - Removing
isNextDevfrom@decocms/nextjs, the migrator, the skill and the smoke apps happens in #697 and #700, not here.
2. deco schema --check (fa834811)
-
For CI: generates the schema in memory, writes nothing, and exits 1 when
.deco/schema.gen.jsonis missing or differs from whatdeco schemawould write. The message says what moved:- blocks that are new in code or gone,
- the number of changed definitions,
- the generating version,
- or formatting only.
It ends with: Run
deco schemaand commit .deco/schema.gen.json. -
Schema errors exit 1 with the errors.
--checkand--watchcan't be combined, and there is no dev watch. -
Usage line:
deco schema [--root <dir>] [--watch | --check]. Seven tests incli/run.test.ts, including one through the bin under plain Node.dd-06follows the new usage line. -
The migrator adds it to each site's CI workflow in #700.
3. multivariate stops at the first matching rule, as v7 did (ac23a701)
- Before this change, the resolver resolved every variant's rule before the built-in
multivariateran. A matcher after the winner still ran, so a random rule there drew a number and recorded a cohort. - Now the built-in
multivariate(marked the waylazyis) gets its rules resolved in order, one at a time, up to the firsttrueone. Later variants are left out and their matchers are never called. - A
multivariatethat the site declares in its block map replaces the built-in and still gets every rule resolved, like any other block. - Legacy multivariate types (
website/flags/multivariate.ts, ā¦) go through the same path. builtin-03/mv-02now claims first-match order. That test changed because the docs claim changes. New tests cover in-order evaluation, a random rule after the winner never drawing, legacy types, and the block-map override. Mutation-checked: 4 fail without the change.- CLAUDE.md now says that
lazyand the built-inmultivariateare the resolver's special cases.
Docs site follow-up (deco-sites/docs-tanstack, /next/*)
- api-reference / releases-and-drafts:
devdefaults toNODE_ENV === "development"(guarded;falsewithoutprocess). Passdevto override it, e.g.dev: import.meta.env.DEVon a Worker. Remove "the SDK reads no environment variable" wherever it refers todev. - cli / checking:
deco schema --check(usage line, exit codes, the message) and a CI snippet that runsdeco schema --check. - matchers-and-variants line 75: replace "Rules aren't lazy: they're cheap booleans and always run." with "Rules run in order and stop at the first
trueone, as in v7: a later rule's matcher never runs, so a random rule after the winner draws nothing and sets no cookie. Amultivariateyou declare in your block map gets every rule." - how-resolution-works:
lazyis no longer the only special case. Add the built-inmultivariate's in-order rules.
Gates
bun run checkpasses.bun run buildpasses.skills:checkandskills:readme:checkpass.- A plain-Node import of every built export works (22), and so does
deco --help. bun run test(2 GB): 2092 pass, 3 fail, 95 skipped.- DS-2 is a known failure on
mainsince #689. - Two
schema.test.tstests hit the 5 s timeout under full-suite load. They pass alone (116/116 with noEnv, guides.cli and telemetry).
- DS-2 is a known failure on
Draft shape and block JSON: unchanged (2026-10-16)
Two who-edited-what experiments came and went on this branch, and the net change is none:
- A top-level
editsfield on the draft (00125500), reverted ina886d720. - A reserved
__metakey on saved blocks that the resolver dropped (b1cd9a13,36fb75f1), reverted in the commit on top. Both reverts are merged up the stack.
PO: "only have metadata and changed by on v8 hosted". Studio keeps who and when in the hosted draft object's storage metadata, so the draft stays exactly { set, delete } and blocks has no reserved metadata key. The tree at M2 equals its state before these commits.
https://claude.ai/code/session_016ZsmzpZ2zahRBoATBb2tBu
Main refresh (2026-10-17, 63b9e6d2)
- Merged main
8bd3d88c(#709) into this branch (normal merge, no conflicts), then merged up the stack to M2 #707. - Gates at the top of the stack (M2
5131a3c1):bun run check,bun run build,skills:check,skills:readme:checkpass.bun run test(2 GB memory cap): 122 files pass, 1 skipped; 2,745 tests pass, 95 skipped, 0 fail. DS-2 now passes (fixed on main by #709); no timeouts this run. - CI on this PR's new head: all checks pass.
š¤ Generated with Claude Code