From dce7f50e503dc374aaf879df33e725af16c4cc78 Mon Sep 17 00:00:00 2001 From: soryu Date: Fri, 8 May 2026 16:34:11 +0100 Subject: feat(directives): drop directives.goal — orchestration reads contract body (#132) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Hard cut. The unified contracts surface owns spec text now; the directive itself is just a folder. The orchestrator daemon reads the active contract's body when it spawns, replans, or runs completion. Schema (migration 20260510000000): - DROP TABLE directive_goal_history - ALTER TABLE directives DROP COLUMN goal - ALTER TABLE directives DROP COLUMN goal_updated_at New repo helper: - get_active_contract_body(directive_id) — picks the active|queued|draft contract (in that order), most-recent first. Backend cuts: - Directive / DirectiveSummary / CreateDirectiveRequest / UpdateDirectiveRequest lose goal & goalUpdatedAt. - CreateDirectiveRequest gains optional `contractBody` — when provided, create_directive_for_owner auto-creates a first contract with that body in the same transaction. - Removed: update_directive_goal, update_directive_goal_keep_orchestrator, save_directive_goal_history, get_directive_goal_history, DirectiveGoalHistory model, UpdateGoalRequest. - Removed handlers::directives::update_goal + the /directives/{id}/goal route. - orchestration::directive::build_planning_prompt / build_completion_prompt / build_order_pickup_prompt now take a `contract_body: &str` instead of `goal_history`. classify_goal_change + try_interrupt_planner_with_goal_edit + GoalChangeKind + GoalEditInterruptResult removed (they were only useful for the small-vs-large goal-edit interrupt cycle). CLI: - `makima directive update-goal` removed (UpdateGoalArgs deleted, Commands enum trimmed, ApiClient::directive_update_goal + UpdateGoalRequest deleted). Frontend: - Directive / DirectiveSummary / CreateDirectiveRequest types lose goal & goalUpdatedAt; CreateDirectiveRequest gains `contractBody`. - useDirective drops updateGoal helper. - api.ts updateDirectiveGoal removed. - Legacy DirectiveList + DirectiveDetail components deleted; the /directives route now always renders the document-mode page. The user-settings documentModeEnabled flag is no longer consulted at the route level. - NewContractModal passes body via contractBody. Co-authored-by: Claude Opus 4.7 (1M context) --- makima/frontend/src/routes/directives.tsx | 324 +-------------------- makima/frontend/src/routes/document-directives.tsx | 4 +- 2 files changed, 13 insertions(+), 315 deletions(-) (limited to 'makima/frontend/src/routes') 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 ( -
- -
-

Loading...

-
-
- ); - } - - if (settings?.documentModeEnabled) { - return ; - } - - return ; -} - -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([]); - 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 ( -
- -
-

Loading...

-
-
- ); - } - - 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 ( -
- -
- {/* Left: List */} -
- navigate(`/directives/${id}`)} - onCreate={() => setShowCreate(true)} - onStart={handleContextStart} - onPause={handleContextPause} - onArchive={handleContextArchive} - onDelete={handleContextDelete} - onGoToPR={handleContextGoToPR} - /> -
- - {/* Right: Detail or Create */} -
- {showCreate ? ( -
-

- New Directive -

-
-
- - 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" - /> -
-
- -