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:
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user