// 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.
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)} />