Skip to content

Commit c1fa187

Browse files
committed
feat: testing onboarding mode, other stuff
1 parent f11048e commit c1fa187

9 files changed

Lines changed: 265 additions & 53 deletions

File tree

TESTING_ONBOARDING.md

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
# Testing Onboarding Flow
2+
3+
## Test Mode
4+
5+
Redstring supports a special "test mode" that uses completely separate storage, allowing you to test the first-time user experience without affecting your main session.
6+
7+
### How to Use Test Mode
8+
9+
#### Web/Localhost
10+
Add `?test=true` to your URL:
11+
```
12+
http://localhost:5173/?test=true
13+
```
14+
15+
This will:
16+
- Use separate localStorage keys (prefixed with `test_`)
17+
- Use separate IndexedDB database (`test_RedstringFolderStorage`)
18+
- Not interfere with your main session
19+
20+
#### Electron
21+
Launch Electron with a test flag (you'll need to add command-line argument support):
22+
```bash
23+
npm run electron -- --test
24+
```
25+
26+
Or manually add `?test=true` to the initial URL in your Electron main process.
27+
28+
### What Test Mode Does
29+
30+
When `?test=true` is in the URL, Redstring will:
31+
32+
1. **Use separate storage keys:**
33+
- `test_redstring-alpha-welcome-seen` instead of `redstring-alpha-welcome-seen`
34+
- `test_redstring_workspace_folder_path` instead of `redstring_workspace_folder_path`
35+
- `test_RedstringFolderStorage` IndexedDB instead of `RedstringFolderStorage`
36+
37+
2. **Show first-time onboarding flow:**
38+
- Welcome modal appears
39+
- Storage setup modal appears
40+
- Can test folder selection and universe creation
41+
42+
3. **Keep your real data safe:**
43+
- Your actual workspace folder is untouched
44+
- Your actual universe files are not affected
45+
- Your main session preferences are preserved
46+
47+
### Testing Workflow
48+
49+
1. **Open test mode:**
50+
```
51+
http://localhost:5173/?test=true
52+
```
53+
54+
2. **Go through onboarding:**
55+
- Click "Get Started"
56+
- Choose a test folder (create a separate "RedstringTest" folder)
57+
- Test the universe creation flow
58+
59+
3. **Test returning user flow:**
60+
- Reload with `?test=true`
61+
- Should load directly into your test universe
62+
63+
4. **Reset test mode:**
64+
- Use Debug menu → "Reset Onboarding Flow"
65+
- Or manually clear: `localStorage.removeItem('test_redstring-alpha-welcome-seen')`
66+
67+
5. **Return to normal mode:**
68+
- Remove `?test=true` from URL
69+
- Your regular session loads normally
70+
71+
### Manual Storage Clearing
72+
73+
If you need to manually reset test mode:
74+
75+
```javascript
76+
// Clear test mode onboarding flag
77+
localStorage.removeItem('test_redstring-alpha-welcome-seen');
78+
79+
// Clear test mode folder
80+
localStorage.removeItem('test_redstring_workspace_folder_path');
81+
82+
// Clear test mode IndexedDB
83+
indexedDB.deleteDatabase('test_RedstringFolderStorage');
84+
85+
// Reload
86+
window.location.reload();
87+
```
88+
89+
### Debug Menu Option
90+
91+
The Debug menu includes "Reset Onboarding Flow" which will:
92+
- Clear the onboarding completion flag
93+
- Clear stored folder handles
94+
- Reload the page
95+
96+
This works in both normal and test mode depending on which mode you're in.
97+
98+
## Recommended Test Folder Structure
99+
100+
Create a separate test folder to avoid confusion:
101+
102+
```
103+
~/Documents/
104+
├── Redstring/ # Your real workspace
105+
│ ├── default.redstring
106+
│ └── project.redstring
107+
└── RedstringTest/ # Test mode workspace
108+
└── default.redstring
109+
```
110+
111+
This way you can safely test onboarding without risking your actual data.

repro_parsing.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
2+
// Logic extracted from LLMClient.js to reproduce JSON.parse error on truncation
3+
4+
const partialJson = '{"name": "Iron Man", "description": "Tony Sta'; // Truncated
5+
6+
try {
7+
console.log('Attempting to parse truncated JSON...');
8+
const result = JSON.parse(partialJson);
9+
console.log('Success:', result);
10+
} catch (error) {
11+
console.log('Caught expected error:', error.message);
12+
}
13+
14+
// Logic with the proposed fix
15+
console.log('\n--- Testing Fix ---');
16+
try {
17+
const result = JSON.parse(partialJson);
18+
console.log('Success:', result);
19+
} catch (e) {
20+
console.log('Safely caught error:', e.message);
21+
const safeError = { error: 'Response was truncated by the spell!' };
22+
console.log('Returning safe error:', safeError);
23+
}

src/NodeCanvas.jsx

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1839,6 +1839,13 @@ function NodeCanvas() {
18391839
// Help modal state
18401840
const [showHelpModal, setShowHelpModal] = useState(false);
18411841

1842+
// Helper to get storage key with test mode support
1843+
const getStorageKey = (key) => {
1844+
const params = new URLSearchParams(window.location.search);
1845+
const isTestMode = params.get('test') === 'true';
1846+
return isTestMode ? `test_${key}` : key;
1847+
};
1848+
18421849
// Check for stored folder on app startup and attempt to restore
18431850
useEffect(() => {
18441851
let isMounted = true;
@@ -1911,7 +1918,7 @@ function NodeCanvas() {
19111918
let hasCompletedOnboarding = false;
19121919
try {
19131920
if (typeof window !== 'undefined') {
1914-
hasCompletedOnboarding = localStorage.getItem('redstring-alpha-welcome-seen') === 'true';
1921+
hasCompletedOnboarding = localStorage.getItem(getStorageKey('redstring-alpha-welcome-seen')) === 'true';
19151922
}
19161923
} catch { }
19171924

@@ -13257,7 +13264,7 @@ function NodeCanvas() {
1325713264
// Mark onboarding as complete when user closes the modal
1325813265
try {
1325913266
if (typeof window !== 'undefined') {
13260-
localStorage.setItem('redstring-alpha-welcome-seen', 'true');
13267+
localStorage.setItem(getStorageKey('redstring-alpha-welcome-seen'), 'true');
1326113268
}
1326213269
} catch { }
1326313270
setShowOnboardingModal(false);
@@ -13271,7 +13278,7 @@ function NodeCanvas() {
1327113278
// Mark onboarding as complete
1327213279
try {
1327313280
if (typeof window !== 'undefined') {
13274-
localStorage.setItem('redstring-alpha-welcome-seen', 'true');
13281+
localStorage.setItem(getStorageKey('redstring-alpha-welcome-seen'), 'true');
1327513282
}
1327613283
} catch { }
1327713284

@@ -13310,7 +13317,7 @@ function NodeCanvas() {
1331013317

1331113318
// Mark onboarding as complete
1331213319
if (typeof window !== 'undefined') {
13313-
localStorage.setItem('redstring-alpha-welcome-seen', 'true');
13320+
localStorage.setItem(getStorageKey('redstring-alpha-welcome-seen'), 'true');
1331413321
}
1331513322

1331613323
// Close storage setup modal
@@ -13376,7 +13383,7 @@ function NodeCanvas() {
1337613383

1337713384
// Mark onboarding as complete
1337813385
if (typeof window !== 'undefined') {
13379-
localStorage.setItem('redstring-alpha-welcome-seen', 'true');
13386+
localStorage.setItem(getStorageKey('redstring-alpha-welcome-seen'), 'true');
1338013387
}
1338113388

1338213389
// Close storage setup modal

src/ai/BridgeClient.jsx

Lines changed: 40 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,9 @@ const BridgeClient = () => {
177177
});
178178
// Track last telemetry timestamp sent to UI to avoid spam
179179
const lastTelemetryTsRef = useRef(0);
180+
// Track last user activity to throttle polling when idle
181+
// Initialized to now so we start in fast mode
182+
const lastActivityRef = useRef(Date.now());
180183

181184
useEffect(() => {
182185
mountedRef.current = true;
@@ -383,7 +386,11 @@ const BridgeClient = () => {
383386
// Store the actual functions in a global variable that the bridge server can access
384387
if (typeof window !== 'undefined') {
385388
window.redstringStoreActions = {
389+
// Helper to mark activity
390+
_markActive: () => { lastActivityRef.current = Date.now(); },
391+
386392
ensureGraph: async (graphId, initialData) => {
393+
lastActivityRef.current = Date.now();
387394
console.log('MCPBridge: Calling ensureGraph', graphId, initialData);
388395
const st = useGraphStore.getState();
389396
if (!st.graphs.has(graphId)) {
@@ -588,6 +595,7 @@ const BridgeClient = () => {
588595
return { success: true };
589596
},
590597
chat: async (message, context) => {
598+
lastActivityRef.current = Date.now();
591599
console.log('MCPBridge: Forwarding chat message to AI model', { message, context });
592600
// The actual chat handling happens in the MCP server
593601
return { success: true, message, context };
@@ -597,22 +605,22 @@ const BridgeClient = () => {
597605
// options: { mode, nodeIds, graphId, coordinates, zoom }
598606
console.log('MCPBridge: Navigating to', options);
599607
const { navigateToNodes, navigateToFitContent, navigateToCoordinates } = await import('../services/canvasNavigationService.js');
600-
608+
601609
if (options.nodeIds && options.nodeIds.length > 0) {
602-
navigateToNodes(options.nodeIds, {
610+
navigateToNodes(options.nodeIds, {
603611
graphId: options.graphId,
604-
delay: options.delay || 150
612+
delay: options.delay || 150
605613
});
606614
} else if (options.coordinates) {
607615
navigateToCoordinates(
608-
options.coordinates.x,
609-
options.coordinates.y,
616+
options.coordinates.x,
617+
options.coordinates.y,
610618
{ zoom: options.zoom, delay: options.delay || 150 }
611619
);
612620
} else {
613-
navigateToFitContent({
621+
navigateToFitContent({
614622
graphId: options.graphId,
615-
delay: options.delay || 150
623+
delay: options.delay || 150
616624
});
617625
}
618626
return { success: true, navigated: true };
@@ -1226,6 +1234,8 @@ const BridgeClient = () => {
12261234
if (actionsResponse.ok) {
12271235
const actionsData = await actionsResponse.json();
12281236
if (actionsData.pendingActions && actionsData.pendingActions.length > 0) {
1237+
// Activity detected! Reset idle timer
1238+
lastActivityRef.current = Date.now();
12291239
console.log('✅ MCP Bridge: Found pending actions:', actionsData.pendingActions.length);
12301240
// Execute actions in a stable dependency-friendly order
12311241
const priority = (act) => {
@@ -1533,9 +1543,29 @@ const BridgeClient = () => {
15331543
};
15341544

15351545
// Check for bridge updates every 1s; guard with mountedRef to auto-resume after remounts
1536-
bridgeIntervalRef.current = setInterval(() => {
1537-
if (mountedRef.current) checkForBridgeUpdates();
1538-
}, 1000);
1546+
// Adaptive polling loop
1547+
const pollingLoop = async () => {
1548+
if (!mountedRef.current) return;
1549+
1550+
const now = Date.now();
1551+
const timeSinceActivity = now - lastActivityRef.current;
1552+
const isIdle = timeSinceActivity > 30000; // 30 seconds idle
1553+
1554+
// Fast poll (1s) if active, slow poll (5s) if idle
1555+
const interval = isIdle ? 5000 : 1000;
1556+
1557+
await checkForBridgeUpdates();
1558+
1559+
if (mountedRef.current && connectionStateRef.current.isConnected) {
1560+
bridgeIntervalRef.current = setTimeout(pollingLoop, interval);
1561+
} else if (mountedRef.current) {
1562+
// If disconnected, check less frequently but keep checking to resume
1563+
bridgeIntervalRef.current = setTimeout(pollingLoop, 5000);
1564+
}
1565+
};
1566+
1567+
// Start the loop
1568+
pollingLoop();
15391569

15401570
// Cleanup function
15411571
return () => {

src/components/AlphaOnboardingModal.jsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,10 @@ const AlphaOnboardingModal = ({
3939

4040
const handleClose = () => {
4141
if (typeof window !== 'undefined') {
42-
localStorage.setItem('redstring-alpha-welcome-seen', 'true');
42+
const params = new URLSearchParams(window.location.search);
43+
const isTestMode = params.get('test') === 'true';
44+
const key = isTestMode ? 'test_redstring-alpha-welcome-seen' : 'redstring-alpha-welcome-seen';
45+
localStorage.setItem(key, 'true');
4346
}
4447
onClose();
4548
};

0 commit comments

Comments
 (0)