The New Archon (Beta) - The Operating System for AI Coding Assistants!

This commit is contained in:
Cole Medin
2025-08-13 07:58:24 -05:00
parent 13e1fc6a0e
commit 59084036f6
603 changed files with 131376 additions and 417 deletions

View File

@@ -0,0 +1,60 @@
import { useState } from 'react';
import { bugReportService, BugContext } from '../services/bugReportService';
export const useBugReport = () => {
const [isOpen, setIsOpen] = useState(false);
const [context, setContext] = useState<BugContext | null>(null);
const [loading, setLoading] = useState(false);
const openBugReport = async (error?: Error) => {
setLoading(true);
try {
const bugContext = await bugReportService.collectBugContext(error);
setContext(bugContext);
setIsOpen(true);
} catch (contextError) {
console.error('Failed to collect bug context:', contextError);
// Still open the modal but with minimal context
setContext({
error: {
message: error?.message || 'Manual bug report',
stack: error?.stack,
name: error?.name || 'UserReportedError'
},
app: {
version: 'unknown',
url: window.location.href,
timestamp: new Date().toISOString()
},
system: {
platform: navigator.platform,
userAgent: navigator.userAgent,
memory: 'unknown'
},
services: {
server: false,
mcp: false,
agents: false
},
logs: ['Failed to collect logs']
});
setIsOpen(true);
} finally {
setLoading(false);
}
};
const closeBugReport = () => {
setIsOpen(false);
setContext(null);
};
return {
isOpen,
context,
loading,
openBugReport,
closeBugReport
};
};

View File

@@ -0,0 +1,92 @@
import { useState, useRef } from 'react'
interface TiltOptions {
max: number
scale: number
speed: number
perspective: number
easing: string
}
export const useCardTilt = (options: Partial<TiltOptions> = {}) => {
const {
max = 15,
scale = 1.05,
speed = 500,
perspective = 1000,
easing = 'cubic-bezier(.03,.98,.52,.99)',
} = options
const [tiltStyles, setTiltStyles] = useState({
transform: `perspective(${perspective}px) rotateX(0deg) rotateY(0deg) scale3d(1, 1, 1)`,
transition: `transform ${speed}ms ${easing}`,
reflectionOpacity: 0,
reflectionPosition: '50% 50%',
glowIntensity: 0,
glowPosition: { x: 50, y: 50 },
})
const cardRef = useRef<HTMLDivElement>(null)
const isHovering = useRef(false)
const handleMouseMove = (e: React.MouseEvent<HTMLDivElement>) => {
if (!cardRef.current) return
const rect = cardRef.current.getBoundingClientRect()
const x = e.clientX - rect.left
const y = e.clientY - rect.top
const centerX = rect.width / 2
const centerY = rect.height / 2
const percentX = (x - centerX) / centerX
const percentY = (y - centerY) / centerY
const tiltX = max * -1 * percentY
const tiltY = max * percentX
// Calculate glow position (0-100%)
const glowX = (x / rect.width) * 100
const glowY = (y / rect.height) * 100
// Calculate reflection position
const reflectionX = 50 + percentX * 15
const reflectionY = 50 + percentY * 15
setTiltStyles({
transform: `perspective(${perspective}px) rotateX(${tiltX}deg) rotateY(${tiltY}deg) scale3d(${scale}, ${scale}, ${scale})`,
transition: `transform ${speed}ms ${easing}`,
reflectionOpacity: 0.15,
reflectionPosition: `${reflectionX}% ${reflectionY}%`,
glowIntensity: 1,
glowPosition: { x: glowX, y: glowY },
})
}
const handleMouseEnter = () => {
isHovering.current = true
}
const handleMouseLeave = () => {
isHovering.current = false
setTiltStyles({
transform: `perspective(${perspective}px) rotateX(0deg) rotateY(0deg) scale3d(1, 1, 1)`,
transition: `transform ${speed}ms ${easing}`,
reflectionOpacity: 0,
reflectionPosition: '50% 50%',
glowIntensity: 0,
glowPosition: { x: 50, y: 50 },
})
}
const handleClick = () => {
// Bounce animation on click
if (cardRef.current) {
cardRef.current.style.animation = 'card-bounce 0.4s'
cardRef.current.addEventListener(
'animationend',
() => {
if (cardRef.current) {
cardRef.current.style.animation = ''
}
},
{ once: true },
)
}
}
return {
cardRef,
tiltStyles,
handlers: {
onMouseMove: handleMouseMove,
onMouseEnter: handleMouseEnter,
onMouseLeave: handleMouseLeave,
onClick: handleClick,
},
}
}

View File

@@ -0,0 +1,203 @@
import { useEffect, useRef, useState } from 'react';
interface NeonGlowOptions {
opacity?: number;
blur?: number;
size?: number;
color?: string;
speed?: number;
enabled?: boolean;
}
interface NeonGlowHook {
containerRef: React.RefObject<HTMLDivElement>;
isAnimating: boolean;
start: () => void;
stop: () => void;
updateOptions: (options: Partial<NeonGlowOptions>) => void;
}
export const useNeonGlow = (initialOptions: NeonGlowOptions = {}): NeonGlowHook => {
const containerRef = useRef<HTMLDivElement>(null);
const [isAnimating, setIsAnimating] = useState(false);
const [options, setOptions] = useState<Required<NeonGlowOptions>>({
opacity: 0.8,
blur: 2,
size: 100,
color: 'blue',
speed: 2000,
enabled: true,
...initialOptions
});
const animationRef = useRef<number>();
const elementsRef = useRef<HTMLDivElement[]>([]);
// Create optimized heart chakra pattern
const createHeartChakra = () => {
if (!containerRef.current) return;
// Clear existing elements
elementsRef.current.forEach(el => {
if (containerRef.current?.contains(el)) {
containerRef.current.removeChild(el);
}
});
elementsRef.current = [];
const container = containerRef.current;
const centerX = container.clientWidth / 2;
const centerY = container.clientHeight / 2;
const radius = options.size;
// Create heart shape using mathematical equation
// Using fewer points for better performance (20 instead of 100)
const heartPoints = [];
for (let i = 0; i < 20; i++) {
const t = (i / 20) * Math.PI * 2;
// Heart equation: x = 16sin³(t), y = 13cos(t) - 5cos(2t) - 2cos(3t) - cos(4t)
const heartX = centerX + Math.pow(Math.sin(t), 3) * radius * 0.8;
const heartY = centerY - (13 * Math.cos(t) - 5 * Math.cos(2 * t) - 2 * Math.cos(3 * t) - Math.cos(4 * t)) * radius * 0.04;
heartPoints.push({ x: heartX, y: heartY });
}
// Create 12 radiating lines from center (reduced from more for performance)
const rayPoints = [];
for (let ray = 0; ray < 12; ray++) {
const rayAngle = (ray * Math.PI * 2 / 12);
const rayRadius = radius * 0.8;
rayPoints.push({
x: centerX + Math.cos(rayAngle) * rayRadius,
y: centerY + Math.sin(rayAngle) * rayRadius
});
}
// Create elements using CSS animations instead of JS manipulation
[...heartPoints, ...rayPoints].forEach((point, index) => {
const element = document.createElement('div');
element.className = 'neon-glow-particle';
// Use CSS custom properties for easy updates
element.style.cssText = `
position: absolute;
width: 8px;
height: 8px;
border-radius: 50%;
left: ${point.x}px;
top: ${point.y}px;
transform: translate(-50%, -50%);
background: transparent;
box-shadow:
0 0 10px hsla(220, 90%, 60%, var(--neon-opacity)),
0 0 20px hsla(260, 80%, 50%, calc(var(--neon-opacity) * 0.7)),
0 0 30px hsla(220, 70%, 40%, calc(var(--neon-opacity) * 0.5));
filter: blur(var(--neon-blur));
animation: neonPulse var(--neon-speed) ease-in-out infinite;
animation-delay: ${index * 50}ms;
pointer-events: none;
`;
container.appendChild(element);
elementsRef.current.push(element);
});
// Update CSS custom properties
updateCSSProperties();
};
const updateCSSProperties = () => {
if (!containerRef.current) return;
const container = containerRef.current;
container.style.setProperty('--neon-opacity', options.opacity.toString());
container.style.setProperty('--neon-blur', `${options.blur}px`);
container.style.setProperty('--neon-speed', `${options.speed}ms`);
};
const start = () => {
if (!options.enabled || isAnimating) return;
setIsAnimating(true);
createHeartChakra();
};
const stop = () => {
setIsAnimating(false);
if (animationRef.current) {
cancelAnimationFrame(animationRef.current);
}
// Clean up elements
elementsRef.current.forEach(el => {
if (containerRef.current?.contains(el)) {
containerRef.current.removeChild(el);
}
});
elementsRef.current = [];
};
const updateOptions = (newOptions: Partial<NeonGlowOptions>) => {
setOptions(prev => ({ ...prev, ...newOptions }));
};
// Add CSS keyframes when component mounts
useEffect(() => {
const style = document.createElement('style');
style.textContent = `
@keyframes neonPulse {
0%, 100% {
opacity: 1;
transform: translate(-50%, -50%) scale(1);
}
50% {
opacity: 0.6;
transform: translate(-50%, -50%) scale(1.2);
}
}
.neon-glow-container {
position: relative;
overflow: hidden;
}
`;
document.head.appendChild(style);
return () => {
if (document.head.contains(style)) {
document.head.removeChild(style);
}
};
}, []);
// Update CSS properties when options change
useEffect(() => {
if (isAnimating) {
updateCSSProperties();
}
}, [options, isAnimating]);
// Recreate pattern when size changes
useEffect(() => {
if (isAnimating && containerRef.current) {
createHeartChakra();
}
}, [options.size]);
// Cleanup on unmount
useEffect(() => {
return () => {
stop();
};
}, []);
return {
containerRef,
isAnimating,
start,
stop,
updateOptions
};
};

View File

@@ -0,0 +1,53 @@
import { useRef, useCallback } from 'react';
export interface PendingUpdate<T> {
id: string;
timestamp: number;
data: T;
operation: 'create' | 'update' | 'delete' | 'reorder';
}
/**
* Hook for tracking optimistic updates to prevent re-applying server echoes
*
* @example
* const { addPendingUpdate, isPendingUpdate } = useOptimisticUpdates<Task>();
*
* // When making an optimistic update
* addPendingUpdate({
* id: task.id,
* timestamp: Date.now(),
* data: updatedTask,
* operation: 'update'
* });
*
* // When receiving server update
* if (!isPendingUpdate(task.id, serverTask)) {
* // Apply the update
* }
*/
export function useOptimisticUpdates<T extends { id: string }>() {
const pendingUpdatesRef = useRef<Map<string, PendingUpdate<T>>>(new Map());
const addPendingUpdate = useCallback((update: PendingUpdate<T>) => {
pendingUpdatesRef.current.set(update.id, update);
// Auto-cleanup after 5 seconds
setTimeout(() => {
pendingUpdatesRef.current.delete(update.id);
}, 5000);
}, []);
const isPendingUpdate = useCallback((id: string, data: T): boolean => {
const pending = pendingUpdatesRef.current.get(id);
if (!pending) return false;
// Compare relevant fields based on operation type
return JSON.stringify(pending.data) === JSON.stringify(data);
}, []);
const removePendingUpdate = useCallback((id: string) => {
pendingUpdatesRef.current.delete(id);
}, []);
return { addPendingUpdate, isPendingUpdate, removePendingUpdate };
}

View File

@@ -0,0 +1,37 @@
import { useEffect, useCallback, DependencyList } from 'react';
import { WebSocketService, WebSocketMessage } from '../services/socketIOService';
/**
* Hook for managing Socket.IO subscriptions with proper cleanup and memoization
*
* @example
* useSocketSubscription(
* taskUpdateSocketIO,
* 'task_updated',
* (data) => {
* console.log('Task updated:', data);
* },
* [dependency1, dependency2]
* );
*/
export function useSocketSubscription<T = any>(
socket: WebSocketService,
eventName: string,
handler: (data: T) => void,
deps: DependencyList = []
) {
// Memoize the handler
const stableHandler = useCallback(handler, deps);
useEffect(() => {
const messageHandler = (message: WebSocketMessage) => {
stableHandler(message.data || message);
};
socket.addMessageHandler(eventName, messageHandler);
return () => {
socket.removeMessageHandler(eventName, messageHandler);
};
}, [socket, eventName, stableHandler]);
}

View File

@@ -0,0 +1,74 @@
import { useEffect, useState } from 'react';
/**
* Custom hook for creating staggered entrance animations
* @param items Array of items to animate
* @param staggerDelay Delay between each item animation (in seconds)
* @param forceReanimateCounter Optional counter to force reanimation when it changes
* @returns Animation variants and props for Framer Motion
*/
export const useStaggeredEntrance = <T,>(items: T[], staggerDelay: number = 0.15, forceReanimateCounter?: number) => {
const [isVisible, setIsVisible] = useState(false);
useEffect(() => {
// Set visible after component mounts for the animation to trigger
setIsVisible(true);
// Reset visibility briefly to trigger reanimation when counter changes
if (forceReanimateCounter !== undefined && forceReanimateCounter > 0) {
setIsVisible(false);
const timer = setTimeout(() => {
setIsVisible(true);
}, 50);
return () => clearTimeout(timer);
}
}, [forceReanimateCounter]);
// Parent container variants
const containerVariants = {
hidden: {
opacity: 0
},
visible: {
opacity: 1,
transition: {
staggerChildren: staggerDelay,
delayChildren: 0.1
}
}
};
// Child item variants
const itemVariants = {
hidden: {
opacity: 0,
y: 20,
scale: 0.98
},
visible: {
opacity: 1,
y: 0,
scale: 1,
transition: {
duration: 0.4,
ease: 'easeOut'
}
}
};
// Title animation variants
const titleVariants = {
hidden: {
opacity: 0,
scale: 0.98
},
visible: {
opacity: 1,
scale: 1,
transition: {
duration: 0.4,
ease: 'easeOut'
}
}
};
return {
isVisible,
containerVariants,
itemVariants,
titleVariants
};
};

View File

@@ -0,0 +1,142 @@
/**
* Task Socket Hook - Simplified real-time task synchronization
*
* This hook provides a clean interface to the task socket service,
* replacing the complex useOptimisticUpdates pattern with a simpler
* approach that avoids conflicts and connection issues.
*/
import { useEffect, useRef, useCallback } from 'react';
import { taskSocketService, TaskSocketEvents } from '../services/taskSocketService';
import { WebSocketState } from '../services/socketIOService';
export interface UseTaskSocketOptions {
projectId: string;
onTaskCreated?: (task: any) => void;
onTaskUpdated?: (task: any) => void;
onTaskDeleted?: (task: any) => void;
onTaskArchived?: (task: any) => void;
onTasksReordered?: (data: any) => void;
onInitialTasks?: (tasks: any[]) => void;
onConnectionStateChange?: (state: WebSocketState) => void;
}
export function useTaskSocket(options: UseTaskSocketOptions) {
const {
projectId,
onTaskCreated,
onTaskUpdated,
onTaskDeleted,
onTaskArchived,
onTasksReordered,
onInitialTasks,
onConnectionStateChange
} = options;
const componentIdRef = useRef<string>(`task-socket-${Math.random().toString(36).substring(7)}`);
const currentProjectIdRef = useRef<string | null>(null);
const isInitializedRef = useRef<boolean>(false);
// Memoized handlers to prevent unnecessary re-registrations
const memoizedHandlers = useCallback((): Partial<TaskSocketEvents> => {
return {
onTaskCreated,
onTaskUpdated,
onTaskDeleted,
onTaskArchived,
onTasksReordered,
onInitialTasks,
onConnectionStateChange
};
}, [
onTaskCreated,
onTaskUpdated,
onTaskDeleted,
onTaskArchived,
onTasksReordered,
onInitialTasks,
onConnectionStateChange
]);
// Initialize connection once and register handlers
useEffect(() => {
if (!projectId || isInitializedRef.current) return;
const initializeConnection = async () => {
try {
console.log(`[USE_TASK_SOCKET] Initializing connection for project: ${projectId}`);
// Register handlers first
taskSocketService.registerHandlers(componentIdRef.current, memoizedHandlers());
// Connect to project (singleton service will handle deduplication)
await taskSocketService.connectToProject(projectId);
currentProjectIdRef.current = projectId;
isInitializedRef.current = true;
console.log(`[USE_TASK_SOCKET] Successfully initialized for project: ${projectId}`);
} catch (error) {
console.error(`[USE_TASK_SOCKET] Failed to initialize for project ${projectId}:`, error);
}
};
initializeConnection();
}, [projectId, memoizedHandlers]);
// Update handlers when they change (without reconnecting)
useEffect(() => {
if (isInitializedRef.current && currentProjectIdRef.current === projectId) {
console.log(`[USE_TASK_SOCKET] Updating handlers for component: ${componentIdRef.current}`);
taskSocketService.registerHandlers(componentIdRef.current, memoizedHandlers());
}
}, [memoizedHandlers, projectId]);
// Handle project change (different project)
useEffect(() => {
if (!projectId) return;
// If project changed, reconnect
if (isInitializedRef.current && currentProjectIdRef.current !== projectId) {
console.log(`[USE_TASK_SOCKET] Project changed from ${currentProjectIdRef.current} to ${projectId}`);
const switchProject = async () => {
try {
// Update handlers for new project
taskSocketService.registerHandlers(componentIdRef.current, memoizedHandlers());
// Connect to new project (service handles disconnecting from old)
await taskSocketService.connectToProject(projectId);
currentProjectIdRef.current = projectId;
console.log(`[USE_TASK_SOCKET] Successfully switched to project: ${projectId}`);
} catch (error) {
console.error(`[USE_TASK_SOCKET] Failed to switch to project ${projectId}:`, error);
}
};
switchProject();
}
}, [projectId, memoizedHandlers]);
// Cleanup on unmount
useEffect(() => {
const componentId = componentIdRef.current;
return () => {
console.log(`[USE_TASK_SOCKET] Cleaning up component: ${componentId}`);
taskSocketService.unregisterHandlers(componentId);
isInitializedRef.current = false;
};
}, []);
// Return utility functions
return {
isConnected: taskSocketService.isConnected(),
connectionState: taskSocketService.getConnectionState(),
reconnect: taskSocketService.reconnect.bind(taskSocketService),
getCurrentProjectId: taskSocketService.getCurrentProjectId.bind(taskSocketService)
};
}

View File

@@ -0,0 +1,74 @@
import { useEffect, useRef, useState } from 'react';
/**
* Custom hook for automatic terminal scrolling behavior
* Automatically scrolls to bottom when dependencies change
* BUT stops auto-scrolling when user manually scrolls up
*
* @param dependencies - Array of dependencies that trigger scroll
* @param enabled - Optional flag to enable/disable scrolling (default: true)
* @returns ref to attach to the scrollable container
*/
export const useTerminalScroll = <T = any>(
dependencies: T[],
enabled: boolean = true
) => {
const scrollContainerRef = useRef<HTMLDivElement>(null);
const [isUserScrolling, setIsUserScrolling] = useState(false);
const scrollTimeoutRef = useRef<NodeJS.Timeout | null>(null);
// Check if user is at bottom of scroll
const isAtBottom = () => {
if (!scrollContainerRef.current) return true;
const { scrollTop, scrollHeight, clientHeight } = scrollContainerRef.current;
// Allow 50px threshold for "at bottom" detection
return scrollHeight - scrollTop - clientHeight < 50;
};
// Handle user scroll events
useEffect(() => {
const container = scrollContainerRef.current;
if (!container) return;
const handleScroll = () => {
// Clear any existing timeout
if (scrollTimeoutRef.current) {
clearTimeout(scrollTimeoutRef.current);
}
// Check if user scrolled away from bottom
if (!isAtBottom()) {
setIsUserScrolling(true);
}
// Set timeout to re-enable auto-scroll if user returns to bottom
scrollTimeoutRef.current = setTimeout(() => {
if (isAtBottom()) {
setIsUserScrolling(false);
}
}, 100);
};
container.addEventListener('scroll', handleScroll);
return () => {
container.removeEventListener('scroll', handleScroll);
if (scrollTimeoutRef.current) {
clearTimeout(scrollTimeoutRef.current);
}
};
}, []);
// Auto-scroll effect
useEffect(() => {
if (scrollContainerRef.current && enabled && !isUserScrolling) {
// Use requestAnimationFrame for smooth scrolling
requestAnimationFrame(() => {
if (scrollContainerRef.current && !isUserScrolling) {
scrollContainerRef.current.scrollTop = scrollContainerRef.current.scrollHeight;
}
});
}
}, [...dependencies, isUserScrolling]);
return scrollContainerRef;
};