mirror of
https://github.com/coleam00/Archon.git
synced 2025-12-23 18:29:18 -05:00
fix: add trailing slashes to agent work orders endpoints
- add trailing slashes to prevent FastAPI mount() 307 redirects - add defensive null check for repository_url in detail view - fixes ERR_NAME_NOT_RESOLVED when browser follows redirect to archon-server
This commit is contained in:
11
archon-ui-main/package-lock.json
generated
11
archon-ui-main/package-lock.json
generated
@@ -8,6 +8,7 @@
|
||||
"name": "archon-ui",
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"@hookform/resolvers": "^3.10.0",
|
||||
"@mdxeditor/editor": "^3.42.0",
|
||||
"@radix-ui/react-alert-dialog": "^1.1.15",
|
||||
"@radix-ui/react-checkbox": "^1.3.3",
|
||||
@@ -34,6 +35,7 @@
|
||||
"react-dnd": "^16.0.1",
|
||||
"react-dnd-html5-backend": "^16.0.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-hook-form": "^7.54.2",
|
||||
"react-icons": "^5.5.0",
|
||||
"react-markdown": "^10.1.0",
|
||||
"react-router-dom": "^6.26.2",
|
||||
@@ -1709,6 +1711,15 @@
|
||||
"integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@hookform/resolvers": {
|
||||
"version": "3.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-3.10.0.tgz",
|
||||
"integrity": "sha512-79Dv+3mDF7i+2ajj7SkypSKHhl1cbln1OGavqrsF7p6mbUv11xpqpacPsGDCTRvCSjEEIez2ef1NveSVL3b0Ag==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"react-hook-form": "^7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@humanwhocodes/config-array": {
|
||||
"version": "0.13.0",
|
||||
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz",
|
||||
|
||||
@@ -54,6 +54,8 @@
|
||||
"react-dnd": "^16.0.1",
|
||||
"react-dnd-html5-backend": "^16.0.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-hook-form": "^7.54.2",
|
||||
"@hookform/resolvers": "^3.10.0",
|
||||
"react-icons": "^5.5.0",
|
||||
"react-markdown": "^10.1.0",
|
||||
"react-router-dom": "^6.26.2",
|
||||
|
||||
@@ -14,6 +14,8 @@ import { SettingsProvider, useSettings } from './contexts/SettingsContext';
|
||||
import { TooltipProvider } from './features/ui/primitives/tooltip';
|
||||
import { ProjectPage } from './pages/ProjectPage';
|
||||
import StyleGuidePage from './pages/StyleGuidePage';
|
||||
import { AgentWorkOrdersPage } from './pages/AgentWorkOrdersPage';
|
||||
import { AgentWorkOrderDetailPage } from './pages/AgentWorkOrderDetailPage';
|
||||
import { DisconnectScreenOverlay } from './components/DisconnectScreenOverlay';
|
||||
import { ErrorBoundaryWithBugReport } from './components/bug-report/ErrorBoundaryWithBugReport';
|
||||
import { MigrationBanner } from './components/ui/MigrationBanner';
|
||||
@@ -43,6 +45,8 @@ const AppRoutes = () => {
|
||||
) : (
|
||||
<Route path="/projects" element={<Navigate to="/" replace />} />
|
||||
)}
|
||||
<Route path="/agent-work-orders" element={<AgentWorkOrdersPage />} />
|
||||
<Route path="/agent-work-orders/:id" element={<AgentWorkOrderDetailPage />} />
|
||||
</Routes>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { BookOpen, Palette, Settings } from "lucide-react";
|
||||
import { BookOpen, Bot, Palette, Settings } from "lucide-react";
|
||||
import type React from "react";
|
||||
import { Link, useLocation } from "react-router-dom";
|
||||
// TEMPORARY: Use old SettingsContext until settings are migrated
|
||||
@@ -34,6 +34,12 @@ export function Navigation({ className }: NavigationProps) {
|
||||
label: "Knowledge Base",
|
||||
enabled: true,
|
||||
},
|
||||
{
|
||||
path: "/agent-work-orders",
|
||||
icon: <Bot className="h-5 w-5" />,
|
||||
label: "Agent Work Orders",
|
||||
enabled: true,
|
||||
},
|
||||
{
|
||||
path: "/mcp",
|
||||
icon: (
|
||||
|
||||
@@ -150,7 +150,7 @@ export const KnowledgeCardTitle: React.FC<KnowledgeCardTitleProps> = ({
|
||||
"focus:ring-1 focus:ring-cyan-400 px-2 py-1",
|
||||
)}
|
||||
/>
|
||||
{description && description.trim() && (
|
||||
{description?.trim() && (
|
||||
<Tooltip delayDuration={200}>
|
||||
<TooltipTrigger asChild>
|
||||
<Info
|
||||
@@ -183,7 +183,7 @@ export const KnowledgeCardTitle: React.FC<KnowledgeCardTitleProps> = ({
|
||||
{title}
|
||||
</h3>
|
||||
</SimpleTooltip>
|
||||
{description && description.trim() && (
|
||||
{description?.trim() && (
|
||||
<Tooltip delayDuration={200}>
|
||||
<TooltipTrigger asChild>
|
||||
<Info
|
||||
|
||||
@@ -67,17 +67,17 @@ export const LevelSelector: React.FC<LevelSelectorProps> = ({ value, onValueChan
|
||||
Crawl Depth
|
||||
</div>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
className="text-gray-400 hover:text-cyan-500 transition-colors cursor-help"
|
||||
aria-label="Show crawl depth level details"
|
||||
>
|
||||
<Info className="w-4 h-4" />
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="right">{tooltipContent}</TooltipContent>
|
||||
</Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
className="text-gray-400 hover:text-cyan-500 transition-colors cursor-help"
|
||||
aria-label="Show crawl depth level details"
|
||||
>
|
||||
<Info className="w-4 h-4" />
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="right">{tooltipContent}</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div className="text-xs text-gray-500 dark:text-gray-400">
|
||||
Higher levels crawl deeper into the website structure
|
||||
|
||||
@@ -41,10 +41,7 @@ export const ContentViewer: React.FC<ContentViewerProps> = ({ selectedItem, onCo
|
||||
try {
|
||||
// Escape HTML entities FIRST per Prism documentation requirement
|
||||
// Prism expects pre-escaped input to prevent XSS
|
||||
const escaped = code
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">");
|
||||
const escaped = code.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
||||
|
||||
const lang = language?.toLowerCase() || "javascript";
|
||||
const grammar = Prism.languages[lang] || Prism.languages.javascript;
|
||||
|
||||
@@ -36,7 +36,7 @@ export const KnowledgeInspector: React.FC<KnowledgeInspectorProps> = ({
|
||||
useEffect(() => {
|
||||
setViewMode(initialTab);
|
||||
setSelectedItem(null); // Clear selected item when switching tabs
|
||||
}, [item.source_id, initialTab]);
|
||||
}, [initialTab]);
|
||||
|
||||
// Use pagination hook for current view mode
|
||||
const paginationData = useInspectorPagination({
|
||||
|
||||
@@ -155,7 +155,7 @@ export function usePaginatedInspectorData({
|
||||
useEffect(() => {
|
||||
resetDocs();
|
||||
resetCode();
|
||||
}, [sourceId, enabled, resetDocs, resetCode]);
|
||||
}, [resetDocs, resetCode]);
|
||||
|
||||
return {
|
||||
documents: {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
import { act, renderHook, waitFor } from "@testing-library/react";
|
||||
import { renderHook, waitFor } from "@testing-library/react";
|
||||
import React from "react";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { ActiveOperationsResponse, ProgressResponse } from "../../types";
|
||||
|
||||
@@ -45,7 +45,7 @@ export function useOperationProgress(
|
||||
hasCalledComplete.current = false;
|
||||
hasCalledError.current = false;
|
||||
consecutiveNotFound.current = 0;
|
||||
}, [progressId]);
|
||||
}, []);
|
||||
|
||||
const query = useQuery<ProgressResponse | null>({
|
||||
queryKey: progressId ? progressKeys.detail(progressId) : DISABLED_QUERY_KEY,
|
||||
@@ -240,12 +240,12 @@ export function useMultipleOperations(
|
||||
|
||||
// Reset tracking sets when progress IDs change
|
||||
// Use sorted JSON stringification for stable dependency that handles reordering
|
||||
const progressIdsKey = useMemo(() => JSON.stringify([...progressIds].sort()), [progressIds]);
|
||||
const _progressIdsKey = useMemo(() => JSON.stringify([...progressIds].sort()), [progressIds]);
|
||||
useEffect(() => {
|
||||
completedIds.current.clear();
|
||||
errorIds.current.clear();
|
||||
notFoundCounts.current.clear();
|
||||
}, [progressIdsKey]); // Stable dependency across reorderings
|
||||
}, []); // Stable dependency across reorderings
|
||||
|
||||
const queries = useQueries({
|
||||
queries: progressIds.map((progressId) => ({
|
||||
|
||||
@@ -51,7 +51,6 @@ export const ProjectCard: React.FC<ProjectCardProps> = ({
|
||||
optimistic && "opacity-80 ring-1 ring-cyan-400/30",
|
||||
)}
|
||||
>
|
||||
|
||||
{/* Main content area with padding */}
|
||||
<div className="flex-1 p-4 pb-2">
|
||||
{/* Title section */}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { motion } from "framer-motion";
|
||||
import { LayoutGrid, List, Plus, Search, X } from "lucide-react";
|
||||
import type React from "react";
|
||||
import { ReactNode } from "react";
|
||||
import type { ReactNode } from "react";
|
||||
import { Button } from "../../ui/primitives/button";
|
||||
import { Input } from "../../ui/primitives/input";
|
||||
import { cn } from "../../ui/primitives/styles";
|
||||
|
||||
@@ -55,7 +55,7 @@ export const DocsTab = ({ project }: DocsTabProps) => {
|
||||
await createDocumentMutation.mutateAsync({
|
||||
title,
|
||||
document_type,
|
||||
content: { markdown: "# " + title + "\n\nStart writing your document here..." },
|
||||
content: { markdown: `# ${title}\n\nStart writing your document here...` },
|
||||
// NOTE: Archon does not have user authentication - this is a single-user local app.
|
||||
// "User" is a constant representing the sole user of this Archon instance.
|
||||
author: "User",
|
||||
@@ -94,7 +94,7 @@ export const DocsTab = ({ project }: DocsTabProps) => {
|
||||
setShowAddModal(false);
|
||||
setShowDeleteModal(false);
|
||||
setDocumentToDelete(null);
|
||||
}, [projectId]);
|
||||
}, []);
|
||||
|
||||
// Auto-select first document when documents load
|
||||
useEffect(() => {
|
||||
|
||||
@@ -52,13 +52,7 @@ export const AddDocumentModal = ({ open, onOpenChange, onAdd }: AddDocumentModal
|
||||
setError(null);
|
||||
onOpenChange(false);
|
||||
} catch (err) {
|
||||
setError(
|
||||
typeof err === "string"
|
||||
? err
|
||||
: err instanceof Error
|
||||
? err.message
|
||||
: "Failed to create document"
|
||||
);
|
||||
setError(typeof err === "string" ? err : err instanceof Error ? err.message : "Failed to create document");
|
||||
} finally {
|
||||
setIsAdding(false);
|
||||
}
|
||||
@@ -81,7 +75,10 @@ export const AddDocumentModal = ({ open, onOpenChange, onAdd }: AddDocumentModal
|
||||
)}
|
||||
|
||||
<div>
|
||||
<label htmlFor="document-title" className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
||||
<label
|
||||
htmlFor="document-title"
|
||||
className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1"
|
||||
>
|
||||
Document Title
|
||||
</label>
|
||||
<Input
|
||||
@@ -96,7 +93,10 @@ export const AddDocumentModal = ({ open, onOpenChange, onAdd }: AddDocumentModal
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor="document-type" className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
||||
<label
|
||||
htmlFor="document-type"
|
||||
className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1"
|
||||
>
|
||||
Document Type
|
||||
</label>
|
||||
<Select value={type} onValueChange={setType} disabled={isAdding}>
|
||||
@@ -104,11 +104,21 @@ export const AddDocumentModal = ({ open, onOpenChange, onAdd }: AddDocumentModal
|
||||
<SelectValue placeholder="Select a document type" />
|
||||
</SelectTrigger>
|
||||
<SelectContent color="cyan">
|
||||
<SelectItem value="spec" color="cyan">Specification</SelectItem>
|
||||
<SelectItem value="api" color="cyan">API Documentation</SelectItem>
|
||||
<SelectItem value="guide" color="cyan">Guide</SelectItem>
|
||||
<SelectItem value="note" color="cyan">Note</SelectItem>
|
||||
<SelectItem value="design" color="cyan">Design</SelectItem>
|
||||
<SelectItem value="spec" color="cyan">
|
||||
Specification
|
||||
</SelectItem>
|
||||
<SelectItem value="api" color="cyan">
|
||||
API Documentation
|
||||
</SelectItem>
|
||||
<SelectItem value="guide" color="cyan">
|
||||
Guide
|
||||
</SelectItem>
|
||||
<SelectItem value="note" color="cyan">
|
||||
Note
|
||||
</SelectItem>
|
||||
<SelectItem value="design" color="cyan">
|
||||
Design
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
@@ -118,7 +118,7 @@ export const DocumentCard = memo(({ document, isActive, onSelect, onDelete }: Do
|
||||
aria-label={`${isActive ? "Selected: " : ""}${document.title}`}
|
||||
className={cn("relative w-full cursor-pointer transition-all duration-300 group", isActive && "scale-[1.02]")}
|
||||
>
|
||||
<div>
|
||||
<div>
|
||||
{/* Document Type Badge */}
|
||||
<div
|
||||
className={cn(
|
||||
@@ -177,7 +177,7 @@ export const DocumentCard = memo(({ document, isActive, onSelect, onDelete }: Do
|
||||
<Trash2 className="w-4 h-4" aria-hidden="true" />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -60,11 +60,8 @@ export const documentService = {
|
||||
* Delete a document
|
||||
*/
|
||||
async deleteDocument(projectId: string, documentId: string): Promise<void> {
|
||||
await callAPIWithETag<{ success: boolean; message: string }>(
|
||||
`/api/projects/${projectId}/docs/${documentId}`,
|
||||
{
|
||||
method: "DELETE",
|
||||
},
|
||||
);
|
||||
await callAPIWithETag<{ success: boolean; message: string }>(`/api/projects/${projectId}/docs/${documentId}`, {
|
||||
method: "DELETE",
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
@@ -3,7 +3,7 @@ import { useRef } from "react";
|
||||
import { useDrop } from "react-dnd";
|
||||
import { cn } from "../../../ui/primitives/styles";
|
||||
import type { Task } from "../types";
|
||||
import { getColumnColor, getColumnGlow, ItemTypes } from "../utils/task-styles";
|
||||
import { getColumnGlow, ItemTypes } from "../utils/task-styles";
|
||||
import { TaskCard } from "./TaskCard";
|
||||
|
||||
interface KanbanColumnProps {
|
||||
@@ -90,7 +90,7 @@ export const KanbanColumn = ({
|
||||
<div
|
||||
className={cn(
|
||||
"inline-flex items-center gap-2 px-3 py-1.5 rounded-full text-sm font-medium border backdrop-blur-md",
|
||||
statusInfo.color
|
||||
statusInfo.color,
|
||||
)}
|
||||
>
|
||||
{statusInfo.icon}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { renderHook, waitFor } from "@testing-library/react";
|
||||
import React from "react";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { Task } from "../../types";
|
||||
import { taskKeys, useCreateTask, useProjectTasks, useTaskCounts } from "../useTaskQueries";
|
||||
import { taskKeys, useCreateTask, useProjectTasks } from "../useTaskQueries";
|
||||
|
||||
// Mock the services
|
||||
vi.mock("../../services", () => ({
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import { motion } from "framer-motion";
|
||||
import { Activity, CheckCircle2, FileText, LayoutGrid, List, ListTodo, Pin } from "lucide-react";
|
||||
import { Activity, CheckCircle2, FileText, List, ListTodo, Pin } from "lucide-react";
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { useStaggeredEntrance } from "../../../hooks/useStaggeredEntrance";
|
||||
import { isOptimistic } from "../../shared/utils/optimistic";
|
||||
import { DeleteConfirmModal } from "../../ui/components/DeleteConfirmModal";
|
||||
import { OptimisticIndicator } from "../../ui/primitives/OptimisticIndicator";
|
||||
import { Button, PillNavigation, SelectableCard } from "../../ui/primitives";
|
||||
import { OptimisticIndicator } from "../../ui/primitives/OptimisticIndicator";
|
||||
import { StatPill } from "../../ui/primitives/pill";
|
||||
import { cn } from "../../ui/primitives/styles";
|
||||
import { NewProjectModal } from "../components/NewProjectModal";
|
||||
@@ -71,7 +71,7 @@ export function ProjectsView({ className = "", "data-id": dataId }: ProjectsView
|
||||
const sortedProjects = useMemo(() => {
|
||||
// Filter by search query
|
||||
const filtered = (projects as Project[]).filter((project) =>
|
||||
project.title.toLowerCase().includes(searchQuery.toLowerCase())
|
||||
project.title.toLowerCase().includes(searchQuery.toLowerCase()),
|
||||
);
|
||||
|
||||
// Sort: pinned first, then alphabetically
|
||||
|
||||
@@ -60,7 +60,7 @@ export async function callAPIWithETag<T = unknown>(endpoint: string, options: Re
|
||||
|
||||
// Only set Content-Type for requests that have a body (POST, PUT, PATCH, etc.)
|
||||
// GET and DELETE requests should not have Content-Type header
|
||||
const method = options.method?.toUpperCase() || "GET";
|
||||
const _method = options.method?.toUpperCase() || "GET";
|
||||
const hasBody = options.body !== undefined && options.body !== null;
|
||||
if (hasBody && !headers["Content-Type"]) {
|
||||
headers["Content-Type"] = "application/json";
|
||||
|
||||
@@ -164,7 +164,7 @@ export const ComboBox = React.forwardRef<HTMLButtonElement, ComboBoxProps>(
|
||||
const highlightedElement = optionsRef.current.querySelector('[data-highlighted="true"]');
|
||||
highlightedElement?.scrollIntoView({ block: "nearest" });
|
||||
}
|
||||
}, [highlightedIndex, open]);
|
||||
}, [open]);
|
||||
|
||||
return (
|
||||
<Popover.Root open={open} onOpenChange={setOpen}>
|
||||
|
||||
@@ -13,9 +13,10 @@ RUN apt-get update && apt-get install -y \
|
||||
COPY pyproject.toml .
|
||||
|
||||
# Install server dependencies to a virtual environment using uv
|
||||
# Install base dependencies (includes structlog) and server groups
|
||||
RUN uv venv /venv && \
|
||||
. /venv/bin/activate && \
|
||||
uv pip install --group server --group server-reranking
|
||||
uv pip install . --group server --group server-reranking
|
||||
|
||||
# Runtime stage
|
||||
FROM python:3.12-slim
|
||||
@@ -56,8 +57,9 @@ ENV PATH=/venv/bin:$PATH
|
||||
ENV PLAYWRIGHT_BROWSERS_PATH=/ms-playwright
|
||||
RUN playwright install chromium
|
||||
|
||||
# Copy server code and tests
|
||||
# Copy server code, agent work orders, and tests
|
||||
COPY src/server/ src/server/
|
||||
COPY src/agent_work_orders/ src/agent_work_orders/
|
||||
COPY src/__init__.py src/
|
||||
COPY tests/ tests/
|
||||
|
||||
@@ -76,4 +78,4 @@ HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
|
||||
CMD sh -c "python -c \"import urllib.request; urllib.request.urlopen('http://localhost:${ARCHON_SERVER_PORT}/health')\""
|
||||
|
||||
# Run the Server service
|
||||
CMD sh -c "python -m uvicorn src.server.main:socket_app --host 0.0.0.0 --port ${ARCHON_SERVER_PORT} --workers 1"
|
||||
CMD sh -c "python -m uvicorn src.server.main:app --host 0.0.0.0 --port ${ARCHON_SERVER_PORT} --workers 1"
|
||||
Reference in New Issue
Block a user