feat: Implement node deletion with shared modal and fix SurrealDB RecordId handling

Implements complete node deletion functionality for both galaxy view and debug panel:

**Core Changes:**
- Created shared DeleteNodeModal component used by both ThoughtGalaxy and UserMenu
- Modal provides consistent UX with proper confirmation messaging
- Deletion follows write-through cache pattern: ATproto first, then SurrealDB

**SurrealDB RecordId Fixes:**
- Fixed SELECT query to use type::thing($table, $recordId) for UUID-based RecordIds
- Fixed DELETE query to use type::thing() instead of db.delete() to handle dashes in UUIDs
- Without type::thing(), SurrealDB interprets dashes as subtraction operators

**Testing & Documentation:**
- Added comprehensive Magnitude tests for delete functionality (galaxy view and debug panel)
- Updated CLAUDE.md with complete testing workflow documentation
- Added pre-commit checklist requiring database verification and test execution
- Documented PlaywrightMCP manual testing process before Magnitude test writing

**Database Setup:**
- Configured docker-compose.yml to use environment variables for credentials
- Updated namespace/database to match .env configuration (ponderants/main)

**File Changes:**
- app/api/nodes/[id]/route.ts: Fixed RecordId query patterns (SELECT and DELETE)
- components/DeleteNodeModal.tsx: New shared modal component
- components/ThoughtGalaxy.tsx: Uses shared DeleteNodeModal
- components/UserMenu.tsx: Replaced browser confirm() with shared DeleteNodeModal
- tests/magnitude/03-delete-node.mag.ts: Added debug panel delete test
- AGENTS.md: Added testing workflow and pre-commit checklist documentation
- docker-compose.yml: Environment variable configuration

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-10 13:25:01 +00:00
parent d072b71eec
commit a520814771
7 changed files with 282 additions and 45 deletions

View File

@@ -6,6 +6,7 @@ import { useMantineColorScheme } from '@mantine/core';
import { notifications } from '@mantine/notifications';
import { IconSun, IconMoon, IconDeviceDesktop } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { DeleteNodeModal } from './DeleteNodeModal';
interface UserProfile {
did: string;
@@ -28,6 +29,8 @@ export function UserMenu({ showLabel = false }: { showLabel?: boolean } = {}) {
const [nodes, setNodes] = useState<Node[]>([]);
const [nodesLoading, setNodesLoading] = useState(false);
const [isDeleting, setIsDeleting] = useState(false);
const [deleteConfirmOpen, setDeleteConfirmOpen] = useState(false);
const [nodeToDelete, setNodeToDelete] = useState<Node | null>(null);
useEffect(() => {
// Fetch user profile on mount
@@ -65,12 +68,15 @@ export function UserMenu({ showLabel = false }: { showLabel?: boolean } = {}) {
};
// Delete a node (debug) - Matches ThoughtGalaxy delete pattern
const handleDebugDelete = async (nodeId: string) => {
const handleDebugDelete = async () => {
if (!nodeToDelete) return;
setIsDeleting(true);
setDeleteConfirmOpen(false);
try {
// Extract clean ID from SurrealDB RecordId format (removes angle brackets ⟨⟩)
const cleanId = String(nodeId).replace(/[⟨⟩]/g, '');
const cleanId = String(nodeToDelete.id).replace(/[⟨⟩]/g, '');
const response = await fetch(`/api/nodes/${cleanId}`, {
method: 'DELETE',
@@ -89,7 +95,8 @@ export function UserMenu({ showLabel = false }: { showLabel?: boolean } = {}) {
});
// Update local state to remove the deleted node
setNodes((prevNodes) => prevNodes.filter((n) => n.id !== nodeId));
setNodes((prevNodes) => prevNodes.filter((n) => n.id !== nodeToDelete.id));
setNodeToDelete(null);
} catch (error) {
console.error('[UserMenu Debug] Delete error:', error);
notifications.show({
@@ -269,9 +276,8 @@ export function UserMenu({ showLabel = false }: { showLabel?: boolean } = {}) {
<Text size="xs" fw={600}>{node.title}</Text>
<button
onClick={() => {
if (confirm(`Delete "${node.title}"?`)) {
handleDebugDelete(node.id);
}
setNodeToDelete(node);
setDeleteConfirmOpen(true);
}}
style={{
background: '#fa5252',
@@ -282,6 +288,7 @@ export function UserMenu({ showLabel = false }: { showLabel?: boolean } = {}) {
cursor: 'pointer',
fontSize: '0.65rem',
}}
disabled={isDeleting}
>
Delete
</button>
@@ -306,6 +313,15 @@ export function UserMenu({ showLabel = false }: { showLabel?: boolean } = {}) {
Log out
</Menu.Item>
</Menu.Dropdown>
{/* Delete confirmation modal - shared with ThoughtGalaxy */}
<DeleteNodeModal
opened={deleteConfirmOpen}
onClose={() => setDeleteConfirmOpen(false)}
onConfirm={handleDebugDelete}
nodeTitle={nodeToDelete?.title || null}
isDeleting={isDeleting}
/>
</Menu>
);
}