From 65bf42c4dbf96e5943e51b0ffff80b01360cad26 Mon Sep 17 00:00:00 2001
From: Claude
Date: Sat, 25 Apr 2026 02:36:39 +0000
Subject: [PATCH 1/2] feat(symbolic-moment): add person picker for Symbolic
Moment with staging
Surfaces an explicit target picker when the user kicks off a Symbolic Moment
with one or more staged counterparts, mirroring the Solo Mirror flow. Adds a
`pendingSymbolicMomentResolver` state and a `runSymbolicMomentLane` callback
that accepts `profileOverride`/`forcePrimaryScope` so each picker option can
route the read at the correct anchor profile. Also offers Map Us, Stage From
Vault, and Dismiss escape hatches so people-selection from vault or after
staging is now obvious instead of being limited to the relational read lane.
https://claude.ai/code/session_01WiNwPjtGzyNaAMab6srzqy
---
vessel/src/app/page.tsx | 145 +++++++++++++++++++++++++++++++++++++---
1 file changed, 134 insertions(+), 11 deletions(-)
diff --git a/vessel/src/app/page.tsx b/vessel/src/app/page.tsx
index fd3813f2..73afffcc 100644
--- a/vessel/src/app/page.tsx
+++ b/vessel/src/app/page.tsx
@@ -579,11 +579,19 @@ export default function App() {
const [pendingSoloMirrorResolver, setPendingSoloMirrorResolver] = useState<{
desc: string;
} | null>(null);
+ const [pendingSymbolicMomentResolver, setPendingSymbolicMomentResolver] = useState<{
+ desc: string;
+ } | null>(null);
useEffect(() => {
if (!pendingSoloMirrorResolver) return;
if (observerStagedProfiles.length > 0) return;
setPendingSoloMirrorResolver(null);
}, [observerStagedProfiles.length, pendingSoloMirrorResolver]);
+ useEffect(() => {
+ if (!pendingSymbolicMomentResolver) return;
+ if (observerStagedProfiles.length > 0) return;
+ setPendingSymbolicMomentResolver(null);
+ }, [observerStagedProfiles.length, pendingSymbolicMomentResolver]);
useEffect(() => {
if (observerStagedProfiles.length > 0) return;
if (!primaryOnlyScope) return;
@@ -1789,6 +1797,7 @@ export default function App() {
setPrimaryOnlyScope(false);
setStagedContexts([]);
setPendingSoloMirrorResolver(null);
+ setPendingSymbolicMomentResolver(null);
advancedClockSuggestionShownForSessionRef.current = null;
setAdvancedClocksOpen(false);
setLastDebugInfo(null);
@@ -1841,6 +1850,7 @@ export default function App() {
);
setPendingSemanticDepth('plain');
setPendingSoloMirrorResolver(null);
+ setPendingSymbolicMomentResolver(null);
setIsFlightRecorderOpen(false);
setReadLaneArmed(snapshot.structuredReadingOptIn === true && snapshot.promptMode !== 'CHAT');
});
@@ -2557,6 +2567,7 @@ export default function App() {
const handleHeaderModeSelect = useCallback((mode: 'CHAT' | 'SOLO_MIRROR' | 'FIELD_REPORT') => {
setPendingSoloMirrorResolver(null);
+ setPendingSymbolicMomentResolver(null);
if (mode === 'CHAT') {
setStructuredReadingOptIn(false);
setActiveMode('CHAT');
@@ -2577,6 +2588,9 @@ export default function App() {
if (intent !== 'CHECK_BONES' && pendingSoloMirrorResolver) {
setPendingSoloMirrorResolver(null);
}
+ if (intent !== 'SYMBOLIC_MOMENT' && pendingSymbolicMomentResolver) {
+ setPendingSymbolicMomentResolver(null);
+ }
if (structuredReadLocked && intentRequiresArchitect) {
const requestedMode = resolveGuidedEntryTargetMode(intent);
@@ -2693,18 +2707,13 @@ export default function App() {
return;
}
if (intent === 'SYMBOLIC_MOMENT') {
- const structuredFieldReport = true;
setRelationshipMappingActive(false);
- enterReadMode('FIELD_REPORT', structuredFieldReport);
- if (!requestReadArm('guided_entry_symbolic_moments')) {
+ const symbolicDesc = safeDesc || 'Read my symbolic moment(s).';
+ if (observerStagedProfiles.length > 0 && !soloScopedProfile) {
+ setPendingSymbolicMomentResolver({ desc: symbolicDesc });
return;
}
- armCorridorLoadingMode(safeDesc || 'Read my symbolic moment(s).');
- maybePrimeAlignmentCorridor('guided_entry', { targetMode: 'FIELD_REPORT' });
- queueGuidedSend(safeDesc || 'Read my symbolic moment(s).', {
- targetModeOverride: 'FIELD_REPORT',
- structuredReadingOptInOverride: structuredFieldReport,
- });
+ runSymbolicMomentLane(symbolicDesc, { source: 'guided_entry_symbolic_moments' });
return;
}
if (intent === 'SET_LOCATION') {
@@ -2759,7 +2768,7 @@ export default function App() {
structuredReadingOptInOverride: true,
});
// eslint-disable-next-line react-hooks/exhaustive-deps
- }, [activeMode, activeProfile, appendClientRuntimeEvent, appendConsoleNotice, appendStructuredUpgradeGateNotice, armCorridorLoadingMode, clearCorridorPrimeTimers, observerStagedProfiles, observerStagedProfiles.length, pendingSoloMirrorResolver, primeAlignmentCorridor, quickScopeCounterpart?.name, requestReadArm, soloScopedProfile, stagedContexts.length, structuredReadLocked, structuredReadingOptIn]);
+ }, [activeMode, activeProfile, appendClientRuntimeEvent, appendConsoleNotice, appendStructuredUpgradeGateNotice, armCorridorLoadingMode, clearCorridorPrimeTimers, observerStagedProfiles, observerStagedProfiles.length, pendingSoloMirrorResolver, pendingSymbolicMomentResolver, primeAlignmentCorridor, quickScopeCounterpart?.name, requestReadArm, soloScopedProfile, stagedContexts.length, structuredReadLocked, structuredReadingOptIn]);
const sendScopedMessage = useCallback((
text: string,
@@ -2973,9 +2982,51 @@ export default function App() {
targetModeOverride: 'SOLO_MIRROR',
structuredReadingOptInOverride: structuredSoloMirror,
}).catch(() => { });
-
+
}, [armCorridorLoadingMode, primeAlignmentCorridor, requestReadArm, sendMessage, sendScopedMessage, structuredReadLocked]);
+ const runSymbolicMomentLane = useCallback((
+ desc: string,
+ options?: {
+ forcePrimaryScope?: boolean;
+ profileOverride?: VaultProfile | null;
+ source?: string;
+ },
+ ) => {
+ const structuredFieldReport = true;
+ setRelationshipMappingActive(false);
+ chatInputRef.current?.setText?.('');
+ const safeDesc = typeof desc === 'string' && desc.trim().length > 0 ? desc : 'Read my symbolic moment(s).';
+ setStructuredReadingOptIn(structuredFieldReport);
+ setActiveMode('FIELD_REPORT');
+ if (!requestReadArm(options?.source || 'guided_entry_symbolic_moments')) {
+ return;
+ }
+ armCorridorLoadingMode(safeDesc);
+ maybePrimeAlignmentCorridor('guided_entry', { targetMode: 'FIELD_REPORT' });
+ if (options?.profileOverride) {
+ void sendMessage(safeDesc, {
+ profileOverride: options.profileOverride,
+ stagedContextsOverride: [],
+ targetModeOverride: 'FIELD_REPORT',
+ structuredReadingOptInOverride: structuredFieldReport,
+ }).catch(() => { });
+ return;
+ }
+ if (options?.forcePrimaryScope) {
+ void sendMessage(safeDesc, {
+ stagedContextsOverride: [],
+ targetModeOverride: 'FIELD_REPORT',
+ structuredReadingOptInOverride: structuredFieldReport,
+ }).catch(() => { });
+ return;
+ }
+ void sendScopedMessage(safeDesc, {
+ targetModeOverride: 'FIELD_REPORT',
+ structuredReadingOptInOverride: structuredFieldReport,
+ }).catch(() => { });
+ }, [armCorridorLoadingMode, primeAlignmentCorridor, requestReadArm, sendMessage, sendScopedMessage]);
+
const handleCounterpartSoloMirror = useCallback((profile: VaultProfile, source: string) => {
appendClientRuntimeEvent('COUNTERPART_QUICK_ACTION_SELECTED', {
action: 'solo_mirror',
@@ -4047,6 +4098,78 @@ export default function App() {
)}
+ {pendingSymbolicMomentResolver && observerStagedProfiles.length > 0 && (
+
+
+ Symbolic Moment Target
+
+
+ A counterpart is staged. Choose whose chart should anchor the symbolic moment, or map both together.
+
+
+ {
+ setPendingSymbolicMomentResolver(null);
+ setSoloFocusProfileId(null);
+ runSymbolicMomentLane(
+ pendingSymbolicMomentResolver.desc,
+ { forcePrimaryScope: true, source: 'guided_entry_symbolic_moments_primary' },
+ );
+ }}
+ className="px-3 py-1.5 rounded-md border border-emerald-400/40 bg-emerald-500/20 text-emerald-100 text-[10px] font-mono uppercase tracking-wider hover:bg-emerald-500/30 transition-colors"
+ >
+ Read For Me {activeProfile?.name ? `(${activeProfile.name})` : ''}
+
+ {observerStagedProfiles.map((profile) => (
+ {
+ setPendingSymbolicMomentResolver(null);
+ setSoloFocusProfileId(profile.id);
+ appendConsoleNotice(
+ `Solo focus is now set to ${profile.name}. Upcoming turns will read ${profile.name}'s chart unless you tap Restore Me.`,
+ );
+ runSymbolicMomentLane(
+ pendingSymbolicMomentResolver.desc,
+ { profileOverride: profile, source: `guided_entry_symbolic_moments_${profile.id}` },
+ );
+ }}
+ className="px-3 py-1.5 rounded-md border border-cyan-400/40 bg-cyan-500/20 text-cyan-100 text-[10px] font-mono uppercase tracking-wider hover:bg-cyan-500/30 transition-colors"
+ >
+ Symbolic Moment: {profile.name}
+
+ ))}
+ {
+ setPendingSymbolicMomentResolver(null);
+ setSoloFocusProfileId(null);
+ handleGuidedEntry(GUIDED_ENTRY_LABELS.MAP_US, 'Map us across the staged counterpart.');
+ }}
+ className="px-3 py-1.5 rounded-md border border-violet-400/40 bg-violet-500/20 text-violet-100 text-[10px] font-mono uppercase tracking-wider hover:bg-violet-500/30 transition-colors"
+ >
+ {GUIDED_ENTRY_LABELS.MAP_US}
+
+
+ Stage From Vault
+
+ setPendingSymbolicMomentResolver(null)}
+ className="px-2.5 py-1.5 rounded-md border border-transparent text-[10px] font-mono uppercase tracking-wider text-slate-500 hover:text-slate-300"
+ >
+ Dismiss
+
+
+
+ )}
+
{(() => {
const inlineStagePersonControl = (
Date: Sun, 26 Apr 2026 19:47:22 +0000
Subject: [PATCH 2/2] feat(symbolic-moment): add all-staged and all-in-scope
picker options
Adds two new picker buttons for multi-person reads:
- "Read All Staged" (indigo): reads all staged counterparts excluding primary
- "All In Scope" (sky): reads primary + all staged profiles together
Implements `stagedOnlyScope` option in `runSymbolicMomentLane` to pass
observer profiles only to sendMessage. Updates picker copy based on the
number of staged profiles and adds tooltips explaining each option.
https://claude.ai/code/session_01WiNwPjtGzyNaAMab6srzqy
---
vessel/src/app/page.tsx | 49 +++++++++++++++++++++++++++++++++++++++--
1 file changed, 47 insertions(+), 2 deletions(-)
diff --git a/vessel/src/app/page.tsx b/vessel/src/app/page.tsx
index 73afffcc..32f91bbf 100644
--- a/vessel/src/app/page.tsx
+++ b/vessel/src/app/page.tsx
@@ -2989,6 +2989,7 @@ export default function App() {
desc: string,
options?: {
forcePrimaryScope?: boolean;
+ stagedOnlyScope?: boolean;
profileOverride?: VaultProfile | null;
source?: string;
},
@@ -3021,11 +3022,19 @@ export default function App() {
}).catch(() => { });
return;
}
+ if (options?.stagedOnlyScope) {
+ void sendMessage(safeDesc, {
+ stagedContextsOverride: observerStagedProfiles.map((p) => ({ profile: p, role: 'OBSERVER' as const })),
+ targetModeOverride: 'FIELD_REPORT',
+ structuredReadingOptInOverride: structuredFieldReport,
+ }).catch(() => { });
+ return;
+ }
void sendScopedMessage(safeDesc, {
targetModeOverride: 'FIELD_REPORT',
structuredReadingOptInOverride: structuredFieldReport,
}).catch(() => { });
- }, [armCorridorLoadingMode, primeAlignmentCorridor, requestReadArm, sendMessage, sendScopedMessage]);
+ }, [armCorridorLoadingMode, observerStagedProfiles, primeAlignmentCorridor, requestReadArm, sendMessage, sendScopedMessage]);
const handleCounterpartSoloMirror = useCallback((profile: VaultProfile, source: string) => {
appendClientRuntimeEvent('COUNTERPART_QUICK_ACTION_SELECTED', {
@@ -4104,7 +4113,9 @@ export default function App() {
Symbolic Moment Target
- A counterpart is staged. Choose whose chart should anchor the symbolic moment, or map both together.
+ {observerStagedProfiles.length === 1
+ ? `${observerStagedProfiles[0].name} is staged. Choose who or what to read in this moment.`
+ : `${observerStagedProfiles.length} profiles staged. Choose the focus or read them together.`}
))}
+ {observerStagedProfiles.length > 1 && (
+ {
+ setPendingSymbolicMomentResolver(null);
+ setSoloFocusProfileId(null);
+ runSymbolicMomentLane(
+ pendingSymbolicMomentResolver.desc,
+ { stagedOnlyScope: true, source: 'guided_entry_symbolic_moments_all_staged' },
+ );
+ }}
+ className="px-3 py-1.5 rounded-md border border-indigo-400/40 bg-indigo-500/20 text-indigo-100 text-[10px] font-mono uppercase tracking-wider hover:bg-indigo-500/30 transition-colors"
+ title={`Read symbolic moment for: ${observerStagedProfiles.map((p) => p.name).join(', ')}`}
+ >
+ Read All Staged ({observerStagedProfiles.length})
+
+ )}
+ {observerStagedProfiles.length > 0 && (
+ {
+ setPendingSymbolicMomentResolver(null);
+ setSoloFocusProfileId(null);
+ runSymbolicMomentLane(
+ pendingSymbolicMomentResolver.desc,
+ { source: 'guided_entry_symbolic_moments_all_in_scope' },
+ );
+ }}
+ className="px-3 py-1.5 rounded-md border border-sky-400/40 bg-sky-500/20 text-sky-100 text-[10px] font-mono uppercase tracking-wider hover:bg-sky-500/30 transition-colors"
+ title="Read the symbolic moment for everyone in scope (you + all staged profiles)"
+ >
+ All In Scope ({observerStagedProfiles.length + 1})
+
+ )}
{