Skip to content

feat: add tier-2 scaffold teaching-coverage gate for core exports#861

Merged
vivek7405 merged 3 commits into
mainfrom
chore/scaffold-coverage-gate
Jul 9, 2026
Merged

feat: add tier-2 scaffold teaching-coverage gate for core exports#861
vivek7405 merged 3 commits into
mainfrom
chore/scaffold-coverage-gate

Conversation

@vivek7405

@vivek7405 vivek7405 commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator

Why

Follow-up to #854. That PR hardened the webjs-scaffold-sync skill (guidance) so the #848-class gap (a new control-flow throw shipped documented but undemoed) would be caught by an agent reading the skill. But guidance is not enforcement. As discussed, tests are enforced two ways: a commit gate ("include a test") AND CI actually running it ("it must exist and pass"). Scaffold-sync only had tier 1, the require-scaffold-with-src.sh commit floor, which proves you touched a scaffold file but cannot tell a real demo from a doc bullet. That floor was green when #848 shipped no demo.

What: the missing tier 2

test/scaffolds/gallery-coverage.test.js reconciles the live @webjsdev/core export surface against a hand-curated test/scaffolds/gallery-coverage.json manifest and FAILS when an export is neither:

  • { demo }, a runnable gallery file that actually references the symbol, or
  • { exempt }, with a reason (internal: plumbing, or deferred: agent-facing but not yet demoed).

It runs under npm test (walked by scripts/run-node-tests.js) and in CI, so a local --no-verify cannot skip it. A new core export turns CI red until it is classified, the exact analogue of "a test must exist and pass." Had this existed, adding forbidden / unauthorized would have failed CI until they were demoed (which #854 did) or consciously exempted.

Design details:

  • The reconcile() core is a pure function. Its failure modes (new export, stale key, missing demo file, demo that does not reference the symbol, empty exemption) are proven with synthetic inputs alongside the real-surface assertion, so the gate's teeth are themselves tested.
  • The manifest seeds all 100 current exports: 12 demoed, 59 internal-exempt, 29 deferred. The deferred set (agent-facing APIs like Suspense, css / shadow styles, navigate, the remaining lit directives) is the honest audit output. It is printed every run and tracked in scaffold: close the coverage-gate deferred gaps (core + server + convention demos) #859 so it stays visible and gets converted to real demos over time.
  • Made notFound genuinely demoed: routing/[id] now throws it for a reserved id (/features/routing/missing renders 404, verified on a booted app), so the gate drives a demo rather than an exemption.

Scope / limitation

The gate covers the export surface (where the #848 throwers live). A new routing convention file type (a *.ts boundary) is not an export, so it stays tier-1-only for now, noted in the skill and the hook header.

Verification

  • test/scaffolds/gallery-coverage.test.js: 6/6 (real surface green plus 5 counterfactuals proving the gate fails correctly).
  • Full test/scaffolds/*: 38/38.
  • Generated a full-stack app, booted it: /features/routing/42 returns 200, /features/routing/missing returns 404. webjs check: only no-scaffold-placeholder.

Definition of done

The #859 backlog (demoing the deferred exports) stays open and is out of scope here; this PR only adds the gate.

Closes #865

vivek7405 added 2 commits July 9, 2026 11:46
The require-scaffold-with-src commit hook is only a floor: it proves a
feature commit touched SOME scaffold file, but cannot tell a real demo
from a doc bullet, which is how #848 shipped forbidden()/unauthorized()
documented but undemoed.

Add the missing tier-2, mirroring how tests are enforced (a commit floor
plus an un-skippable CI gate). gallery-coverage.test.js reconciles the
live @webjsdev/core export surface against a hand-curated
gallery-coverage.json manifest and FAILS when a new export is neither
demoed in the gallery nor consciously exempted (internal plumbing, or a
deferred agent-facing API). It runs under npm test, so a local
--no-verify cannot skip it: a new export turns CI red until classified.

The reconcile() core is pure; its failure modes (new export, stale key,
missing demo file, empty reason) are proven with synthetic inputs beside
the real-surface assertion. Seed the manifest across all 100 current
exports (12 demoed, 59 internal, 29 deferred, tracked in #859), and make
notFound genuinely demoed by having routing/[id] throw it for a reserved
id (verified: /features/routing/missing renders 404).
…urfaces

Wire the new gate into the surfaces that describe scaffold enforcement:
the webjs-scaffold-sync skill (a two-tier enforcement section plus a
change-type row requiring a manifest entry for every new core export),
the require-scaffold-with-src hook header (cross-reference the CI gate as
the real teeth), and agent-docs/framework-dev.md (a section explaining
both tiers and the manifest workflow).
Close the two enforcement gaps the core-only gate left open, in the same
tier-2 model. The gate now reconciles THREE live surfaces, not one:

- @webjsdev/core exports (unchanged): a { demo } gallery-file pointer.
- @webjsdev/server exports (new, 129): { demoed: true } verified by a
  generated full-stack/api/saas app importing it, else an { exempt }
  reason. 21 demoed, 72 internal, 36 deferred.
- routing convention files (new, 18): the stems DERIVED from
  packages/server/src/router.js source (so a new stem === '...' branch
  auto-appears and forces classification), each demonstrated by a file
  in a generated app. 6 demonstrated, 12 deferred (error/loading/
  not-found boundaries + metadata routes the scaffold does not yet ship).

A pure reconcileSet() handles the demoed/exempt model; its failure modes
(unclassified, stale, demoed-but-not-demonstrated, empty reason) are
proven with synthetic inputs beside the real-surface assertions. Docs
(skill two-tier section + change-type row, hook header, framework-dev)
updated to the broadened scope. Full scaffold suite 44/44.
@vivek7405

Copy link
Copy Markdown
Collaborator Author

Extended to close the two enforcement gaps flagged in review, in the same tier-2 model (commit 308b2e1). The gate now reconciles THREE live surfaces:

  1. @webjsdev/core exports (100): a { demo } gallery-file pointer.
  2. @webjsdev/server exports (129, new): { demoed: true } verified by a generated full-stack/api/saas app importing it. 21 demoed, 72 internal, 36 deferred.
  3. Routing convention files (18, new): stems DERIVED from packages/server/src/router.js (a new stem === '...' branch auto-appears and forces classification), each demonstrated by a file in a generated app. 6 demonstrated, 12 deferred (error/loading/not-found boundaries + metadata routes the scaffold does not yet ship).

A pure reconcileSet() handles the demoed/exempt model; its failure modes are proven with synthetic inputs. Coverage test 12/12, full scaffold suite 44/44.

The remaining gap (writing the deferred demos) is content work the gate is designed to SURFACE, tracked in #859 (now expanded to server + convention deferred items).

@vivek7405 vivek7405 marked this pull request as ready for review July 9, 2026 08:05
@vivek7405 vivek7405 merged commit 35d180d into main Jul 9, 2026
9 of 10 checks passed
@vivek7405 vivek7405 deleted the chore/scaffold-coverage-gate branch July 9, 2026 08:05
vivek7405 added a commit that referenced this pull request Jul 9, 2026
The coverage-gate extension gated routing convention files and server
exports, but this framework-dev paragraph still claimed conventions stay
tier-1-only. Correct it to the shipped three-surface scope.
vivek7405 added a commit that referenced this pull request Jul 9, 2026
…ting

Review round 3: the #861 heuristic wrongly marked documented app-facing
exports internal. Move render/renderStream/renderToString (public API),
readBody/stampRemoteIp (api-routes/rate-limiting docs), attachWebSocket +
the buildRouteTable/matchPage/matchApi/rawActionRequest test helpers
(testing docs) to deferred. A proactive doc-import sweep of every
remaining internal-marked core+server export confirms none others are
mis-classed (parse/stringify appear only in a 'you never see this file'
generated-stub snippet, so they stay internal).
vivek7405 added a commit that referenced this pull request Jul 9, 2026
The coverage-gate extension gated routing convention files and server
exports, but this framework-dev paragraph still claimed conventions stay
tier-1-only. Correct it to the shipped three-surface scope.
vivek7405 added a commit that referenced this pull request Jul 9, 2026
…ting

Review round 3: the #861 heuristic wrongly marked documented app-facing
exports internal. Move render/renderStream/renderToString (public API),
readBody/stampRemoteIp (api-routes/rate-limiting docs), attachWebSocket +
the buildRouteTable/matchPage/matchApi/rawActionRequest test helpers
(testing docs) to deferred. A proactive doc-import sweep of every
remaining internal-marked core+server export confirms none others are
mis-classed (parse/stringify appear only in a 'you never see this file'
generated-stub snippet, so they stay internal).
vivek7405 added a commit that referenced this pull request Jul 9, 2026
* docs: fix stale tier-1-only convention note left by #861

The coverage-gate extension gated routing convention files and server
exports, but this framework-dev paragraph still claimed conventions stay
tier-1-only. Correct it to the shipped three-surface scope.

* feat: ship sitemap/robots/manifest metadata-route demos in the scaffold

Close 4 coverage-gate deferred gaps: the scaffold now ships example
app/sitemap.ts (uses sitemap() from @webjsdev/server), app/robots.ts,
and app/manifest.ts, flipping the sitemap/robots/manifest routing
conventions and the server sitemap export from exempt to demonstrated.
Gate: conventions 6->9 demonstrated, server 21->22 demoed.

* feat: demo the common lit directives in the scaffold (live/ref/until/keyed/unsafeHTML)

Extend the directives gallery demo with a second card covering live (a
controlled input), ref + createRef (a DOM-node handle), until (a pending
fallback), unsafeHTML (trusted raw HTML), and keyed (fresh subtree on
key change). Flips 6 core exports from deferred to demoed (core 12->18).
The niche completeness directives (guard, templateContent, asyncAppend/
asyncReplace) stay documented-deferred rather than ship contrived demos.

* feat: demo error/loading/not-found boundaries in the scaffold

Round out the boundaries feature with the three remaining nested
boundary conventions: error.ts (with a crash/ sub-route that throws so
it is reachable), loading.ts (the Suspense fallback), and not-found.ts
(nearest 404, served for an unmatched subtree URL). Index links the new
routes. Flips error/loading/not-found conventions to demonstrated
(conventions 9->12). Verified: /crash renders 500, an unmatched URL 404.

* feat: demo shadow-DOM css + computed/effect/batch in the scaffold

Add a <reactive-meter> shadow-DOM component to the components feature:
static shadow=true with static styles=css`` (scoped CSS, the one place
the lit reflex is right), plus computed (derived signal), effect (a
browser-side reaction), and batch (coalesced writes). Flips css/computed/
effect/batch from deferred to demoed (core 18->22). Shadow DSD verified
in SSR output.

* feat: demo navigate()/revalidate() in the client-router gallery

Add a <router-controls> component to the client-router feature that
drives programmatic soft navigation (navigate()) and browser snapshot
eviction (revalidate()) from JS handlers. Flips navigate/revalidate to
demoed (core 22->24).

* fix: manifest literal placeholder + until() re-render flicker in scaffold demos

Two review findings: (1) gallery files are copied verbatim with no
{{APP_NAME}} substitution, so manifest.ts served a literal '{{APP_NAME}}'
in /manifest.json; hardcode a neutral 'webjs app' the user adapts.
(2) the directives demo called later() inside render(), so until()'s new
promise identity each render flashed back to the fallback on any
re-render; create the promise once in a field. Also rephrase the inline
comments to satisfy the prose-punctuation invariant.

* fix: reclassify session/store exports back to deferred (over-reached internal)

Review round 2: cookieSessionStorage, storeSessionStorage (+ its alias
storeSession), and getStore are app-facing and documented (docs/sessions,
docs/cache, agent-docs/built-ins), so internal was wrong. Move them back
to deferred so they stay in the demo backlog. cookieSessionStorage now
matches its alias cookieSession. Six genuinely-internal reclassifications
(attachWebSocket, runInstrumentation, low-level cookie serializers,
runWithActionSignal) stand.

* fix: correct more internal/deferred mis-classes from #861 regex bucketing

Review round 3: the #861 heuristic wrongly marked documented app-facing
exports internal. Move render/renderStream/renderToString (public API),
readBody/stampRemoteIp (api-routes/rate-limiting docs), attachWebSocket +
the buildRouteTable/matchPage/matchApi/rawActionRequest test helpers
(testing docs) to deferred. A proactive doc-import sweep of every
remaining internal-marked core+server export confirms none others are
mis-classed (parse/stringify appear only in a 'you never see this file'
generated-stub snippet, so they stay internal).
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.

harden scaffold-sync: tier-2 CI coverage gate for core exports

1 participant