docs: Add comprehensive implementation plans for all todo items
Created detailed markdown plans for all items in todo.md: 1. 01-playwright-scaffolding.md - Base Playwright infrastructure 2. 02-magnitude-tests-comprehensive.md - Complete test coverage 3. 03-stream-ai-to-deepgram-tts.md - TTS latency optimization 4. 04-fix-galaxy-node-clicking.md - Galaxy navigation bugs 5. 05-dark-light-mode-theme.md - Dark/light mode with dynamic favicons 6. 06-fix-double-border-desktop.md - UI polish 7. 07-delete-backup-files.md - Code cleanup 8. 08-ai-transition-to-edit.md - Intelligent node creation flow 9. 09-umap-minimum-nodes-analysis.md - Technical analysis Each plan includes: - Detailed problem analysis - Proposed solutions with code examples - Manual Playwright MCP testing strategy - Magnitude test specifications - Implementation steps - Success criteria Ready to implement in sequence. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
383
plans/04-fix-galaxy-node-clicking.md
Normal file
383
plans/04-fix-galaxy-node-clicking.md
Normal file
@@ -0,0 +1,383 @@
|
||||
# Plan: Fix Galaxy Node Clicking and Navigation
|
||||
|
||||
**Priority:** HIGH - Critical user experience issue
|
||||
**Dependencies:** None
|
||||
**Affects:** Galaxy visualization navigation, user frustration
|
||||
|
||||
## Overview
|
||||
|
||||
There are two critical bugs with galaxy node interaction:
|
||||
1. When going directly to a node ID link (`/galaxy/node:xxx`), it redirects to `/chat`
|
||||
2. When clicking on a node in `/galaxy` (either general or on a specific node ID URL), the modal closes automatically
|
||||
|
||||
Both issues severely impact the galaxy user experience and need immediate fixing.
|
||||
|
||||
## Current Broken Behavior
|
||||
|
||||
### Issue 1: Direct Node URL Redirects to Chat
|
||||
```
|
||||
User clicks: /galaxy/node:abc123
|
||||
Expected: Shows node detail page with full content
|
||||
Actual: Redirects to /chat
|
||||
```
|
||||
|
||||
### Issue 2: Modal Auto-Closes on Node Click
|
||||
```
|
||||
User action: Click node sphere in galaxy visualization
|
||||
Expected: Modal stays open showing node details
|
||||
Actual: Modal opens briefly then closes immediately
|
||||
```
|
||||
|
||||
## Root Cause Analysis
|
||||
|
||||
Let me investigate the current implementation:
|
||||
|
||||
### 1. Check `/app/galaxy/[node-id]/page.tsx`
|
||||
|
||||
Likely issues:
|
||||
- Missing authentication check causing redirect
|
||||
- Incorrect route parameter parsing
|
||||
- Navigation logic in wrong place
|
||||
|
||||
### 2. Check Galaxy Modal Component
|
||||
|
||||
Likely issues:
|
||||
- Event handler conflict (click bubbling to parent)
|
||||
- State management issue (modal state reset)
|
||||
- React Three Fiber event handling bug
|
||||
|
||||
## Proposed Fixes
|
||||
|
||||
### Fix 1: Node Detail Page Route
|
||||
|
||||
#### Current (broken):
|
||||
```typescript
|
||||
// app/galaxy/[node-id]/page.tsx
|
||||
export default async function NodeDetailPage({ params }: { params: { 'node-id': string } }) {
|
||||
// Redirects to /chat if not authenticated?
|
||||
}
|
||||
```
|
||||
|
||||
#### Fixed:
|
||||
```typescript
|
||||
// app/galaxy/[node-id]/page.tsx
|
||||
import { cookies } from 'next/headers';
|
||||
import { redirect } from 'next/navigation';
|
||||
import { verifySurrealJwt } from '@/lib/auth/jwt';
|
||||
import { connectToDB } from '@/lib/db';
|
||||
|
||||
export default async function NodeDetailPage({
|
||||
params,
|
||||
}: {
|
||||
params: { 'node-id': string };
|
||||
}) {
|
||||
// 1. Verify authentication
|
||||
const cookieStore = await cookies();
|
||||
const surrealJwt = cookieStore.get('ponderants-auth')?.value;
|
||||
|
||||
if (!surrealJwt) {
|
||||
// Redirect to login, preserving the intended destination
|
||||
redirect(`/login?redirect=/galaxy/${params['node-id']}`);
|
||||
}
|
||||
|
||||
const userSession = verifySurrealJwt(surrealJwt);
|
||||
if (!userSession) {
|
||||
redirect(`/login?redirect=/galaxy/${params['node-id']}`);
|
||||
}
|
||||
|
||||
// 2. Fetch node data
|
||||
const db = await connectToDB();
|
||||
const nodeId = params['node-id'];
|
||||
|
||||
const nodes = await db.select<NodeData>(nodeId);
|
||||
const node = nodes[0];
|
||||
|
||||
if (!node) {
|
||||
// Node doesn't exist or user doesn't have access
|
||||
redirect('/galaxy?error=node-not-found');
|
||||
}
|
||||
|
||||
// 3. Verify user owns this node
|
||||
if (node.user_did !== userSession.did) {
|
||||
redirect('/galaxy?error=unauthorized');
|
||||
}
|
||||
|
||||
// 4. Render node detail page
|
||||
return (
|
||||
<Stack p="xl">
|
||||
<Title order={1}>{node.title}</Title>
|
||||
<Text>{node.body}</Text>
|
||||
{node.atp_uri && (
|
||||
<Anchor href={node.atp_uri} target="_blank">
|
||||
View on Bluesky
|
||||
</Anchor>
|
||||
)}
|
||||
<Button component={Link} href="/galaxy">
|
||||
Back to Galaxy
|
||||
</Button>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Fix 2: Galaxy Modal Component
|
||||
|
||||
#### Problem: Click event bubbling
|
||||
|
||||
When user clicks node in R3F, the click event might be:
|
||||
- Bubbling to parent canvas
|
||||
- Triggering canvas click handler that closes modal
|
||||
- Or: React state update race condition
|
||||
|
||||
#### Solution: Stop event propagation
|
||||
|
||||
```typescript
|
||||
// components/galaxy/NodeSphere.tsx
|
||||
import { useRef } from 'react';
|
||||
import { ThreeEvent } from '@react-three/fiber';
|
||||
|
||||
function NodeSphere({ node, onClick }: { node: NodeData; onClick: (node: NodeData) => void }) {
|
||||
const meshRef = useRef();
|
||||
|
||||
const handleClick = (event: ThreeEvent<MouseEvent>) => {
|
||||
// CRITICAL: Stop propagation to prevent canvas click handler
|
||||
event.stopPropagation();
|
||||
|
||||
// Call parent onClick
|
||||
onClick(node);
|
||||
};
|
||||
|
||||
return (
|
||||
<mesh
|
||||
ref={meshRef}
|
||||
position={node.coords_3d}
|
||||
onClick={handleClick}
|
||||
>
|
||||
<sphereGeometry args={[0.1, 16, 16]} />
|
||||
<meshStandardMaterial color="#fff" />
|
||||
</mesh>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
#### Problem: Modal state management
|
||||
|
||||
Modal might be controlled by both:
|
||||
- Galaxy component state
|
||||
- URL query params
|
||||
- Leading to race condition
|
||||
|
||||
#### Solution: Single source of truth
|
||||
|
||||
```typescript
|
||||
// app/galaxy/page.tsx
|
||||
'use client';
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useSearchParams, useRouter } from 'next/navigation';
|
||||
|
||||
export default function GalaxyPage() {
|
||||
const searchParams = useSearchParams();
|
||||
const router = useRouter();
|
||||
|
||||
// Modal state controlled ONLY by URL query param
|
||||
const selectedNodeId = searchParams.get('node');
|
||||
|
||||
const handleNodeClick = (nodeId: string) => {
|
||||
// Update URL to open modal
|
||||
router.push(`/galaxy?node=${encodeURIComponent(nodeId)}`, { scroll: false });
|
||||
};
|
||||
|
||||
const handleModalClose = () => {
|
||||
// Remove query param to close modal
|
||||
router.push('/galaxy', { scroll: false });
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<ThoughtGalaxy
|
||||
nodes={nodes}
|
||||
links={links}
|
||||
onNodeClick={handleNodeClick}
|
||||
/>
|
||||
|
||||
<Modal
|
||||
opened={!!selectedNodeId}
|
||||
onClose={handleModalClose}
|
||||
size="lg"
|
||||
>
|
||||
{selectedNodeId && <NodeDetailModal nodeId={selectedNodeId} />}
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Fix 3: Canvas Click Handler
|
||||
|
||||
If canvas has a click handler that closes modal, remove it or add condition:
|
||||
|
||||
```typescript
|
||||
// components/galaxy/ThoughtGalaxy.tsx
|
||||
<Canvas
|
||||
onClick={(e) => {
|
||||
// Only close modal if clicking on background (not a node)
|
||||
if (e.target === e.currentTarget) {
|
||||
onBackgroundClick?.();
|
||||
}
|
||||
}}
|
||||
>
|
||||
{/* ... */}
|
||||
</Canvas>
|
||||
```
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
### Manual Playwright MCP Testing
|
||||
|
||||
#### Test 1: Direct Node URL
|
||||
```typescript
|
||||
test('Direct node URL loads correctly', async ({ page }) => {
|
||||
// Create a node first
|
||||
const nodeId = await createTestNode(page);
|
||||
|
||||
// Navigate directly to node detail page
|
||||
await page.goto(`/galaxy/${nodeId}`);
|
||||
|
||||
// Verify we're on the correct page
|
||||
await expect(page).toHaveURL(/\/galaxy\/node:/);
|
||||
await expect(page.getByRole('heading', { level: 1 })).toBeVisible();
|
||||
await expect(page.getByText(/View on Bluesky/)).toBeVisible();
|
||||
});
|
||||
```
|
||||
|
||||
#### Test 2: Modal Stays Open
|
||||
```typescript
|
||||
test('Galaxy modal stays open when clicking node', async ({ page }) => {
|
||||
await page.goto('/galaxy');
|
||||
|
||||
// Click on a node sphere
|
||||
await page.evaluate(() => {
|
||||
const event = new CustomEvent('node-click', { detail: { nodeId: 'node:123' } });
|
||||
window.dispatchEvent(event);
|
||||
});
|
||||
|
||||
// Modal should open
|
||||
await expect(page.getByRole('dialog')).toBeVisible();
|
||||
|
||||
// Wait 2 seconds
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
// Modal should still be open
|
||||
await expect(page.getByRole('dialog')).toBeVisible();
|
||||
});
|
||||
```
|
||||
|
||||
#### Test 3: Modal Navigation
|
||||
```typescript
|
||||
test('Can navigate from galaxy to node detail page', async ({ page }) => {
|
||||
await page.goto('/galaxy');
|
||||
|
||||
// Click node to open modal
|
||||
await clickNode(page, 'node:123');
|
||||
|
||||
// Modal opens
|
||||
await expect(page.getByRole('dialog')).toBeVisible();
|
||||
|
||||
// Click "View Full Detail"
|
||||
await page.click('button:has-text("View Full Detail")');
|
||||
|
||||
// Navigate to detail page
|
||||
await expect(page).toHaveURL(/\/galaxy\/node:123/);
|
||||
await expect(page.getByRole('dialog')).not.toBeVisible();
|
||||
});
|
||||
```
|
||||
|
||||
### Magnitude Tests
|
||||
|
||||
```typescript
|
||||
import { test } from 'magnitude-test';
|
||||
|
||||
test('User can navigate to node via direct URL', async (agent) => {
|
||||
// Create a node first
|
||||
await agent.open('http://localhost:3000/chat');
|
||||
await agent.act('Create a test node');
|
||||
const nodeUrl = await agent.getData('node-url');
|
||||
|
||||
// Navigate directly to node URL
|
||||
await agent.open(nodeUrl);
|
||||
await agent.check('Node detail page loads');
|
||||
await agent.check('Node title is visible');
|
||||
await agent.check('Node body is visible');
|
||||
await agent.check('Bluesky link is visible');
|
||||
});
|
||||
|
||||
test('Galaxy modal stays open when clicking nodes', async (agent) => {
|
||||
await agent.open('http://localhost:3000/galaxy');
|
||||
await agent.act('Click on a node sphere');
|
||||
|
||||
await agent.check('Modal opens');
|
||||
await agent.wait(2000);
|
||||
await agent.check('Modal is still open');
|
||||
await agent.check('Node details are visible');
|
||||
});
|
||||
|
||||
test('Can navigate from galaxy modal to node detail page', async (agent) => {
|
||||
await agent.open('http://localhost:3000/galaxy');
|
||||
await agent.act('Click on a node');
|
||||
await agent.check('Modal opens');
|
||||
|
||||
await agent.act('Click "View Full Detail" button');
|
||||
await agent.check('Navigated to node detail page');
|
||||
await agent.check('URL contains node ID');
|
||||
await agent.check('Full node content is visible');
|
||||
});
|
||||
```
|
||||
|
||||
## Implementation Steps
|
||||
|
||||
1. **Fix node detail page route**
|
||||
- Add proper authentication check
|
||||
- Add redirect with return URL
|
||||
- Add node ownership verification
|
||||
- Add error handling for missing nodes
|
||||
|
||||
2. **Fix modal event handling**
|
||||
- Add `event.stopPropagation()` to node click handler
|
||||
- Remove or condition any canvas-level click handlers
|
||||
- Ensure modal state is controlled by URL query param
|
||||
|
||||
3. **Test with Playwright MCP**
|
||||
- Test direct URL navigation
|
||||
- Test modal stays open
|
||||
- Test modal to detail page navigation
|
||||
- Fix any issues found
|
||||
|
||||
4. **Add Magnitude tests**
|
||||
- Write tests covering all 3 scenarios
|
||||
- Ensure tests pass
|
||||
|
||||
5. **Deploy and verify in production**
|
||||
|
||||
## Success Criteria
|
||||
|
||||
- ✅ Direct node URLs (`/galaxy/node:xxx`) load correctly
|
||||
- ✅ No redirect to `/chat` when accessing node URLs
|
||||
- ✅ Galaxy modal stays open after clicking node
|
||||
- ✅ Can navigate from modal to detail page
|
||||
- ✅ Can navigate from detail page back to galaxy
|
||||
- ✅ All Playwright MCP tests pass
|
||||
- ✅ All Magnitude tests pass
|
||||
- ✅ No console errors during navigation
|
||||
|
||||
## Files to Update
|
||||
|
||||
1. `app/galaxy/[node-id]/page.tsx` - Fix authentication and routing
|
||||
2. `app/galaxy/page.tsx` - Fix modal state management
|
||||
3. `components/galaxy/ThoughtGalaxy.tsx` - Fix canvas click handling
|
||||
4. `components/galaxy/NodeSphere.tsx` - Fix node click event propagation
|
||||
|
||||
## Files to Create
|
||||
|
||||
1. `tests/playwright/galaxy-navigation.spec.ts` - Manual tests
|
||||
2. `tests/magnitude/galaxy-navigation.mag.ts` - Magnitude tests
|
||||
Reference in New Issue
Block a user