49e76068da
PHASE 0 — specs/ARCHETYPE-TAXONOMY-DOCTRINE.md records the ruling as
shared law: 83 designed glyphs are the full four-sport taxonomy; a glyph
renders ONLY where its archetype is modeled and proven. 39 of 83 map to a
real archetype and are wired; the 44 unmapped are DORMANT SLOTS for
WNBA/NBA/Soccer, not a wiring gap. Wiring them would mean inventing 44
archetypes to consume artwork -- decoration presented as classification,
which is forbidden. DUAL THREAT and PAINT BOSS are modeled archetypes with
no mark: the mirror gap, flagged to the design side. When a sport's
archetypes ship, activation is a MANIFEST lookup, not new art.
PHASE 1 — E1 movement strip. The spec's own line is "the movement strip is
defined once here and reused everywhere a line has a past", so it is a
primitive, not a fourth chart.
RECONCILED RATHER THAN FORKED: lib/gradeShift.js ALREADY implements E1's
colour law -- toward/against/flat, including the direction flip that makes
an UNDER's favourable move the opposite sign of an OVER's. MovementStrip
CONSUMES buildGradeTimeline instead of reimplementing it, and a test
asserts it never redefines isUnder. GradeShift stays the grade-history
view; this is the reusable strip. That is the card-fork lesson applied
before it could happen again.
Spec laws honoured: STEPS NOT CURVES (H then V, no smoothing -- a curve
invents prices that never traded, and a test rejects any C/S/Q/T command);
green only when the move FAVOURS the read; FLAT renders as a hairline plus
FLAT · [N]D because a flat market is a finding; and too little history
says NO MOVEMENT HISTORY rather than rendering blank.
PHASE 2 — F9-F11 offseason hub shell, built from Vyndr Offseason.dc.html.
The spec's load-bearing words are used verbatim: "OUTLOOKS REPRICE ON NEWS
· NOT GAME ODDS" (an offseason number is not a game line), the QUIET WIRE
empty state ("No outlook-moving news since X. We don't manufacture
movement."), WHAT CHANGED TODAY as the hero with the countdown ambient and
top-right, the tag-colour-is-meaning row anatomy, the open -> NOW -> FAIR
triplet with the movement strip embedded, and the OUTLOOK ONLY block where
every row carries NOT GRADED.
THE DESIGN FILE'S SAMPLE DATA IS NOT IN THE COMPONENT. Wembanyama +420 ->
+330, Nabers cleared 11:42 AM, the Summer League names -- all of it is a
SPEC for what a live feed renders, and copying it in would be fabrication
carrying a designer's authority. A test asserts none of those strings
appear.
The IN-SEASON information architecture is NOT invented here. The spec
covers an offseason hub; nothing specifies how content, articles, wire and
the live slate share year-round navigation. That remains the open design
gap, and the route notes it.
Two test bugs caught and fixed: my first assertions matched my own doc
comments -- the ordering check found "WHAT CHANGED TODAY" in the header
block and the no-curves check caught the word "curve" in the sentence
explaining why curves are wrong. A guard that reads its own explanation is
not reading the render; both now strip comments first.
PHASE 3 — both surfaces registered under the reachability guard. Read-only
throughout, serving fingerprint 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
106 lines
4.1 KiB
JavaScript
106 lines
4.1 KiB
JavaScript
'use strict';
|
|
|
|
/**
|
|
* F9-F11 offseason hub + E1 movement strip — the laws, not the pixels.
|
|
*
|
|
* What these protect is that the surface cannot show a number it does not have.
|
|
* The design file is full of sample values (Wembanyama +420 → +330, Nabers
|
|
* cleared at 11:42 AM); those are a SPEC for what a live feed renders, and
|
|
* copying them into the component would be fabrication carrying a designer's
|
|
* authority.
|
|
*/
|
|
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
const ROOT = path.join(__dirname, '..', '..');
|
|
const read = (r) => fs.readFileSync(path.join(ROOT, r), 'utf8');
|
|
|
|
/**
|
|
* Strip comments before matching. My first version of these assertions matched
|
|
* my own doc blocks -- the ordering check found "WHAT CHANGED TODAY" in the
|
|
* header comment, and the no-curves check caught the word "curve" in the
|
|
* sentence explaining why curves are wrong. A guard that reads its own
|
|
* explanation is not reading the render.
|
|
*/
|
|
const stripComments = (s) => s.replace(/\/\*[\s\S]*?\*\//g, '').replace(/^\s*\/\/.*$/gm, '');
|
|
|
|
const HUB_RAW = read('web/src/components/vyndr/OffseasonHub.tsx');
|
|
const STRIP_RAW = read('web/src/components/vyndr/MovementStrip.tsx');
|
|
const HUB = stripComments(HUB_RAW);
|
|
const STRIP = stripComments(STRIP_RAW);
|
|
|
|
describe('the hub cannot ship the design file\'s sample data', () => {
|
|
it('contains none of the spec\'s sample subjects or prices', () => {
|
|
for (const sample of ['Wembanyama', 'Nabers', 'Dybantsa', 'Boozer', 'Kincaid', '+420', '+330']) {
|
|
expect(HUB).not.toContain(sample);
|
|
}
|
|
});
|
|
|
|
it('renders the QUIET WIRE empty state verbatim, not a generic blank', () => {
|
|
expect(HUB).toMatch(/QUIET WIRE/);
|
|
expect(HUB).toMatch(/We don't manufacture movement\./);
|
|
});
|
|
|
|
it('carries the spec\'s load-bearing subhead', () => {
|
|
// An offseason number is not a game line; saying so is the difference
|
|
// between a read and a bet.
|
|
expect(HUB).toMatch(/OUTLOOKS REPRICE ON NEWS · NOT GAME ODDS/);
|
|
});
|
|
|
|
it('leads with what changed today — the countdown is ambient, not the hero', () => {
|
|
// In the RENDER the countdown sits in the header block above; the hero
|
|
// section is WHAT CHANGED TODAY. Both must be present and ordered.
|
|
const iCountdown = HUB.indexOf('DAYS TO');
|
|
const iHero = HUB.indexOf('WHAT CHANGED TODAY');
|
|
expect(iCountdown).toBeGreaterThan(-1);
|
|
expect(iHero).toBeGreaterThan(iCountdown);
|
|
expect(HUB_RAW).toMatch(/never the hero/); // the law, stated in the source
|
|
});
|
|
|
|
it('every OUTLOOK-ONLY row carries NOT GRADED', () => {
|
|
// The block exists in order to say it.
|
|
expect(HUB).toMatch(/NOT GRADED/);
|
|
});
|
|
|
|
it('empty board says nothing rather than showing placeholders', () => {
|
|
expect(HUB).toMatch(/Nothing shown rather than a board of placeholders/);
|
|
});
|
|
});
|
|
|
|
describe('E1 movement strip obeys its spec laws', () => {
|
|
it('does NOT reimplement the colour law — it consumes gradeShift', () => {
|
|
// Forking the toward/against rule is how two surfaces drift apart.
|
|
expect(STRIP).toMatch(/buildGradeTimeline/);
|
|
expect(STRIP).not.toMatch(/isUnder\s*\(/);
|
|
});
|
|
|
|
it('draws STEPS, not curves', () => {
|
|
// H then V — a hold, then a jump. No smoothing, which would invent prices
|
|
// that never traded.
|
|
expect(STRIP).toMatch(/H\$\{x1\} V\$\{y1\}/);
|
|
// No SVG curve commands in the emitted path -- C, S, Q or T.
|
|
expect(STRIP).not.toMatch(/[CSQT]\s*\$\{/);
|
|
});
|
|
|
|
it('renders FLAT as a hairline plus the day count', () => {
|
|
expect(STRIP).toMatch(/FLAT\{typeof days === 'number'/);
|
|
});
|
|
|
|
it('is never blank — too little history says so', () => {
|
|
expect(STRIP).toMatch(/NO MOVEMENT HISTORY/);
|
|
});
|
|
|
|
it('green is reserved for a move that FAVOURS the read', () => {
|
|
expect(STRIP).toMatch(/dir === 'toward' \? TOWARD/);
|
|
});
|
|
});
|
|
|
|
describe('the archetype doctrine is recorded', () => {
|
|
it('the 83-taxonomy doctrine exists and forbids decoration-as-data', () => {
|
|
const d = read('specs/ARCHETYPE-TAXONOMY-DOCTRINE.md');
|
|
expect(d).toMatch(/Decoration-as-data is forbidden/);
|
|
expect(d).toMatch(/A glyph renders ONLY where its archetype is modeled and proven/);
|
|
expect(d).toMatch(/DUAL THREAT/);
|
|
});
|
|
});
|