Skip to content

Commit 33be2ad

Browse files
committed
Update project configuration and enhance onboarding experience
- Change PROJECT_ID and SERVICE_NAME in deployment scripts to reflect the correct project settings. - Modify AbstractionCarousel component to include 'related' type in filtering logic for reachable levels. - Enhance NodeCanvas component to manage onboarding modal visibility and local storage state. - Update SaveStatusDisplay to handle browser-only mode and improve user interaction. - Allow AlphaOnboardingModal to remain open during universe loading for better user experience.
1 parent c92b1ac commit 33be2ad

8 files changed

Lines changed: 289 additions & 21 deletions

ONBOARDING_FIXES.md

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
# Onboarding Flow Fixes & UX Improvements
2+
3+
## Issues Fixed
4+
5+
### 1. Modal Closing Prematurely
6+
**Problem**: The onboarding modal was closing immediately when clicking local file buttons, before the universe actually loaded. This meant users saw the modal disappear but then potentially reappear if the load failed.
7+
8+
**Solution**:
9+
- Removed immediate `handleClose()` calls from local file buttons in `AlphaOnboardingModal.jsx`
10+
- Added auto-close logic in `NodeCanvas.jsx` that watches `isUniverseLoaded`, `hasUniverseFile`, and `universeLoadingError` states
11+
- Modal now closes automatically only when the universe successfully loads
12+
- Lines changed:
13+
- `AlphaOnboardingModal.jsx:287, 305` (removed handleClose calls)
14+
- `AlphaOnboardingModal.jsx:562` (removed handleClose from GitHub flow)
15+
- `NodeCanvas.jsx:1230-1234` (added auto-close logic)
16+
17+
### 2. File Handle Reconnection Issue
18+
**Problem**: When loading an existing local file, the file handle was stored in `fileStorage.js` but not communicated to `universeBackend.js` (which manages saves through the new universe system). This caused "reconnect to continue saving locally" prompts.
19+
20+
**Solution**:
21+
- Added file handle bridging after successful file creation/opening
22+
- After `loadUniverseFromFile()` completes, the file handle is retrieved from `fileStorage` and registered with `universeBackend`
23+
- Added 200ms delay (100ms setTimeout + 100ms internal wait) to ensure universe backend has initialized
24+
- Added comprehensive error handling and logging
25+
- Lines changed:
26+
- `NodeCanvas.jsx:10784-10810` (createLocal handler)
27+
- `NodeCanvas.jsx:10828-10854` (openLocal handler)
28+
29+
**Key implementation details**:
30+
- Uses `setTimeout` to avoid blocking the UI
31+
- Waits for universe backend to initialize before attempting to bridge
32+
- Logs success/failure clearly for debugging
33+
- Non-fatal errors (fileStorage's autosave may still work as fallback)
34+
35+
### 3. GitHub Flow Modal Behavior
36+
**Problem**: When selecting GitHub with existing OAuth+App connections and clicking "Start with GitHub Sync", the modal was closing immediately before the universe loaded, creating an inconsistent UX.
37+
38+
**Solution**:
39+
- Removed `handleClose()` from the "Start with GitHub Sync" button (line 562)
40+
- Modal now auto-closes when the universe loads, consistent with local file behavior
41+
- GitNativeFederation panel opens properly for all GitHub steps (lines 10889-10890)
42+
43+
### 4. GitNativeFederation Panel Opening
44+
**Status**: Already working correctly!
45+
- Lines 10889-10890 in `NodeCanvas.jsx` ensure the federation panel opens for ALL GitHub flows
46+
- This code runs before the step-specific logic, so it applies universally
47+
- Resume logic (lines 1237-1252) ensures the panel opens after OAuth/App redirects
48+
49+
## Flow Summary
50+
51+
### Local File Creation Flow
52+
1. User clicks "Create New" button
53+
2. File picker opens, user selects location
54+
3. Empty universe created and written to file
55+
4. File handle stored in both `fileStorage` and `universeBackend`
56+
5. Universe marked as loaded (`hasUniverseFile: true`, `isUniverseLoaded: true`)
57+
6. Modal auto-closes when load completes
58+
7. Auto-save enabled for seamless saving
59+
60+
### Local File Opening Flow
61+
1. User clicks "Load Existing" button
62+
2. File picker opens, user selects file
63+
3. File content loaded and validated
64+
4. Universe data imported into store
65+
5. File handle stored in both `fileStorage` and `universeBackend`
66+
6. Universe marked as loaded
67+
7. Modal auto-closes when load completes
68+
8. Auto-save enabled for seamless saving
69+
70+
### GitHub Flow (Existing Connections)
71+
1. User clicks "Start with GitHub Sync"
72+
2. Storage mode set to 'git'
73+
3. Left panel expands and switches to federation view
74+
4. Existing Git universe loaded (or empty state created)
75+
5. Universe marked as loaded
76+
6. Modal auto-closes when load completes
77+
78+
### GitHub Flow (New Setup)
79+
1. User clicks OAuth/App buttons
80+
2. Storage mode set to 'git'
81+
3. Left panel expands and switches to federation view
82+
4. User redirected to GitHub for authentication
83+
5. After redirect, session flags trigger panel re-opening
84+
6. User completes setup in GitNativeFederation panel
85+
86+
## Key Improvements
87+
88+
1. **Consistent UX**: Modal behavior is now consistent across all paths (local and Git)
89+
2. **No Reconnection Prompts**: File handles properly bridged between systems
90+
3. **Clear Logging**: All operations log success/failure for debugging
91+
4. **Graceful Error Handling**: Non-fatal errors don't break the flow
92+
5. **Proper Timing**: Delays ensure systems are initialized before bridging
93+
6. **Federation Panel**: Always opens for Git flows as intended
94+
95+
## New Features Added
96+
97+
### 5. **Dismissable Onboarding Modal**
98+
**Feature**: Users can now close the onboarding modal at any time to explore Redstring, even without setting up a storage method.
99+
100+
**Implementation**:
101+
- Modal can be closed via backdrop click or close button
102+
- Closing sets `redstring-alpha-welcome-seen` in localStorage to prevent re-showing
103+
- Modal won't reappear unless user clears localStorage or has no universe setup
104+
- Successfully completing onboarding also marks it as seen
105+
- Lines changed:
106+
- `NodeCanvas.jsx:1219-1242` (checks localStorage, marks as seen)
107+
- `NodeCanvas.jsx:10777-10783` (marks as seen on manual close)
108+
- `AlphaOnboardingModal.jsx:683` (enables backdrop closing)
109+
110+
**Benefit**: Users can explore the app without being forced through onboarding first. Browser-only storage works as a fallback.
111+
112+
### 6. **"Connect" Button in Browser-Only Mode**
113+
**Feature**: When using browser-only storage (no file or Git connection), the save status indicator shows "Connect" instead of "Not Saved", and clicking it opens the GitNativeFederation panel.
114+
115+
**Implementation**:
116+
- `SaveStatusDisplay` now detects browser-only mode by checking:
117+
- `sourceOfTruth === 'browser'`
118+
- OR no local file handle AND no Git repo linked
119+
- When in browser-only mode, displays "Connect" with pointer cursor
120+
- Clicking opens left panel with federation view
121+
- Hover effect (scale 1.05) indicates it's interactive
122+
- Lines changed:
123+
- `SaveStatusDisplay.jsx:6-78` (detection and state management)
124+
- `SaveStatusDisplay.jsx:80-134` (clickable UI with hover effects)
125+
- `NodeCanvas.jsx:10610-10615` (wires up federation panel opening)
126+
127+
**Benefit**: Clear call-to-action for users to set up persistent storage, directly accessible from the always-visible status indicator.
128+
129+
## Testing Checklist
130+
131+
### Core Onboarding
132+
- [ ] Create new local file → modal closes after creation
133+
- [ ] Load existing local file → modal closes after load
134+
- [ ] Load existing file → save changes → no reconnection prompt
135+
- [ ] GitHub with existing connections → modal closes, federation panel opens
136+
- [ ] GitHub OAuth flow → redirect works, panel opens on return
137+
- [ ] GitHub App flow → redirect works, panel opens on return
138+
- [ ] Cancel file picker → modal stays open with error message
139+
- [ ] File load error → modal shows error, stays open
140+
141+
### New Features
142+
- [ ] Close onboarding modal via backdrop → modal doesn't reappear
143+
- [ ] Close onboarding modal via X button → modal doesn't reappear
144+
- [ ] Complete onboarding → modal doesn't reappear on refresh
145+
- [ ] Browser-only storage → status shows "Connect" instead of "Not Saved"
146+
- [ ] Click "Connect" → left panel opens with GitNativeFederation view
147+
- [ ] Hover over "Connect" → button scales up slightly
148+
- [ ] Set up file or Git → status changes from "Connect" to normal status
149+

scripts/fast-build.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ cd "${REPO_ROOT}"
1313
# Start timing
1414
START_TIME=$(date +%s)
1515

16-
PROJECT_ID="your-project-id"
16+
PROJECT_ID="redstring-470201"
1717
SERVICE_NAME="redstring-test"
1818
REGION="us-central1"
1919
PORT="8080"

scripts/fast-deploy-prod.sh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ BLUE='\033[0;34m'
2121
BOLD='\033[1m'
2222
NC='\033[0m' # No Color
2323

24-
PROJECT_ID="your-project-id"
25-
SERVICE_NAME="your-service-name"
24+
PROJECT_ID="redstring-470201"
25+
SERVICE_NAME="redstring-prod"
2626
REGION="us-central1"
2727
PORT="4000"
2828

scripts/fast-deploy-test.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ BLUE='\033[0;34m'
2121
BOLD='\033[1m'
2222
NC='\033[0m' # No Color
2323

24-
PROJECT_ID="your-project-id"
24+
PROJECT_ID="redstring-470201"
2525
SERVICE_NAME="redstring-test"
2626
REGION="us-central1"
2727
PORT="4000"

src/AbstractionCarousel.jsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -500,7 +500,7 @@ const AbstractionCarousel = ({
500500
if (!abstractionChainWithDims.length) return -6;
501501
// Find the most abstract reachable level (exclude Thing if marked as non-reachable)
502502
const reachableLevels = abstractionChainWithDims
503-
.filter(n => !n.isNonReachable && (n.type === 'current' || n.type === 'generic'))
503+
.filter(n => !n.isNonReachable && (n.type === 'current' || n.type === 'generic' || n.type === 'related'))
504504
.map(n => n.level);
505505

506506
if (reachableLevels.length === 0) return -6;
@@ -522,7 +522,7 @@ const AbstractionCarousel = ({
522522
if (!abstractionChainWithDims.length) return 6;
523523
// Find the most specific reachable level
524524
const reachableLevels = abstractionChainWithDims
525-
.filter(n => !n.isNonReachable && (n.type === 'current' || n.type === 'generic'))
525+
.filter(n => !n.isNonReachable && (n.type === 'current' || n.type === 'generic' || n.type === 'related'))
526526
.map(n => n.level);
527527

528528
if (reachableLevels.length === 0) return 6;
@@ -920,7 +920,7 @@ const AbstractionCarousel = ({
920920
const currentPos = physicsStateRef.current.realPosition;
921921
const rounded = Math.round(currentPos);
922922
const levels = abstractionChainWithDims
923-
.filter(n => !n.isNonReachable && (n.type === 'current' || n.type === 'generic'))
923+
.filter(n => !n.isNonReachable && (n.type === 'current' || n.type === 'generic' || n.type === 'related'))
924924
.map(n => n.level);
925925
const min = Math.min(...levels);
926926
const max = Math.max(...levels);
@@ -995,7 +995,7 @@ const AbstractionCarousel = ({
995995
// Compute hint placement levels and positions
996996
const reachableChainLevels = useMemo(() => {
997997
return abstractionChainWithDims
998-
.filter(n => !n.isNonReachable && (n.type === 'current' || n.type === 'generic'))
998+
.filter(n => !n.isNonReachable && (n.type === 'current' || n.type === 'generic' || n.type === 'related'))
999999
.map(n => n.level);
10001000
}, [abstractionChainWithDims]);
10011001

@@ -1513,4 +1513,4 @@ const AbstractionCarousel = ({
15131513
);
15141514
};
15151515

1516-
export default AbstractionCarousel;
1516+
export default AbstractionCarousel;

src/NodeCanvas.jsx

Lines changed: 86 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1215,8 +1215,12 @@ function NodeCanvas() {
12151215
}
12161216
} catch {}
12171217

1218+
// Check if user has dismissed the onboarding modal before
1219+
const hasSeenOnboarding = localStorage.getItem('redstring-alpha-welcome-seen') === 'true';
1220+
12181221
const shouldShowOnboarding =
12191222
!suppressForGitFlow &&
1223+
!hasSeenOnboarding &&
12201224
!isUniverseLoading && (
12211225
!hasUniverseFile ||
12221226
!isUniverseLoaded ||
@@ -1226,6 +1230,16 @@ function NodeCanvas() {
12261230
if (shouldShowOnboarding && !showOnboardingModal) {
12271231
setShowOnboardingModal(true);
12281232
}
1233+
1234+
// Auto-close modal when universe successfully loads
1235+
if (showOnboardingModal && isUniverseLoaded && hasUniverseFile && !universeLoadingError) {
1236+
console.log('[NodeCanvas] Universe loaded successfully, closing onboarding modal');
1237+
setShowOnboardingModal(false);
1238+
// Mark as seen when successfully completing onboarding
1239+
try {
1240+
localStorage.setItem('redstring-alpha-welcome-seen', 'true');
1241+
} catch {}
1242+
}
12291243
}, [isUniverseLoading, hasUniverseFile, isUniverseLoaded, universeLoadingError, showOnboardingModal]);
12301244

12311245
// Resume Git onboarding after OAuth/App redirects by opening Federation panel and hiding modal
@@ -5582,7 +5596,7 @@ function NodeCanvas() {
55825596
insertRelativeToNodeId: currentlySelectedNode.prototypeId
55835597
});
55845598

5585-
addToAbstractionChain(
5599+
storeActions.addToAbstractionChain(
55865600
chainOwnerPrototypeId, // the node whose chain we're modifying (actual chain owner)
55875601
currentAbstractionDimension, // dimension (Physical, Conceptual, etc.)
55885602
abstractionPrompt.direction, // 'above' or 'below'
@@ -10601,7 +10615,12 @@ function NodeCanvas() {
1060110615
/>
1060210616

1060310617
{/* SaveStatusDisplay Component */}
10604-
<SaveStatusDisplay />
10618+
<SaveStatusDisplay
10619+
onOpenFederation={() => {
10620+
setLeftPanelExpanded(true);
10621+
setLeftPanelInitialView('federation');
10622+
}}
10623+
/>
1060510624

1060610625
{/* NodeControlPanel Component - with animation */}
1060710626
{(nodeControlPanelShouldShow || nodeControlPanelVisible) && (
@@ -10755,15 +10774,21 @@ function NodeCanvas() {
1075510774
{/* Onboarding Modal */}
1075610775
<AlphaOnboardingModal
1075710776
isVisible={showOnboardingModal}
10758-
onClose={() => setShowOnboardingModal(false)}
10777+
onClose={() => {
10778+
setShowOnboardingModal(false);
10779+
// Mark as seen when manually closing
10780+
try {
10781+
localStorage.setItem('redstring-alpha-welcome-seen', 'true');
10782+
} catch {}
10783+
}}
1075910784
onCreateLocal={async () => {
1076010785
try {
1076110786
// Set storage mode to local
1076210787
console.log('[NodeCanvas] Setting storage mode to local for file-based storage');
1076310788
storeActions.setStorageMode('local');
1076410789

1076510790
// Import file storage functions
10766-
const { createUniverseFile, enableAutoSave } = fileStorage;
10791+
const { createUniverseFile, enableAutoSave, getCurrentFileHandle } = fileStorage;
1076710792

1076810793
// Create universe file with file picker
1076910794
const initialData = await createUniverseFile();
@@ -10775,6 +10800,34 @@ function NodeCanvas() {
1077510800
// Enable auto-save
1077610801
enableAutoSave(() => useGraphStore.getState());
1077710802

10803+
// Bridge file handle to universeBackend so saves work without reconnection
10804+
// Add small delay to ensure universe backend has processed the loaded data
10805+
setTimeout(async () => {
10806+
try {
10807+
const { default: universeBackend } = await import('./services/universeBackend.js');
10808+
const currentFileHandle = getCurrentFileHandle();
10809+
10810+
if (!currentFileHandle) {
10811+
console.warn('[NodeCanvas] No file handle available to bridge');
10812+
return;
10813+
}
10814+
10815+
// Wait briefly for universe backend to initialize
10816+
await new Promise(resolve => setTimeout(resolve, 100));
10817+
10818+
const activeUniverse = universeBackend.getActiveUniverse();
10819+
if (activeUniverse) {
10820+
await universeBackend.setFileHandle(activeUniverse.slug, currentFileHandle);
10821+
console.log('[NodeCanvas] ✅ File handle bridged to universeBackend for', activeUniverse.slug);
10822+
} else {
10823+
console.warn('[NodeCanvas] No active universe found to bridge file handle');
10824+
}
10825+
} catch (bridgeError) {
10826+
console.warn('[NodeCanvas] Failed to bridge file handle to universeBackend:', bridgeError);
10827+
// Non-fatal - fileStorage's own autosave might still work
10828+
}
10829+
}, 100);
10830+
1077810831
// Ensure universe connection is marked as established
1077910832
storeActions.setUniverseConnected(true);
1078010833
} else {
@@ -10792,7 +10845,7 @@ function NodeCanvas() {
1079210845
storeActions.setStorageMode('local');
1079310846

1079410847
// Import file storage functions
10795-
const { openUniverseFile, enableAutoSave } = fileStorage;
10848+
const { openUniverseFile, enableAutoSave, getCurrentFileHandle } = fileStorage;
1079610849

1079710850
// Open universe file with file picker
1079810851
const loadedData = await openUniverseFile();
@@ -10804,6 +10857,34 @@ function NodeCanvas() {
1080410857
// Enable auto-save
1080510858
enableAutoSave(() => useGraphStore.getState());
1080610859

10860+
// Bridge file handle to universeBackend so saves work without reconnection
10861+
// Add small delay to ensure universe backend has processed the loaded data
10862+
setTimeout(async () => {
10863+
try {
10864+
const { default: universeBackend } = await import('./services/universeBackend.js');
10865+
const currentFileHandle = getCurrentFileHandle();
10866+
10867+
if (!currentFileHandle) {
10868+
console.warn('[NodeCanvas] No file handle available to bridge');
10869+
return;
10870+
}
10871+
10872+
// Wait briefly for universe backend to initialize
10873+
await new Promise(resolve => setTimeout(resolve, 100));
10874+
10875+
const activeUniverse = universeBackend.getActiveUniverse();
10876+
if (activeUniverse) {
10877+
await universeBackend.setFileHandle(activeUniverse.slug, currentFileHandle);
10878+
console.log('[NodeCanvas] ✅ File handle bridged to universeBackend for', activeUniverse.slug);
10879+
} else {
10880+
console.warn('[NodeCanvas] No active universe found to bridge file handle');
10881+
}
10882+
} catch (bridgeError) {
10883+
console.warn('[NodeCanvas] Failed to bridge file handle to universeBackend:', bridgeError);
10884+
// Non-fatal - fileStorage's own autosave might still work
10885+
}
10886+
}, 100);
10887+
1080710888
// Ensure universe connection is marked as established
1080810889
storeActions.setUniverseConnected(true);
1080910890
}

0 commit comments

Comments
 (0)