diff options
Diffstat (limited to 'makima/frontend/src/routes/directives.tsx')
| -rw-r--r-- | makima/frontend/src/routes/directives.tsx | 324 |
1 files changed, 11 insertions, 313 deletions
diff --git a/makima/frontend/src/routes/directives.tsx b/makima/frontend/src/routes/directives.tsx index 895c86a..f397e54 100644 --- a/makima/frontend/src/routes/directives.tsx +++ b/makima/frontend/src/routes/directives.tsx @@ -1,317 +1,15 @@ -import { useState, useEffect, useCallback } from "react"; -import { useParams, useNavigate } from "react-router"; -import { Masthead } from "../components/Masthead"; -import { DirectiveList } from "../components/directives/DirectiveList"; -import { DirectiveDetail } from "../components/directives/DirectiveDetail"; -import { useDirectives, useDirective } from "../hooks/useDirectives"; -import { useDogs } from "../hooks/useDogs"; -import { useUserSettings } from "../hooks/useUserSettings"; -import { useAuth } from "../contexts/AuthContext"; +// Top-level /directives route — now always renders the document-mode UI. +// +// The legacy tabular UI (DirectiveList + DirectiveDetail) was retired +// when directives.goal was dropped: contracts own the spec text now, +// and the document-mode page is the only surface that knows how to +// edit them. The user-settings `documentModeEnabled` toggle is no +// longer consulted here (kept around in settings for future flag use). +// +// /directives/:id is delegated to DocumentDirectivesPage which reads +// the param itself. import DocumentDirectivesPage from "./document-directives"; -import { getRepositorySuggestions, startDirective, pauseDirective, updateDirective, type RepositoryHistoryEntry, type DirectiveSummary } from "../lib/api"; -/** - * Top-level /directives route. Gates between the legacy tabular UI and the - * Document Mode (POC) UI based on the user's settings flag. - * - * Both code paths support /directives/:id deep links — the param is read by - * each branch independently via useParams. - */ export default function DirectivesPage() { - const { settings, loading: settingsLoading } = useUserSettings(); - - // While settings are loading for the very first time, render nothing inside - // a Masthead-wrapped shell so we don't briefly flash the legacy UI just to - // swap to document mode a moment later. - if (settingsLoading && !settings) { - return ( - <div className="relative z-10 min-h-screen flex flex-col bg-[#0a1628]"> - <Masthead showNav /> - <main className="flex-1 flex items-center justify-center"> - <p className="text-[#7788aa] font-mono text-sm">Loading...</p> - </main> - </div> - ); - } - - if (settings?.documentModeEnabled) { - return <DocumentDirectivesPage />; - } - - return <LegacyDirectivesPage />; -} - -function LegacyDirectivesPage() { - const { isAuthenticated, isAuthConfigured, isLoading: authLoading } = useAuth(); - const navigate = useNavigate(); - const { id: selectedId } = useParams<{ id: string }>(); - const { directives, loading: listLoading, create, remove, refresh: refreshList } = useDirectives(); - const { directive, refresh: refreshDetail, update, start, pause, advance, completeStep, failStep, skipStep, updateGoal, cleanup, pickUpOrders, createPR } = useDirective(selectedId); - const { dogs, loading: dogsLoading, create: createDog, update: updateDog, remove: removeDog, pickUpOrders: pickUpDogOrders } = useDogs(selectedId); - - const [showCreate, setShowCreate] = useState(false); - const [newTitle, setNewTitle] = useState(""); - const [newGoal, setNewGoal] = useState(""); - const [newRepoUrl, setNewRepoUrl] = useState(""); - const [repoSuggestions, setRepoSuggestions] = useState<RepositoryHistoryEntry[]>([]); - const [showRepoSuggestions, setShowRepoSuggestions] = useState(false); - - // Fetch repository suggestions when create form opens - useEffect(() => { - if (showCreate) { - getRepositorySuggestions("remote", undefined, 10) - .then((res) => { - setRepoSuggestions(res.entries); - setShowRepoSuggestions(res.entries.length > 0); - }) - .catch(() => { - setRepoSuggestions([]); - setShowRepoSuggestions(false); - }); - } else { - setRepoSuggestions([]); - setShowRepoSuggestions(false); - } - }, [showCreate]); - - const applyRepoSuggestion = useCallback((suggestion: RepositoryHistoryEntry) => { - if (suggestion.repositoryUrl) { - setNewRepoUrl(suggestion.repositoryUrl); - } - if (!newTitle.trim() && suggestion.name) { - setNewTitle(suggestion.name); - } - setShowRepoSuggestions(false); - }, [newTitle]); - - useEffect(() => { - if (!authLoading && isAuthConfigured && !isAuthenticated) { - navigate("/login"); - } - }, [authLoading, isAuthConfigured, isAuthenticated, navigate]); - - if (authLoading) { - return ( - <div className="relative z-10 min-h-screen flex flex-col bg-[#0a1628]"> - <Masthead showNav /> - <main className="flex-1 flex items-center justify-center"> - <p className="text-[#7788aa] font-mono text-sm">Loading...</p> - </main> - </div> - ); - } - - const handleContextStart = async (directive: DirectiveSummary) => { - try { - await startDirective(directive.id); - await refreshList(); - } catch (e) { - console.error("Failed to start directive:", e); - } - }; - - const handleContextPause = async (directive: DirectiveSummary) => { - try { - await pauseDirective(directive.id); - await refreshList(); - } catch (e) { - console.error("Failed to pause directive:", e); - } - }; - - const handleContextArchive = async (directive: DirectiveSummary) => { - try { - await updateDirective(directive.id, { status: "archived" }); - await refreshList(); - } catch (e) { - console.error("Failed to archive directive:", e); - } - }; - - const handleContextDelete = async (directive: DirectiveSummary) => { - if (!window.confirm("Delete this directive?")) return; - try { - await remove(directive.id); - if (directive.id === selectedId) navigate("/directives"); - } catch (e) { - console.error("Failed to delete:", e); - } - }; - - const handleContextGoToPR = (directive: DirectiveSummary) => { - if (directive.prUrl) window.open(directive.prUrl, "_blank"); - }; - - const handleCreate = async () => { - if (!newTitle.trim() || !newGoal.trim()) return; - try { - const d = await create({ - title: newTitle.trim(), - goal: newGoal.trim(), - repositoryUrl: newRepoUrl.trim() || undefined, - }); - setShowCreate(false); - setNewTitle(""); - setNewGoal(""); - setNewRepoUrl(""); - navigate(`/directives/${d.id}`); - } catch (e) { - console.error("Failed to create directive:", e); - } - }; - - const handleDelete = async () => { - if (!selectedId) return; - if (!window.confirm("Delete this directive?")) return; - try { - await remove(selectedId); - navigate("/directives"); - } catch (e) { - console.error("Failed to delete:", e); - } - }; - - return ( - <div className="relative z-10 min-h-screen flex flex-col bg-[#0a1628]"> - <Masthead showNav /> - <main className="flex-1 flex overflow-hidden" style={{ height: "calc(100vh - 80px)" }}> - {/* Left: List */} - <div className="w-[280px] shrink-0 border-r border-dashed border-[rgba(117,170,252,0.2)] overflow-hidden flex flex-col"> - <DirectiveList - directives={directives} - selectedId={selectedId ?? null} - onSelect={(id) => navigate(`/directives/${id}`)} - onCreate={() => setShowCreate(true)} - onStart={handleContextStart} - onPause={handleContextPause} - onArchive={handleContextArchive} - onDelete={handleContextDelete} - onGoToPR={handleContextGoToPR} - /> - </div> - - {/* Right: Detail or Create */} - <div className="flex-1 overflow-hidden"> - {showCreate ? ( - <div className="p-4 max-w-lg"> - <h2 className="text-[14px] font-mono text-white font-medium mb-4"> - New Directive - </h2> - <div className="flex flex-col gap-3"> - <div> - <label className="text-[10px] font-mono text-[#9bc3ff] uppercase tracking-wide block mb-1"> - Title - </label> - <input - value={newTitle} - onChange={(e) => setNewTitle(e.target.value)} - placeholder="Project title..." - className="w-full bg-[#0a1628] border border-[rgba(117,170,252,0.2)] rounded px-2 py-1.5 text-[12px] font-mono text-white" - /> - </div> - <div> - <label className="text-[10px] font-mono text-[#9bc3ff] uppercase tracking-wide block mb-1"> - Goal - </label> - <textarea - value={newGoal} - onChange={(e) => setNewGoal(e.target.value)} - placeholder="What should this directive accomplish?" - rows={4} - className="w-full bg-[#0a1628] border border-[rgba(117,170,252,0.2)] rounded px-2 py-1.5 text-[12px] font-mono text-white resize-y" - /> - </div> - {showRepoSuggestions && repoSuggestions.length > 0 && ( - <div> - <label className="block font-mono text-xs text-[#8b949e] uppercase mb-1"> - Recent Repositories - </label> - <div className="border border-[rgba(117,170,252,0.2)] bg-[#0a1525] max-h-32 overflow-y-auto"> - {repoSuggestions.map((suggestion) => ( - <button - key={suggestion.id} - type="button" - onClick={() => applyRepoSuggestion(suggestion)} - className="w-full text-left px-3 py-2 font-mono text-xs hover:bg-[rgba(117,170,252,0.1)] border-b border-[rgba(117,170,252,0.1)] last:border-b-0" - > - <div className="flex items-center justify-between"> - <span className="text-[#9bc3ff] truncate">{suggestion.name}</span> - <span className="text-[10px] text-[#556677] ml-2"> - {suggestion.useCount}× - </span> - </div> - <div className="text-[10px] text-[#556677] truncate"> - {suggestion.repositoryUrl} - </div> - </button> - ))} - </div> - </div> - )} - <div> - <label className="text-[10px] font-mono text-[#9bc3ff] uppercase tracking-wide block mb-1"> - Repository URL (optional) - </label> - <input - value={newRepoUrl} - onChange={(e) => setNewRepoUrl(e.target.value)} - placeholder="https://github.com/..." - className="w-full bg-[#0a1628] border border-[rgba(117,170,252,0.2)] rounded px-2 py-1.5 text-[12px] font-mono text-white" - /> - </div> - <div className="flex gap-2"> - <button - type="button" - onClick={handleCreate} - disabled={!newTitle.trim() || !newGoal.trim()} - className="text-[11px] font-mono text-emerald-400 hover:text-emerald-300 border border-emerald-800 rounded px-3 py-1 disabled:opacity-50" - > - Create - </button> - <button - type="button" - onClick={() => setShowCreate(false)} - className="text-[11px] font-mono text-[#7788aa] hover:text-white border border-[#2a3a5a] rounded px-3 py-1" - > - Cancel - </button> - </div> - </div> - </div> - ) : selectedId && directive ? ( - <DirectiveDetail - directive={directive} - onStart={start} - onPause={pause} - onAdvance={advance} - onCompleteStep={completeStep} - onFailStep={failStep} - onSkipStep={skipStep} - onUpdateGoal={updateGoal} - onUpdate={update} - onDelete={handleDelete} - onRefresh={refreshDetail} - onCleanup={cleanup} - onPickUpOrders={pickUpOrders} - onCreatePR={createPR} - dogs={dogs} - dogsLoading={dogsLoading} - onCreateDog={createDog} - onUpdateDog={updateDog} - onDeleteDog={removeDog} - onPickUpDogOrders={pickUpDogOrders} - /> - ) : ( - <div className="flex-1 flex items-center justify-center h-full"> - <p className="text-[#556677] font-mono text-[12px]"> - {listLoading - ? "Loading..." - : "Select a directive or create a new one"} - </p> - </div> - )} - </div> - </main> - </div> - ); + return <DocumentDirectivesPage />; } |
