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 rendered
    choiceId:     'help',            // diagnostic only — not rendered
    sceneRoot:    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 open
window.CovertCubicles.UI_Modal.getApi();      // → diagnostic surface
§2 · Automated assertions
IDAssertionVerdict
Assertions pending execution.
§3 · Integrated demo · M26 + M27 + M28 + orchestrator stub
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.
Demo · Full decision loop
§4 · Orchestrator stub event log