// Main app // Depends on: everything const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{ "density": "comfortable" }/*EDITMODE-END*/; function TweaksPanel({ open, onClose, values, onChange }) { if (!open) return null; return (
tweaks · display
conversation style (voice, asymmetry, directness) lives in phase 00 and is co-signed by both parties. these tweaks only affect how this view renders for you.
density
{[ { v: 'comfortable', l: 'comfortable' }, { v: 'compact', l: 'compact' }, ].map(o => (
onChange({ density: o.v })} >{o.l}
))}
); } class PhaseErrorBoundary extends React.Component { constructor(props) { super(props); this.state = { error: null }; } static getDerivedStateFromError(error) { return { error }; } componentDidCatch(error, info) { console.error('PHASE CRASH', this.props.phase, error, info.componentStack); } render() { if (this.state.error) { return (
crash in phase: {this.props.phase}
{this.state.error.message} {"\n\n"} {this.state.error.stack}
); } return this.props.children; } } function StyleGate({ onGo }) { return (
phase 01 · topic lock

set the tone first.

before any topic drafting, both parties pick and co-sign the session\u2019s conversation style. this shapes every agent\u2019s voice for the rest of the session and can\u2019t be edited mid-argument.

); } function App() { const [phase, setPhase] = React.useState(() => { const saved = localStorage.getItem('cg_phase'); const liveMode = CG.hasKey && CG.hasKey() && CG.getMode && CG.getMode() === 'fresh'; if (liveMode) { // Default to style in live mode until style is locked. Once locked, allow saved phase. const styleLocked = CG.session && CG.session.isStyleLocked && CG.session.isStyleLocked(); if (!styleLocked) return 'style'; if (!saved) return 'topic'; } return saved || 'topic'; }); // Seat: 'A' or 'B'. Coerce any legacy 'god' value. const [seat, setSeat] = React.useState(() => { const s = localStorage.getItem('cg_seat'); return (s === 'A' || s === 'B') ? s : 'A'; }); const [tweaks, setTweaks] = React.useState(TWEAK_DEFAULTS); const [tweaksOpen, setTweaksOpen] = React.useState(false); const [claudeModalOpen, setClaudeModalOpen] = React.useState(false); const [roomModalOpen, setRoomModalOpen] = React.useState(false); // When in multiplayer, lock seat to session seat const [, forceSess] = React.useState(0); React.useEffect(() => { const u1 = CG.peer.onStatus(() => forceSess(t => t + 1)); const u2 = CG.session.onState(() => forceSess(t => t + 1)); const u3 = CG.onStatus(() => forceSess(t => t + 1)); return () => { u1(); u2(); u3(); }; }, []); const sessionSeat = CG.session.getSeat(); React.useEffect(() => { if (sessionSeat && sessionSeat !== seat) setSeat(sessionSeat); }, [sessionSeat]); React.useEffect(() => { localStorage.setItem('cg_phase', phase); }, [phase]); React.useEffect(() => { localStorage.setItem('cg_seat', seat); }, [seat]); // Reset phase when a hard reset fires (new room, start fresh, etc.) React.useEffect(() => { const handler = () => setPhase('style'); window.addEventListener('cg:reset', handler); return () => window.removeEventListener('cg:reset', handler); }, []); // Broadcast this user's current phase to the partner via shared.cursor[seat].phase. // Purely presence — nobody is forced to follow anyone. React.useEffect(() => { if (!CG.session.isMultiplayer || !CG.session.isMultiplayer()) return; const mySeat = CG.session.getSeat(); if (!mySeat) return; try { const cur = (CG.session.shared && CG.session.shared.cursor) || {}; CG.session.setShared({ cursor: { ...cur, [mySeat]: { ...(cur[mySeat] || {}), phase } } }); } catch (e) {} }, [phase, sessionSeat]); // Tweaks edit-mode protocol React.useEffect(() => { const handler = (e) => { if (e.data?.type === '__activate_edit_mode') setTweaksOpen(true); if (e.data?.type === '__deactivate_edit_mode') setTweaksOpen(false); }; window.addEventListener('message', handler); window.parent.postMessage({ type: '__edit_mode_available' }, '*'); return () => window.removeEventListener('message', handler); }, []); const updateTweaks = (patch) => { const next = { ...tweaks, ...patch }; setTweaks(next); window.parent.postMessage({ type: '__edit_mode_set_keys', edits: patch }, '*'); }; // Temperature rough mapping to phase — removed along with the top-bar gauge. // Live-mode routing: every phase is Claude-backed. Topic (01) is blocked until style (00) is co-signed. const liveMode = (CG.hasKey() && CG.getMode() === 'fresh') || !!(CG.session && CG.session.isGuest && CG.session.isGuest()); const styleLocked = CG.session && CG.session.isStyleLocked && CG.session.isStyleLocked(); const showStyleGate = liveMode && phase === 'topic' && !styleLocked; const PhaseView = { style: StyleLockPhase, topic: TopicLockPhase, intake: liveMode && window.LiveIntakePhase ? window.LiveIntakePhase : IntakePhase, opening: liveMode && window.LiveOpeningPhase ? window.LiveOpeningPhase : OpeningPhase, steelman: liveMode && window.LiveSteelmanPhase ? window.LiveSteelmanPhase : SteelmanPhase, room: liveMode && window.LiveRoomPhase ? window.LiveRoomPhase : RoomPhase, callout: CalloutPhase, apology: liveMode && window.LiveApologyPhase ? window.LiveApologyPhase : ApologyPhase, artifact: liveMode && window.LiveArtifactPhase ? window.LiveArtifactPhase : ArtifactPhase, }[phase] || TopicLockPhase; return (
setClaudeModalOpen(true)} onOpenRoom={() => setRoomModalOpen(true)} onPause={() => alert('paused · state saved · each party gets a private "where we left off" note')} /> setClaudeModalOpen(false)} /> setRoomModalOpen(false)} />
{showStyleGate ? setPhase('style')} /> : }
setTweaksOpen(false)} values={tweaks} onChange={updateTweaks} /> {/* Coach's ledger · private floating drawer */} {phase !== 'style' && phase !== 'topic' && } {window.CG_VERSION && (
{window.CG_VERSION}
)}
); } ReactDOM.createRoot(document.getElementById('root')).render();