Classification · Internal Research Build · Covert Cubicles M28
PENDING
M28 · Phase 6 · Layer 7 (UI & Presentation)
UI_Modal — Consequence Modal Renderer
Post-decision overlay rendering literary continuation prose with focus-trapped Continue dismissal.
Module of
31 (Module 28)
Dependencies
None — fully decoupled, consequence record passed in via render() opts
Consumed by
Orchestrator (test page now, M31 GM_Game in production)
ADRs recorded
ADR-099 to ADR-104 (six)
Test strategy
Hybrid per ADR-092: API assertions + integrated M26+M27+M28 demo
Generated
5 May 2026 · Build state v1.8 (M26, M27 closed; M28 closes Phase 6 UI)
Contract banner — orchestrator responsibilities (full decision loop)
M28 is invoked AFTER M27's onChoice fires AND the orchestrator has resolved the consequence record. The orchestrator: (1) receives onChoice from M27, (2) calls LOGIC_Decision.submitDecision(), (3) calls LOGIC_Consequence.applyEffects() to resolve the consequence record, (4) captures the modal-open event for M21, (5) calls M28.render() with the consequence, (6) waits for onDismiss, (7) captures the modal-close event for M21, (8) advances or holds scene state, (9) re-renders M27 with the next decision. The integrated demo below executes this full loop end-to-end.
Synthetic consequence records
The consequences exercised below are constructed inline in the test page. Real-module integration (LOGIC_Consequence resolving from M3 branchText plus stability/relational deltas) is verified at the next regression sweep.
§1 · Public API surface
// M28 is presentation-only. No data dependencies, no logic dependencies.window.CovertCubicles.UI_Modal.render(target, {
consequence: { branchText: '...', /* other fields preserved as metadata */ },
onDismiss: function () { /* orchestrator handles next state */ },
decisionId: 'p1-d2', // diagnostic only — not renderedchoiceId: 'help', // diagnostic only — not renderedsceneRoot: sceneEl // optional: receives inert during open
});
window.CovertCubicles.UI_Modal.closeModal();
// Escape hatch: forcibly closes any open modal WITHOUT invoking onDismiss.window.CovertCubicles.UI_Modal.isOpen(); // → true if a modal is currently openwindow.CovertCubicles.UI_Modal.getApi(); // → diagnostic surface
The frame below renders the full decision loop. Click a phase button to present a decision; click a choice; the consequence modal will appear on top of the scene. Click Continue to dismiss; the orchestrator stub logs every event and re-renders M27 with the next decision in sequence (or empty-state on the final decision). The demo ends with M27 cleared after Phase 4's consequence dismisses.