Content studio API + preview page; widen the reachability guard; correct
two inventory errors INVENTORY CORRECTION, and it was mine. Phase 2's two "orphans" are NOT orphans -- my board grepped only web/src/app and missed component-level mounting. The transitive check says both are already mounted: BookComparisonPanel -> GradeResultCard -> app/scan/page.tsx NewsWire -> ExploreHub -> app/explore/page.tsx So book comparison is DONE (wired to /api/books, rendering on the grade card) and THE WIRE is DONE-BY-DESIGN, mounted in ExploreHub. Its header names an "Offseason Hub" as its home, and that hub genuinely does not exist -- but that is board item #8, not a mounting bug, and inventing a surface to satisfy a comment would be the wrong fix. The lesson is the same one this session keeps teaching: I checked one directory and reported a conclusion the check could not support. ALSO CAUGHT: I overwrote src/routes/content.js, which was the Session-29 content-templates route, by picking a filename without looking. Restored from git with no work lost; the new surface lives at /api/content-studio and both now coexist. PHASE 0/1 — /api/content-studio serves finished posts (copy, branded card, card_svg, the fact_contract each was REQUIRED to have, and the facts that actually backed it) plus a POST for editorial status in Redis. Private via internal key; the Next proxy holds the key server-side so the browser never does. /studio renders it as a thin client -- copy and card side by side with the fact contract visible, because reviewing copy by reading it is exactly how a wrong number ships. Never-blank: a night with nothing generated says so. API-FIRST is the point: the endpoint an autonomous poster will call is the one the page already renders, so the agent handoff is a pointer change, not a rebuild. Contract documented at docs/CONTENT-STUDIO-API.md. EXPRESS 5 BROKE 23 SUITES at first: `router.get('/:date?')` throws at mount time in Express 5, taking down everything that imports app.js. Two explicit routes instead. PHASE 3 — the reachability guard is widened from grade-fields-only to a general built-but-unread check. Book comparison, THE WIRE and the content studio are now registered surfaces; a page counts as its own entry point (Next mounts it by convention) while everything else must trace to one. 22 checks green; a registered-but-unimported surface still goes red. FULLY ISOLATED: read-only on model/slate/ledger, serving fingerprint verified unchanged, accrual clock unchanged at 0 eligible dates. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W1sivYNqY2TS5ftykmHBU9
This commit is contained in:
@@ -69,6 +69,20 @@ const CONTRACT = [
|
||||
{ promise: 'the ceiling stance / grade scale legend',
|
||||
payload: null, backend: 'src/services/model/servedGrade.js',
|
||||
adapter: null, component: 'web/src/components/vyndr/GradeScaleLegend.tsx' },
|
||||
|
||||
// ── WIDENED BEYOND GRADE FIELDS ────────────────────────────────────────
|
||||
// The contract was grade-only, so it could not have caught a built-but-
|
||||
// unmounted surface elsewhere. Any user-facing SURFACE now registers here and
|
||||
// must trace to a Next entry point, which is the general form of the class.
|
||||
{ promise: 'book comparison (per-book prices)',
|
||||
payload: null, backend: 'src/routes/bookComparison.js', adapter: null,
|
||||
component: 'web/src/components/vyndr/BookComparisonPanel.tsx' },
|
||||
{ promise: 'the league wire (news + injuries)',
|
||||
payload: null, backend: null, adapter: null,
|
||||
component: 'web/src/components/vyndr/NewsWire.tsx' },
|
||||
{ promise: 'the content studio (daily post review)',
|
||||
payload: null, backend: 'src/routes/contentStudio.js', adapter: null,
|
||||
component: 'web/src/app/studio/page.tsx' },
|
||||
];
|
||||
|
||||
const read = (rel) => {
|
||||
@@ -152,6 +166,12 @@ describe('every promised honest field reaches a rendered pixel', () => {
|
||||
);
|
||||
|
||||
it.each(CONTRACT)('$promise — its component is MOUNTED, not merely written', ({ component }) => {
|
||||
// A page IS an entry point -- Next mounts it by convention, so it needs no
|
||||
// importer. Everything else must be reachable FROM one.
|
||||
if (isEntry(component)) {
|
||||
expect(fs.existsSync(path.join(ROOT, component))).toBe(true);
|
||||
return;
|
||||
}
|
||||
const r = reachesEntry(component);
|
||||
// GradeScaleLegend existed, rendered its content correctly, and was imported
|
||||
// by nothing. That is what this catches.
|
||||
|
||||
Reference in New Issue
Block a user