Add preview tab + point and click editor (vibe-kanban) (#870)
* Perfect! The preview tab implementation is complete and all type checks pass. Let me provide a summary of what was implemented: 1. **Updated `TabType`** - Added 'preview' to the union type 2. **Created `useDevserverPreview` hook** - Intelligent devserver URL detection with: - Real-time log parsing using the regex `(?:localhost|127\.0\.0\.1|0\.0\.0\.0):(\d+)[^\d]*` - Three states: idle/searching/ready - URL normalization (0.0.0.0 → localhost) - Process monitoring for setup scripts and dev servers 3. **Built `PreviewTab` component** - Full-featured iframe preview with: - Proper sandbox settings for security - Toolbar with refresh, copy URL, and open external buttons - Smart error handling for mixed content/CORS issues - Clear empty states and loading indicators 4. **Enhanced `TabNavigation`** - Conditionally shows preview tab with: - Monitor icon and spinner when searching - Only appears when devserver is detected or likely 5. **Integrated in `TaskDetailsPanel`** - Wires everything together in fullscreen mode - **Smart Detection**: Parses logs in real-time to find devserver URLs - **Robust Error Handling**: Graceful fallbacks for iframe loading issues - **Security**: Proper iframe sandboxing with necessary permissions - **Performance**: Efficient log processing with deduplication - **UX**: Clear states (searching/ready/error) with helpful messaging The implementation follows the Oracle's architectural recommendations with proper separation of concerns and real-time detection capabilities. * Cleanup script changes for task attempt 4f59c374-1cc5-4b3c-a79a-5f63756a366b * Perfect! Now the preview tab will use the **first** dev server URL it detects rather than being overwritten by subsequent matches. This means: - If both backend (e.g., `:3001`) and frontend (e.g., `:5173`) servers start - The preview tab will show whichever URL appears first in the logs - Subsequent URLs will be ignored to avoid switching the preview unexpectedly This provides a more stable preview experience when multiple dev servers are running. * Cleanup script changes for task attempt 4f59c374-1cc5-4b3c-a79a-5f63756a366b * add parent lib * fmt * Listen for communication from the preview tab (vibe-kanban ace46045) In frontend/src/components/tasks/TaskDetails/PreviewTab.tsx We should expect that the iframe will communicate via frontend/src/utils/previewBridge.ts When a message is received, we should add some details about the clicked element to the follow up textarea * Component to view clicked element (vibe-kanban e3b90cc1) frontend/src/components/tasks/TaskDetails/PreviewTab.tsx frontend/src/components/tasks/TaskFollowUpSection.tsx When a user clicks on an element, we should display a box in the follow up section similar to how we show reviews or conflicts. The section should display a summary of each of the elements, the name of the component and the file location. When the user sends a follow up, a markdown equivalent of the summary should be appended to the top of the follow up message. * Component to view clicked element (vibe-kanban e3b90cc1) frontend/src/components/tasks/TaskDetails/PreviewTab.tsx frontend/src/components/tasks/TaskFollowUpSection.tsx When a user clicks on an element, we should display a box in the follow up section similar to how we show reviews or conflicts. The section should display a summary of each of the elements, the name of the component and the file location. When the user sends a follow up, a markdown equivalent of the summary should be appended to the top of the follow up message. * Tweaks to component click (vibe-kanban 756e1212) Preview tab frontend/src/components/tasks/TaskDetails/PreviewTab.tsx - Preview should remember which URL you were on - Auto select the follow up box after point and click, so you can type feedback Clicked elements: frontend/src/components/tasks/ClickedElementsBanner.tsx, frontend/src/contexts/ClickedElementsProvider.tsx - The list of components should not overflow horizontally, instead we should truncate, omiting components from the left first - If the user clicks on a component, it should omit the downstream components from the list, they should be displayed disabled and the prompt should start from the selected component * strip ansi when parsing dev server URL * cleanup * cleanup * improve help copy * start dev server from preview page * dev server wip * restructure * instructions * fix * restructur * fmt * i18n * i18n fix * config fix * wip cleanup * minor cleanup * Preview tab feedback (vibe-kanban d531fff8) In the PreviewToolbar, each icon button should have a tooltip * fix + fmt * move dev script textarea * improve when help is shown * i18n * improve URL matching * fix close logs * auto install companion * cleanup notices * Copy tweak
This commit is contained in:
committed by
GitHub
parent
0ace01b55f
commit
2781e3651b
@@ -5,10 +5,11 @@ import { Button } from '@/components/ui/button';
|
||||
import { Card, CardContent } from '@/components/ui/card';
|
||||
import { AlertTriangle, Plus } from 'lucide-react';
|
||||
import { Loader } from '@/components/ui/loader';
|
||||
import { projectsApi, tasksApi, attemptsApi } from '@/lib/api';
|
||||
import { tasksApi, attemptsApi } from '@/lib/api';
|
||||
import { openTaskForm } from '@/lib/openTaskForm';
|
||||
|
||||
import { useSearch } from '@/contexts/search-context';
|
||||
import { useProject } from '@/contexts/project-context';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useTaskViewManager } from '@/hooks/useTaskViewManager';
|
||||
import {
|
||||
@@ -32,7 +33,7 @@ import {
|
||||
|
||||
import TaskKanbanBoard from '@/components/tasks/TaskKanbanBoard';
|
||||
import { TaskDetailsPanel } from '@/components/tasks/TaskDetailsPanel';
|
||||
import type { TaskWithAttemptStatus, Project, TaskAttempt } from 'shared/types';
|
||||
import type { TaskWithAttemptStatus, TaskAttempt } from 'shared/types';
|
||||
import type { DragEndEvent } from '@/components/ui/shadcn-io/kanban';
|
||||
import { useProjectTasks } from '@/hooks/useProjectTasks';
|
||||
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
|
||||
@@ -43,7 +44,7 @@ type Task = TaskWithAttemptStatus;
|
||||
|
||||
export function ProjectTasks() {
|
||||
const { t } = useTranslation(['tasks', 'common']);
|
||||
const { projectId, taskId, attemptId } = useParams<{
|
||||
const { taskId, attemptId } = useParams<{
|
||||
projectId: string;
|
||||
taskId?: string;
|
||||
attemptId?: string;
|
||||
@@ -51,6 +52,14 @@ export function ProjectTasks() {
|
||||
const navigate = useNavigate();
|
||||
const { enableScope, disableScope } = useHotkeysContext();
|
||||
|
||||
// Use project context for project data
|
||||
const {
|
||||
project,
|
||||
projectId,
|
||||
isLoading: projectLoading,
|
||||
error: projectError,
|
||||
} = useProject();
|
||||
|
||||
useEffect(() => {
|
||||
enableScope(Scope.KANBAN);
|
||||
|
||||
@@ -59,25 +68,22 @@ export function ProjectTasks() {
|
||||
};
|
||||
}, [enableScope, disableScope]);
|
||||
|
||||
const [project, setProject] = useState<Project | null>(null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
// Helper functions to open task forms
|
||||
const handleCreateTask = () => {
|
||||
if (project?.id) {
|
||||
openTaskForm({ projectId: project.id });
|
||||
if (projectId) {
|
||||
openTaskForm({ projectId });
|
||||
}
|
||||
};
|
||||
|
||||
const handleEditTask = (task: Task) => {
|
||||
if (project?.id) {
|
||||
openTaskForm({ projectId: project.id, task });
|
||||
if (projectId) {
|
||||
openTaskForm({ projectId, task });
|
||||
}
|
||||
};
|
||||
|
||||
const handleDuplicateTask = (task: Task) => {
|
||||
if (project?.id) {
|
||||
openTaskForm({ projectId: project.id, initialTask: task });
|
||||
if (projectId) {
|
||||
openTaskForm({ projectId, initialTask: task });
|
||||
}
|
||||
};
|
||||
const { query: searchQuery, focusInput } = useSearch();
|
||||
@@ -280,17 +286,6 @@ export function ProjectTasks() {
|
||||
}
|
||||
);
|
||||
|
||||
// Full screen
|
||||
|
||||
const fetchProject = useCallback(async () => {
|
||||
try {
|
||||
const result = await projectsApi.getById(projectId!);
|
||||
setProject(result);
|
||||
} catch (err) {
|
||||
setError('Failed to load project');
|
||||
}
|
||||
}, [projectId]);
|
||||
|
||||
const handleClosePanel = useCallback(() => {
|
||||
// setIsPanelOpen(false);
|
||||
// setSelectedTask(null);
|
||||
@@ -458,26 +453,16 @@ export function ProjectTasks() {
|
||||
});
|
||||
// UI will update via WebSocket stream
|
||||
} catch (err) {
|
||||
setError('Failed to update task status');
|
||||
console.error('Failed to update task status:', err);
|
||||
}
|
||||
},
|
||||
[tasksById]
|
||||
);
|
||||
|
||||
// Initialize project when projectId changes
|
||||
useEffect(() => {
|
||||
if (projectId) {
|
||||
fetchProject();
|
||||
}
|
||||
}, [projectId, fetchProject]);
|
||||
// Combine loading states for initial load
|
||||
const isInitialTasksLoad = isLoading && tasks.length === 0;
|
||||
|
||||
// Remove legacy direct-navigation handler; live sync above covers this
|
||||
|
||||
if (isLoading) {
|
||||
return <Loader message={t('loading')} size={32} className="py-8" />;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
if (projectError) {
|
||||
return (
|
||||
<div className="p-4">
|
||||
<Alert>
|
||||
@@ -485,12 +470,18 @@ export function ProjectTasks() {
|
||||
<AlertTriangle size="16" />
|
||||
{t('common:states.error')}
|
||||
</AlertTitle>
|
||||
<AlertDescription>{error}</AlertDescription>
|
||||
<AlertDescription>
|
||||
{projectError.message || 'Failed to load project'}
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (projectLoading && isInitialTasksLoad) {
|
||||
return <Loader message={t('loading')} size={32} className="py-8" />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`min-h-full ${getMainContainerClasses(isPanelOpen, isFullscreen)}`}
|
||||
@@ -548,7 +539,7 @@ export function ProjectTasks() {
|
||||
</div>
|
||||
|
||||
{/* Right Column - Task Details Panel */}
|
||||
{isPanelOpen && (
|
||||
{isPanelOpen && !projectLoading && (
|
||||
<TaskDetailsPanel
|
||||
task={selectedTask}
|
||||
projectHasDevScript={!!project?.dev_script}
|
||||
|
||||
Reference in New Issue
Block a user