UNPKG

@vibeship/devtools

Version:

Comprehensive markdown-based project management system with AI capabilities for Next.js applications

1,270 lines (1,260 loc) 54.1 kB
"use client"; // src/zero/VibeshipDevTools.tsx import { useState as useState6, useEffect as useEffect4 } from "react"; // src/zero/hooks.ts import { useEffect, useState, useCallback, useRef, useMemo } from "react"; // src/zero/scanner.ts var TASK_PATTERNS = { // JavaScript/TypeScript comments jsComments: [ /\/\/\s*(TODO|FIXME|HACK|NOTE|BUG|OPTIMIZE|REFACTOR):\s*(.+)/gi, /\/\*\s*(TODO|FIXME|HACK|NOTE|BUG|OPTIMIZE|REFACTOR):\s*(.+?)\s*\*\//gi ], // HTML comments htmlComments: [ /<!--\s*(TODO|FIXME|HACK|NOTE|BUG|OPTIMIZE|REFACTOR):\s*(.+?)\s*-->/gi ], // Markdown markdown: [ /[-*]\s*(TODO|FIXME|HACK|NOTE|BUG|OPTIMIZE|REFACTOR):\s*(.+)/gi, />\s*(TODO|FIXME|HACK|NOTE|BUG|OPTIMIZE|REFACTOR):\s*(.+)/gi ] }; function scanDomContent(options = {}) { const tasks = []; const { includeComments = true, includeHtml = true, maxTasks = 100 } = options; if (!includeComments && !includeHtml) { return tasks; } if (includeComments) { const codeElements = document.querySelectorAll("pre, code"); codeElements.forEach((element) => { const content = element.textContent || ""; const elementTasks = scanTextContent(content, "dom", "DOM Element"); tasks.push(...elementTasks); }); } if (includeHtml) { const htmlContent = document.documentElement.innerHTML; TASK_PATTERNS.htmlComments.forEach((pattern) => { let match; while ((match = pattern.exec(htmlContent)) && tasks.length < maxTasks) { tasks.push({ id: `client-html-${tasks.length}`, type: match[1].toLowerCase(), content: match[2].trim(), file: "HTML Content", line: 0, priority: inferPriority(match[1], match[2]), source: "dom" }); } }); } return tasks.slice(0, maxTasks); } function scanNextJsProps(options = {}) { const tasks = []; const { maxTasks = 100 } = options; if (typeof window !== "undefined" && window.__NEXT_DATA__) { const nextData = window.__NEXT_DATA__; const propsString = JSON.stringify(nextData.props); const propTasks = scanTextContent(propsString, "props", "Next.js Props"); tasks.push(...propTasks.slice(0, maxTasks)); } return tasks; } function scanTextContent(content, source, fileName = "Unknown") { const tasks = []; const allPatterns = [ ...TASK_PATTERNS.jsComments, ...TASK_PATTERNS.markdown ]; allPatterns.forEach((pattern) => { let match; const regex = new RegExp(pattern); while (match = regex.exec(content)) { tasks.push({ id: `client-${source}-${tasks.length}`, type: match[1].toLowerCase(), content: match[2].trim(), file: fileName, line: 0, // Can't determine line number in client-side scanning priority: inferPriority(match[1], match[2]), source }); } }); return tasks; } function inferPriority(type, content) { const upperType = type.toUpperCase(); const lowerContent = content.toLowerCase(); if (upperType === "FIXME" || upperType === "BUG" || lowerContent.includes("urgent") || lowerContent.includes("critical") || lowerContent.includes("asap") || lowerContent.includes("important")) { return "high"; } if (upperType === "NOTE" || upperType === "OPTIMIZE" || lowerContent.includes("maybe") || lowerContent.includes("consider") || lowerContent.includes("later") || lowerContent.includes("someday")) { return "low"; } return "medium"; } function scanForTasks(options = {}) { const allTasks = []; const { maxTasks = 100 } = options; const domTasks = scanDomContent(options); allTasks.push(...domTasks); const propTasks = scanNextJsProps(options); allTasks.push(...propTasks); const uniqueTasks = allTasks.reduce((acc, task) => { const key = `${task.type}-${task.content}`; if (!acc.has(key)) { acc.set(key, task); } return acc; }, /* @__PURE__ */ new Map()); return Array.from(uniqueTasks.values()).slice(0, maxTasks); } async function scanSourceMaps(options = {}) { const tasks = []; const { maxTasks = 100 } = options; if (false) { return tasks; } try { const scripts = Array.from(document.scripts); for (const script of scripts) { if (script.src && script.src.endsWith(".js")) { try { const mapResponse = await fetch(script.src + ".map"); if (mapResponse.ok) { const mapData = await mapResponse.json(); if (mapData.sourcesContent) { mapData.sources?.forEach((source, index) => { const content = mapData.sourcesContent[index]; if (content) { const sourceTasks = scanTextContent( content, "sourcemap", source.split("/").pop() || "Unknown" ); tasks.push(...sourceTasks); } }); } } } catch { } } if (tasks.length >= maxTasks) break; } } catch (error) { console.debug("Error scanning source maps:", error); } return tasks.slice(0, maxTasks); } async function scanPublicFiles(options = {}) { const tasks = []; const { maxTasks = 100, publicFiles = [] } = options; const defaultFiles = [ "/README.md", "/CHANGELOG.md", "/TODO.md", "/docs/index.md", "/.tickets/index.md" ]; const filesToScan = [.../* @__PURE__ */ new Set([...defaultFiles, ...publicFiles])]; for (const file of filesToScan) { try { const response = await fetch(file); if (response.ok) { const content = await response.text(); const fileTasks = scanTextContent( content, "public", file.split("/").pop() || "Unknown" ); tasks.push(...fileTasks); } } catch { } if (tasks.length >= maxTasks) break; } return tasks.slice(0, maxTasks); } async function scanForTasksAsync(options = {}) { const allTasks = []; const { maxTasks = 100, includeSourceMaps = false, includePublicFiles = false // Changed to false to avoid 404s in fresh projects } = options; const syncTasks = scanForTasks(options); allTasks.push(...syncTasks); if (includeSourceMaps) { const sourceMapTasks = await scanSourceMaps(options); allTasks.push(...sourceMapTasks); } if (includePublicFiles) { const publicTasks = await scanPublicFiles(options); allTasks.push(...publicTasks); } const uniqueTasks = allTasks.reduce((acc, task) => { const key = `${task.type}-${task.content}`; if (!acc.has(key)) { acc.set(key, task); } return acc; }, /* @__PURE__ */ new Map()); return Array.from(uniqueTasks.values()).slice(0, maxTasks); } function watchForChanges(callback, options = {}) { let timeoutId; const debouncedScan = () => { clearTimeout(timeoutId); timeoutId = setTimeout(async () => { const tasks = options.includeSourceMaps || options.includePublicFiles ? await scanForTasksAsync(options) : scanForTasks(options); callback(tasks); }, 500); }; debouncedScan(); const observer = new MutationObserver(debouncedScan); observer.observe(document.body, { childList: true, subtree: true, characterData: true }); return () => { observer.disconnect(); clearTimeout(timeoutId); }; } // src/zero/hooks.ts function useClientSideScan(options = {}) { const { enabled = true, watchChanges: watchChangesOption = true, scanInterval, ...scanOptions } = options; const [tasks, setTasks] = useState([]); const [isScanning, setIsScanning] = useState(false); const [lastScanTime, setLastScanTime] = useState(null); const cleanupRef = useRef(null); const stableScanOptions = useMemo(() => scanOptions, [ scanOptions.includeComments, scanOptions.includeHtml, scanOptions.includeProps, scanOptions.includeSourceMaps, scanOptions.includePublicFiles, scanOptions.includeAsync, scanOptions.maxTasks, JSON.stringify(scanOptions.publicFiles) // Serialize array for comparison ]); const rescan = useCallback(async () => { if (!enabled) return; setIsScanning(true); try { const newTasks = stableScanOptions.includeAsync || stableScanOptions.includeSourceMaps || stableScanOptions.includePublicFiles ? await scanForTasksAsync(stableScanOptions) : scanForTasks(stableScanOptions); setTasks(newTasks); setLastScanTime(/* @__PURE__ */ new Date()); } finally { setIsScanning(false); } }, [enabled, stableScanOptions]); useEffect(() => { if (!enabled) { setTasks([]); return; } const performInitialScan = async () => { setIsScanning(true); try { const newTasks = stableScanOptions.includeAsync || stableScanOptions.includeSourceMaps || stableScanOptions.includePublicFiles ? await scanForTasksAsync(stableScanOptions) : scanForTasks(stableScanOptions); setTasks(newTasks); setLastScanTime(/* @__PURE__ */ new Date()); } finally { setIsScanning(false); } }; performInitialScan(); if (watchChangesOption) { cleanupRef.current = watchForChanges((newTasks) => { setTasks(newTasks); setLastScanTime(/* @__PURE__ */ new Date()); }, stableScanOptions); } let intervalId; if (scanInterval && scanInterval > 0) { intervalId = setInterval(() => { performInitialScan(); }, scanInterval); } return () => { if (cleanupRef.current) { cleanupRef.current(); cleanupRef.current = null; } if (intervalId) { clearInterval(intervalId); } }; }, [enabled, watchChangesOption, scanInterval, stableScanOptions]); return { tasks, isScanning, lastScanTime, rescan, taskCount: tasks.length }; } function useFeatureDetection() { const [features, setFeatures] = useState({ hasApiRoute: false, hasAiEnabled: false, hasFileAccess: false, isDevMode: false }); useEffect(() => { const detectFeatures = async () => { const detected = { hasApiRoute: false, // Disable API route check in zero-config mode to avoid 404s hasAiEnabled: checkAiEnabled(), hasFileAccess: false, // Always false in client-side mode isDevMode: checkDevMode() }; setFeatures(detected); }; detectFeatures(); }, []); return features; } function checkAiEnabled() { if (typeof window !== "undefined") { const win = window; return !!(win.OPENAI_API_KEY || win.ANTHROPIC_API_KEY || win.process?.env?.OPENAI_API_KEY || win.process?.env?.ANTHROPIC_API_KEY); } return false; } function checkDevMode() { if (typeof window !== "undefined") { const win = window; return win.process?.env?.NODE_ENV === "development" || win.location?.hostname === "localhost" || win.location?.hostname === "127.0.0.1"; } return false; } function useProgressiveLevel() { const features = useFeatureDetection(); const [currentLevel, setCurrentLevel] = useState(0); useEffect(() => { if (features.hasApiRoute && features.hasFileAccess) { setCurrentLevel(3); } else if (features.hasApiRoute) { setCurrentLevel(2); } else { setCurrentLevel(0); } }, [features]); const levels = [ { level: 0, name: "Zero Config", description: "Client-side scanning only", features: ["View tasks", "Read-only mode"] }, { level: 1, name: "Basic Config", description: "API route enabled", features: ["Task scanning", "Basic editing", "File access"] }, { level: 2, name: "Custom Config", description: "Configuration file added", features: ["Custom paths", "AI features", "Advanced patterns"] }, { level: 3, name: "Advanced", description: "Full control mode", features: ["Custom implementations", "Enterprise features"] } ]; return { currentLevel, levels, features, canUpgrade: currentLevel < 3 }; } // src/zero/components/TaskPanel.tsx import { useState as useState3, useMemo as useMemo2 } from "react"; // src/zero/components/VirtualList.tsx import { useRef as useRef2, useState as useState2, useEffect as useEffect2, useCallback as useCallback2 } from "react"; import { jsx } from "react/jsx-runtime"; function VirtualList({ items, height, itemHeight, overscan = 5, renderItem, className = "", onScroll, emptyMessage = "No items to display" }) { const containerRef = useRef2(null); const [scrollTop, setScrollTop] = useState2(0); const [isScrolling, setIsScrolling] = useState2(false); const scrollTimeoutRef = useRef2(void 0); const getItemHeight = useCallback2( (index) => { return typeof itemHeight === "function" ? itemHeight(index) : itemHeight; }, [itemHeight] ); const totalHeight = items.reduce((acc, _, index) => { return acc + getItemHeight(index); }, 0); const getVisibleRange = useCallback2(() => { let accumulatedHeight = 0; let startIndex2 = 0; let endIndex2 = items.length - 1; for (let i = 0; i < items.length; i++) { const itemHeight2 = getItemHeight(i); if (accumulatedHeight + itemHeight2 > scrollTop) { startIndex2 = Math.max(0, i - overscan); break; } accumulatedHeight += itemHeight2; } accumulatedHeight = 0; for (let i = startIndex2; i < items.length; i++) { if (accumulatedHeight > scrollTop + height) { endIndex2 = Math.min(items.length - 1, i + overscan); break; } accumulatedHeight += getItemHeight(i); } return { startIndex: startIndex2, endIndex: endIndex2 }; }, [items.length, scrollTop, height, overscan, getItemHeight]); const getItemOffset = useCallback2( (index) => { let offset = 0; for (let i = 0; i < index; i++) { offset += getItemHeight(i); } return offset; }, [getItemHeight] ); const handleScroll = useCallback2( (e) => { const newScrollTop = e.currentTarget.scrollTop; setScrollTop(newScrollTop); setIsScrolling(true); if (scrollTimeoutRef.current) { clearTimeout(scrollTimeoutRef.current); } scrollTimeoutRef.current = setTimeout(() => { setIsScrolling(false); }, 150); onScroll?.(newScrollTop); }, [onScroll] ); useEffect2(() => { return () => { if (scrollTimeoutRef.current) { clearTimeout(scrollTimeoutRef.current); } }; }, []); if (items.length === 0) { return /* @__PURE__ */ jsx( "div", { className: `flex items-center justify-center text-gray-500 dark:text-gray-400 ${className}`, style: { height }, children: emptyMessage } ); } const { startIndex, endIndex } = getVisibleRange(); const visibleItems = items.slice(startIndex, endIndex + 1); return /* @__PURE__ */ jsx( "div", { ref: containerRef, className: `overflow-auto ${className}`, style: { height }, onScroll: handleScroll, children: /* @__PURE__ */ jsx("div", { style: { height: totalHeight, position: "relative" }, children: visibleItems.map((item, index) => { const actualIndex = startIndex + index; const offset = getItemOffset(actualIndex); const itemHeight2 = getItemHeight(actualIndex); return /* @__PURE__ */ jsx( "div", { style: { position: "absolute", top: offset, left: 0, right: 0, height: itemHeight2, // Reduce opacity while scrolling for better performance opacity: isScrolling ? 0.8 : 1, transition: "opacity 0.2s" }, children: renderItem(item, actualIndex) }, actualIndex ); }) }) } ); } function useVirtualListDynamicHeights(items, estimatedItemHeight = 60) { const heightsRef = useRef2(/* @__PURE__ */ new Map()); const [, forceUpdate] = useState2({}); const measureItem = useCallback2((index, element) => { if (!element) return; const height = element.getBoundingClientRect().height; const currentHeight = heightsRef.current.get(index); if (currentHeight !== height) { heightsRef.current.set(index, height); forceUpdate({}); } }, []); const getItemHeight = useCallback2( (index) => { return heightsRef.current.get(index) || estimatedItemHeight; }, [estimatedItemHeight] ); const resetHeights = useCallback2(() => { heightsRef.current.clear(); forceUpdate({}); }, []); return { getItemHeight, measureItem, resetHeights }; } // src/zero/components/TaskPanel.tsx import { jsx as jsx2, jsxs } from "react/jsx-runtime"; var TASK_TYPE_COLORS = { todo: "bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200", fixme: "bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200", bug: "bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200", hack: "bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200", note: "bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-200", optimize: "bg-purple-100 text-purple-800 dark:bg-purple-900 dark:text-purple-200", refactor: "bg-orange-100 text-orange-800 dark:bg-orange-900 dark:text-orange-200" }; var PRIORITY_ICONS = { high: "\u{1F534}", medium: "\u{1F7E1}", low: "\u{1F7E2}" }; function TaskPanel({ tasks, isScanning = false, lastScanTime, onRescan, className = "" }) { const [filter, setFilter] = useState3("all"); const [searchTerm, setSearchTerm] = useState3(""); const filteredTasks = useMemo2(() => { return tasks.filter((task) => { if (filter !== "all" && task.type !== filter) { return false; } if (searchTerm && !task.content.toLowerCase().includes(searchTerm.toLowerCase())) { return false; } return true; }); }, [tasks, filter, searchTerm]); const taskCounts = useMemo2(() => { const counts = {}; tasks.forEach((task) => { counts[task.type] = (counts[task.type] || 0) + 1; }); return counts; }, [tasks]); const taskTypes = Object.keys(taskCounts); return /* @__PURE__ */ jsxs("div", { className: `flex flex-col h-full bg-white dark:bg-gray-900 ${className}`, children: [ /* @__PURE__ */ jsxs("div", { className: "flex-shrink-0 border-b border-gray-200 dark:border-gray-700 p-4", children: [ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mb-3", children: [ /* @__PURE__ */ jsxs("h2", { className: "text-lg font-semibold text-gray-900 dark:text-white", children: [ "Tasks (", filteredTasks.length, ")" ] }), /* @__PURE__ */ jsx2( "button", { onClick: onRescan, disabled: isScanning, className: "px-3 py-1 text-sm bg-blue-500 text-white rounded hover:bg-blue-600 disabled:opacity-50 disabled:cursor-not-allowed", children: isScanning ? "Scanning..." : "Rescan" } ) ] }), /* @__PURE__ */ jsx2("div", { className: "mb-3", children: /* @__PURE__ */ jsx2( "input", { type: "text", placeholder: "Search tasks...", value: searchTerm, onChange: (e) => setSearchTerm(e.target.value), className: "w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-800 text-gray-900 dark:text-white placeholder-gray-500 dark:placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500" } ) }), /* @__PURE__ */ jsxs("div", { className: "flex gap-2 overflow-x-auto", children: [ /* @__PURE__ */ jsxs( "button", { onClick: () => setFilter("all"), className: `px-3 py-1 text-sm rounded whitespace-nowrap ${filter === "all" ? "bg-blue-500 text-white" : "bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-600"}`, children: [ "All (", tasks.length, ")" ] } ), taskTypes.map((type) => /* @__PURE__ */ jsxs( "button", { onClick: () => setFilter(type), className: `px-3 py-1 text-sm rounded whitespace-nowrap ${filter === type ? "bg-blue-500 text-white" : "bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-600"}`, children: [ type.toUpperCase(), " (", taskCounts[type], ")" ] }, type )) ] }) ] }), /* @__PURE__ */ jsx2("div", { className: "flex-1 overflow-y-auto", children: filteredTasks.length === 0 ? /* @__PURE__ */ jsx2("div", { className: "text-center py-8 text-gray-500 dark:text-gray-400", children: searchTerm || filter !== "all" ? "No tasks match your filters" : "No tasks found. Tasks will appear here when detected in your code." }) : /* @__PURE__ */ jsx2( VirtualList, { items: filteredTasks, height: 600, itemHeight: 120, overscan: 3, renderItem: (task, index) => /* @__PURE__ */ jsx2("div", { className: "px-4 py-2", children: /* @__PURE__ */ jsx2(TaskCard, { task }) }), className: "w-full", emptyMessage: "No tasks found. Tasks will appear here when detected in your code." } ) }), lastScanTime && /* @__PURE__ */ jsxs("div", { className: "flex-shrink-0 border-t border-gray-200 dark:border-gray-700 px-4 py-2 text-xs text-gray-500 dark:text-gray-400", children: [ "Last scan: ", lastScanTime.toLocaleTimeString() ] }) ] }); } function TaskCard({ task }) { const typeColor = TASK_TYPE_COLORS[task.type] || TASK_TYPE_COLORS.note; const priorityIcon = PRIORITY_ICONS[task.priority]; return /* @__PURE__ */ jsx2("div", { className: "p-3 bg-gray-50 dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700", children: /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3", children: [ /* @__PURE__ */ jsx2("span", { className: "text-lg", title: `Priority: ${task.priority}`, children: priorityIcon }), /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 mb-1", children: [ /* @__PURE__ */ jsx2("span", { className: `px-2 py-0.5 text-xs font-medium rounded ${typeColor}`, children: task.type.toUpperCase() }), /* @__PURE__ */ jsx2("span", { className: "text-xs text-gray-500 dark:text-gray-400", children: task.file }) ] }), /* @__PURE__ */ jsx2("p", { className: "text-sm text-gray-900 dark:text-white break-words", children: task.content }), /* @__PURE__ */ jsxs("div", { className: "mt-1 text-xs text-gray-500 dark:text-gray-400", children: [ "Source: ", task.source ] }) ] }) ] }) }); } // src/zero/components/UpgradePrompt.tsx import { useState as useState4 } from "react"; import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime"; function UpgradePrompt({ currentLevel, nextLevel, onUpgrade, onDismiss, onLearnMore }) { const [isExpanded, setIsExpanded] = useState4(false); return /* @__PURE__ */ jsxs2("div", { className: "fixed bottom-4 right-4 max-w-md bg-white dark:bg-gray-900 rounded-lg shadow-lg border border-gray-200 dark:border-gray-700 overflow-hidden", children: [ /* @__PURE__ */ jsxs2("div", { className: "p-4", children: [ /* @__PURE__ */ jsxs2("div", { className: "flex items-start justify-between mb-3", children: [ /* @__PURE__ */ jsxs2("div", { className: "flex-1", children: [ /* @__PURE__ */ jsxs2("h3", { className: "text-lg font-semibold text-gray-900 dark:text-white", children: [ "Unlock ", nextLevel.name ] }), /* @__PURE__ */ jsx3("p", { className: "text-sm text-gray-600 dark:text-gray-400 mt-1", children: nextLevel.description }) ] }), onDismiss && /* @__PURE__ */ jsx3( "button", { onClick: onDismiss, className: "ml-3 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300", "aria-label": "Dismiss", children: /* @__PURE__ */ jsx3("svg", { className: "w-5 h-5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx3("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M6 18L18 6M6 6l12 12" }) }) } ) ] }), /* @__PURE__ */ jsxs2("div", { className: "mb-4", children: [ /* @__PURE__ */ jsx3("h4", { className: "text-sm font-medium text-gray-700 dark:text-gray-300 mb-2", children: "New features:" }), /* @__PURE__ */ jsx3("ul", { className: "space-y-1", children: nextLevel.features.map((feature, index) => /* @__PURE__ */ jsxs2("li", { className: "flex items-center text-sm text-gray-600 dark:text-gray-400", children: [ /* @__PURE__ */ jsx3("svg", { className: "w-4 h-4 mr-2 text-green-500", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx3("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M5 13l4 4L19 7" }) }), feature ] }, index)) }) ] }), nextLevel.setupTime && /* @__PURE__ */ jsxs2("div", { className: "mb-4 flex items-center text-sm text-gray-600 dark:text-gray-400", children: [ /* @__PURE__ */ jsx3("svg", { className: "w-4 h-4 mr-2", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx3("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" }) }), "Setup time: ", nextLevel.setupTime ] }), nextLevel.setupSteps && nextLevel.setupSteps.length > 0 && /* @__PURE__ */ jsxs2("div", { className: "mb-4", children: [ /* @__PURE__ */ jsxs2( "button", { onClick: () => setIsExpanded(!isExpanded), className: "flex items-center text-sm text-blue-600 dark:text-blue-400 hover:text-blue-700 dark:hover:text-blue-300", children: [ /* @__PURE__ */ jsx3( "svg", { className: `w-4 h-4 mr-1 transform transition-transform ${isExpanded ? "rotate-90" : ""}`, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx3("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M9 5l7 7-7 7" }) } ), isExpanded ? "Hide" : "Show", " setup steps" ] } ), isExpanded && /* @__PURE__ */ jsx3("ol", { className: "mt-2 space-y-1 list-decimal list-inside", children: nextLevel.setupSteps.map((step, index) => /* @__PURE__ */ jsx3("li", { className: "text-sm text-gray-600 dark:text-gray-400", children: step }, index)) }) ] }), /* @__PURE__ */ jsxs2("div", { className: "flex gap-3", children: [ onUpgrade && /* @__PURE__ */ jsx3( "button", { onClick: onUpgrade, className: "flex-1 px-4 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500", children: "Upgrade Now" } ), onLearnMore && /* @__PURE__ */ jsx3( "button", { onClick: onLearnMore, className: "flex-1 px-4 py-2 bg-gray-100 dark:bg-gray-800 text-gray-700 dark:text-gray-300 rounded-md hover:bg-gray-200 dark:hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-gray-500", children: "Learn More" } ), !onUpgrade && !onLearnMore && onDismiss && /* @__PURE__ */ jsx3( "button", { onClick: onDismiss, className: "flex-1 px-4 py-2 bg-gray-100 dark:bg-gray-800 text-gray-700 dark:text-gray-300 rounded-md hover:bg-gray-200 dark:hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-gray-500", children: "Maybe Later" } ) ] }) ] }), /* @__PURE__ */ jsx3("div", { className: "h-1 bg-gray-200 dark:bg-gray-700", children: /* @__PURE__ */ jsx3( "div", { className: "h-full bg-blue-500 transition-all duration-300", style: { width: `${(currentLevel + 1) / 4 * 100}%` } } ) }) ] }); } function UpgradeBanner({ message, actionText = "Upgrade", onAction, onDismiss }) { return /* @__PURE__ */ jsx3("div", { className: "bg-blue-50 dark:bg-blue-900/20 border-b border-blue-200 dark:border-blue-800 px-4 py-2", children: /* @__PURE__ */ jsxs2("div", { className: "flex items-center justify-between", children: [ /* @__PURE__ */ jsx3("p", { className: "text-sm text-blue-800 dark:text-blue-200", children: message }), /* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-2", children: [ onAction && /* @__PURE__ */ jsx3( "button", { onClick: onAction, className: "px-3 py-1 text-sm bg-blue-500 text-white rounded hover:bg-blue-600", children: actionText } ), onDismiss && /* @__PURE__ */ jsx3( "button", { onClick: onDismiss, className: "text-blue-600 dark:text-blue-400 hover:text-blue-700 dark:hover:text-blue-300", "aria-label": "Dismiss", children: /* @__PURE__ */ jsx3("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx3("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M6 18L18 6M6 6l12 12" }) }) } ) ] }) ] }) }); } // src/zero/components/UpgradeWizard.tsx import { useState as useState5 } from "react"; import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime"; function UpgradeWizard({ steps, onComplete, onCancel, title = "Enable Full Features", className = "" }) { const [currentStep, setCurrentStep] = useState5(0); const [completedSteps, setCompletedSteps] = useState5(/* @__PURE__ */ new Set()); const [isRunning, setIsRunning] = useState5(false); const [copiedCommand, setCopiedCommand] = useState5(null); const currentStepData = steps[currentStep]; const isLastStep = currentStep === steps.length - 1; const allStepsCompleted = completedSteps.size === steps.length; const handleStepComplete = async () => { setCompletedSteps((prev) => /* @__PURE__ */ new Set([...prev, currentStep])); if (currentStepData.action) { setIsRunning(true); try { await currentStepData.action(); } finally { setIsRunning(false); } } if (isLastStep) { onComplete?.(); } else { setCurrentStep((prev) => prev + 1); } }; const handleCopyCommand = (command) => { navigator.clipboard.writeText(command); setCopiedCommand(command); setTimeout(() => setCopiedCommand(null), 2e3); }; return /* @__PURE__ */ jsx4("div", { className: `fixed inset-0 z-50 flex items-center justify-center bg-black/50 ${className}`, children: /* @__PURE__ */ jsxs3("div", { className: "w-full max-w-2xl bg-white dark:bg-gray-900 rounded-lg shadow-xl overflow-hidden", children: [ /* @__PURE__ */ jsx4("div", { className: "px-6 py-4 border-b border-gray-200 dark:border-gray-700", children: /* @__PURE__ */ jsxs3("div", { className: "flex items-center justify-between", children: [ /* @__PURE__ */ jsx4("h2", { className: "text-xl font-semibold text-gray-900 dark:text-white", children: title }), onCancel && /* @__PURE__ */ jsx4( "button", { onClick: onCancel, className: "text-gray-400 hover:text-gray-600 dark:hover:text-gray-300", "aria-label": "Close", children: /* @__PURE__ */ jsx4("svg", { className: "w-5 h-5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx4("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M6 18L18 6M6 6l12 12" }) }) } ) ] }) }), /* @__PURE__ */ jsx4("div", { className: "px-6 py-3 bg-gray-50 dark:bg-gray-800", children: /* @__PURE__ */ jsx4("div", { className: "flex items-center space-x-2", children: steps.map((step, index) => /* @__PURE__ */ jsxs3("div", { className: "flex items-center", children: [ /* @__PURE__ */ jsx4( "div", { className: `w-8 h-8 rounded-full flex items-center justify-center text-sm font-medium ${completedSteps.has(index) ? "bg-green-500 text-white" : index === currentStep ? "bg-blue-500 text-white" : "bg-gray-300 dark:bg-gray-600 text-gray-600 dark:text-gray-400"}`, children: completedSteps.has(index) ? /* @__PURE__ */ jsx4("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx4("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M5 13l4 4L19 7" }) }) : index + 1 } ), index < steps.length - 1 && /* @__PURE__ */ jsx4( "div", { className: `w-12 h-0.5 ${completedSteps.has(index) ? "bg-green-500" : "bg-gray-300 dark:bg-gray-600"}` } ) ] }, index)) }) }), /* @__PURE__ */ jsxs3("div", { className: "px-6 py-6", children: [ /* @__PURE__ */ jsxs3("h3", { className: "text-lg font-medium text-gray-900 dark:text-white mb-2", children: [ "Step ", currentStep + 1, ": ", currentStepData.title ] }), currentStepData.description && /* @__PURE__ */ jsx4("p", { className: "text-gray-600 dark:text-gray-400 mb-4", children: currentStepData.description }), currentStepData.time && /* @__PURE__ */ jsxs3("div", { className: "flex items-center text-sm text-gray-500 dark:text-gray-400 mb-4", children: [ /* @__PURE__ */ jsx4("svg", { className: "w-4 h-4 mr-1", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx4("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" }) }), "Estimated time: ", currentStepData.time ] }), currentStepData.command && /* @__PURE__ */ jsxs3("div", { className: "mb-4", children: [ /* @__PURE__ */ jsx4("label", { className: "block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2", children: "Run this command:" }), /* @__PURE__ */ jsxs3("div", { className: "relative", children: [ /* @__PURE__ */ jsx4("pre", { className: "bg-gray-900 text-gray-100 p-4 rounded-md font-mono text-sm overflow-x-auto", children: currentStepData.command }), /* @__PURE__ */ jsx4( "button", { onClick: () => handleCopyCommand(currentStepData.command), className: "absolute top-2 right-2 px-3 py-1 text-xs bg-gray-700 hover:bg-gray-600 text-white rounded", children: copiedCommand === currentStepData.command ? "Copied!" : "Copy" } ) ] }) ] }), currentStepData.code && /* @__PURE__ */ jsxs3("div", { className: "mb-4", children: [ /* @__PURE__ */ jsx4("label", { className: "block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2", children: "Add this code:" }), /* @__PURE__ */ jsxs3("div", { className: "relative", children: [ /* @__PURE__ */ jsx4("pre", { className: "bg-gray-900 text-gray-100 p-4 rounded-md font-mono text-sm overflow-x-auto", children: /* @__PURE__ */ jsx4("code", { children: currentStepData.code }) }), /* @__PURE__ */ jsx4( "button", { onClick: () => handleCopyCommand(currentStepData.code), className: "absolute top-2 right-2 px-3 py-1 text-xs bg-gray-700 hover:bg-gray-600 text-white rounded", children: copiedCommand === currentStepData.code ? "Copied!" : "Copy" } ) ] }) ] }) ] }), /* @__PURE__ */ jsx4("div", { className: "px-6 py-4 bg-gray-50 dark:bg-gray-800 border-t border-gray-200 dark:border-gray-700", children: /* @__PURE__ */ jsxs3("div", { className: "flex items-center justify-between", children: [ /* @__PURE__ */ jsx4( "button", { onClick: () => setCurrentStep(Math.max(0, currentStep - 1)), disabled: currentStep === 0, className: "px-4 py-2 text-sm text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-md disabled:opacity-50 disabled:cursor-not-allowed", children: "Previous" } ), /* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-3", children: [ onCancel && /* @__PURE__ */ jsx4( "button", { onClick: onCancel, className: "px-4 py-2 text-sm text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-md", children: "Cancel" } ), /* @__PURE__ */ jsx4( "button", { onClick: handleStepComplete, disabled: isRunning, className: "px-4 py-2 text-sm bg-blue-500 text-white rounded-md hover:bg-blue-600 disabled:opacity-50 disabled:cursor-not-allowed", children: isRunning ? /* @__PURE__ */ jsxs3("span", { className: "flex items-center", children: [ /* @__PURE__ */ jsxs3("svg", { className: "animate-spin -ml-1 mr-2 h-4 w-4 text-white", fill: "none", viewBox: "0 0 24 24", children: [ /* @__PURE__ */ jsx4("circle", { className: "opacity-25", cx: "12", cy: "12", r: "10", stroke: "currentColor", strokeWidth: "4" }), /* @__PURE__ */ jsx4("path", { className: "opacity-75", fill: "currentColor", d: "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" }) ] }), "Running..." ] }) : isLastStep ? allStepsCompleted ? "Complete" : "Finish" : "Next" } ) ] }) ] }) }) ] }) }); } function QuickSetupWizard({ onComplete, onCancel }) { const steps = [ { title: "Create API Route", description: "First, we'll create an API route to enable server-side features.", command: "npx vibeship init --minimal", time: "30 seconds" }, { title: "Restart Development Server", description: "Restart your server to load the new API routes.", command: "npm run dev", time: "10 seconds" }, { title: "All Set!", description: "Your Vibeship DevTools now has full functionality. You can edit files, use AI features, and more!", action: async () => { await new Promise((resolve) => setTimeout(resolve, 1e3)); if (onComplete) { onComplete(); } else { window.location.reload(); } } } ]; return /* @__PURE__ */ jsx4( UpgradeWizard, { title: "Enable Full Features", steps, onComplete, onCancel } ); } // src/zero/components/ErrorBoundary.tsx import React5, { Component } from "react"; import { Fragment, jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime"; var ErrorBoundary = class extends Component { constructor(props) { super(props); this.retryTimeoutId = null; this.retry = () => { this.setState({ isRetrying: true }); this.retryTimeoutId = setTimeout(() => { this.setState({ hasError: false, error: void 0, errorInfo: void 0, isRetrying: false }); }, 500); }; this.state = { hasError: false, isRetrying: false }; } static getDerivedStateFromError(error) { return { hasError: true, error }; } componentDidCatch(error, errorInfo) { this.setState({ error, errorInfo }); this.props.onError?.(error, errorInfo); console.error("ErrorBoundary caught an error:", error, errorInfo); } componentDidUpdate(prevProps) { const { resetOnPropsChange, resetKeys } = this.props; const { hasError } = this.state; if (hasError && resetOnPropsChange) { this.setState({ hasError: false, error: void 0, errorInfo: void 0, isRetrying: false }); } if (hasError && resetKeys && prevProps.resetKeys) { const hasResetKeyChanged = resetKeys.some( (key, index) => key !== prevProps.resetKeys[index] ); if (hasResetKeyChanged) { this.setState({ hasError: false, error: void 0, errorInfo: void 0, isRetrying: false }); } } } componentWillUnmount() { if (this.retryTimeoutId) { clearTimeout(this.retryTimeoutId); } } render() { const { hasError, error, isRetrying } = this.state; const { children, fallback } = this.props; if (hasError && error) { if (fallback) { return fallback(error, this.retry); } return /* @__PURE__ */ jsx5( DefaultErrorFallback, { error, retry: this.retry, isRetrying } ); } return children; } }; function DefaultErrorFallback({ error, retry, isRetrying }) { return /* @__PURE__ */ jsx5("div", { className: "p-4 bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-lg", children: /* @__PURE__ */ jsxs4("div", { className: "flex items-start gap-3", children: [ /* @__PURE__ */ jsx5("div", { className: "flex-shrink-0", children: /* @__PURE__ */ jsx5( "svg", { className: "w-5 h-5 text-red-500", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx5( "path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" } ) } ) }), /* @__PURE__ */ jsxs4("div", { className: "flex-1 min-w-0", children: [ /* @__PURE__ */ jsx5("h3", { className: "text-sm font-medium text-red-800 dark:text-red-200", children: "Something went wrong" }), /* @__PURE__ */ jsx5("p", { className: "mt-1 text-sm text-red-700 dark:text-red-300", children: error.message || "An unexpected error occurred" }), /* @__PURE__ */ jsxs4("details", { className: "mt-2", children: [ /* @__PURE__ */ jsx5("summary", { className: "text-xs text-red-600 dark:text-red-400 cursor-pointer", children: "Error details (development only)" }), /* @__PURE__ */ jsx5("pre", { className: "mt-1 text-xs text-red-600 dark:text-red-400 whitespace-pre-wrap bg-red-100 dark:bg-red-900/40 p-2 rounded border overflow-auto max-h-32", children: error.stack }) ] }), /* @__PURE__ */ jsxs4("div", { className: "mt-3 flex gap-2", children: [ /* @__PURE__ */ jsx5( "button", { onClick: retry, disabled: isRetrying, className: "px-3 py-1 text-sm bg-red-600 text-white rounded hover:bg-red-700 disabled:opacity-50 disabled:cursor-not-allowed", children: isRetrying ? /* @__PURE__ */ jsxs4("span", { className: "flex items-center gap-1", children: [ /* @__PURE__ */ jsxs4("svg", { className: "animate-spin h-3 w-3", fill: "none", viewBox: "0 0 24 24", children: [ /* @__PURE__ */ jsx5("circle", { className: "opacity-25", cx: "12", cy: "12", r: "10", stroke: "currentColor", strokeWidth: "4" }), /* @__PURE__ */ jsx5("path", { className: "opacity-75", fill: "currentColor", d: "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" }) ] }), "Retrying..." ] }) : "Try Again" } ), /* @__PURE__ */ jsx5( "button", { onClick: () => window.location.reload(), className: "px-3 py-1 text-sm bg-gray-600 text-white rounded hover:bg-gray-700", children: "Reload Page" } ) ] }) ] }) ] }) }); } function withErrorBoundary(Component2, errorBoundaryProps) { const WrappedComponent = (props) => /* @__PURE__ */ jsx5(ErrorBoundary, { ...errorBoundaryProps, children: /* @__PURE__ */ jsx5(Component2, { ...props }) }); WrappedComponent.displayName = `withErrorBoundary(${Component2.displayName || Component2.name})`; return WrappedComponent; } function useErrorHandler() { const [error, setError] = React5.useState(null); const handleError = React5.useCallback((error2) => { setError(error2); console.error("Async error caught:", error2); }, []); const clearError = React5.useCallback(() => { setError(null); }, []); if (error) { throw error; } return { handleError, clearError }; } function AsyncErrorHandler({ children, onError }) { const { handleError } = useErrorHandler(); const wrappedHandleError = React5.useCallback((error) => { onError?.(error); handleError(error); }, [handleError, onError]); return /* @__PURE__ */ jsx5(Fragment, { children: children(wrappedHandleError) }); } // src/zero/VibeshipDevTools.tsx import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime"; function VibeshipDevTools({ position = "bottom-left", showUpgradePrompts = true, defaultOpen = false, className = "", onUpgrade, scanOptions = {} }) { const [isOpen, setIsOpen] = useState6(defaultOpen); const [showUpgrade, setShowUpgrade] = useState6(false); const [dismissedUpgrade, setDismissedUpgrade] = useState6(false); const [showSetupWizard, setShowSetupWizard] = useState6(false); useEffect4(() => { const handleKeyDown = (event) => { if ((event.metaKey || event.ctrlKey) && event.shiftKey && event.key === "K") { event.preventDefault(); setIsOpen(!isOpen); } }; window.addEventListener("keydown", handleKeyDown); return () => window.removeEventListener("keydown", handleKeyDown); }, [isOpen]); const { tasks, isScanning, lastScanTime, rescan } = useClientSideScan({ enabled: true, watchChanges: true, includePublicFiles: scanOptions.includePublicFiles ?? false, // Disabled by default to avoid 404s includeSourceMaps: scanOptions.includeSourceMaps ?? false, // Disabled by default to avoid 404s maxTasks: scanOptions.maxTasks ?? 100 }); const { currentLevel, levels, canUpgrade } = useProgressiveLevel(); useEffect4(() => { if (showUpgradePrompts && canUpgrade && !dismissedUpgrade && tasks.length > 0) { const timer = setTimeout(() => { setShowUpgrade(true); }, 3e4); return () => clearTimeout(timer); } }, [showUpgradePrompts, canUpgrade, dismissedUpgrade, tasks.length]); const positionClasses = { "bottom-right": "bottom-4 right-4", "bottom-left": "bottom-4 left-4", "top-right": "top-4 right-4", "top-left": "top-4 left-4" }; const panelPositionClasses = { "bottom-right": "bottom-16 right-4", "bottom-left": "bottom-16 left-4", "top-right": "top-16 right-4", "top-left": "top-16 left-4" }; return /* @__PURE__ */ jsxs5( ErrorBoundary, { fallback: (error, retry) => /* @__PURE__ */ jsx6("div", { className: `fixed ${positionClasses[position]} z-50`, children: /* @__PURE__ */ jsx6( "button", { onClick: retry, className: "relative p-3 bg-red-500 text-white rounded-full shadow-lg hover:bg-red-600 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2", "aria-label": "DevTools Error - Click to retry", title: `DevTools Error: ${error.message}`, children: /* @__PURE__ */ jsx6("svg", { className: "w-6 h-6", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx6("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" }) }) } ) }), children: [ /* @__PURE__ */ jsx6("div", { className: `fixed ${positionClasses[position]} z-50 ${className}`, children: /* @__PURE__ */ jsxs5( "button", { onClick: () => setIsOpen(!isOpen), className: "relative p-3 bg-purple-500 text-white rounded-full shadow-lg hover:bg-purple-600 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-offset-2", "aria-label": "Toggle DevTools", children: [ /* @__PURE__ */ jsx6("svg", { className: "w-6 h-6", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx6("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4" }) }), tasks.length > 0 && /* @__PURE__ */ jsx6("span", { className: "absolute -top-1 -right-1 px-2 py-0.5 text-xs bg-red-500 text-white rounded-full", children: tasks.length }) ] } ) }), isOpen && /* @__PURE__ */ jsxs5("div", { className: `fixed ${panelPositionClasses[position]} z-40 w-96 h-[600px] max-h-[80vh] bg-white dark:bg-gray-900 rounded-lg shadow-xl border border-gray-200 dark:border-gray-700 overflow-hidden`, children: [ currentLevel === 0 && !dismissedUpgrade && /* @__PURE__ */ jsx6( UpgradeBanner, { message: "Run