@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
JavaScript
"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