summaryrefslogtreecommitdiff
path: root/makima/frontend/src/routes/directives.tsx
diff options
context:
space:
mode:
authorsoryu <soryu@soryu.co>2026-05-08 16:33:36 +0100
committersoryu <soryu@soryu.co>2026-05-08 16:33:36 +0100
commit7af816032fbc54d5e0a8e94d4a000f307cd3b370 (patch)
tree50b6aad1aa47e56b61f0700e224028bb7578cb91 /makima/frontend/src/routes/directives.tsx
parente4f1622a0f0ac74707cc1c9810e0b99e948d1319 (diff)
downloadsoryu-drop-directive-goal.tar.gz
soryu-drop-directive-goal.zip
feat(directives): drop directives.goal — orchestration reads contract bodydrop-directive-goal
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) <noreply@anthropic.com>
Diffstat (limited to 'makima/frontend/src/routes/directives.tsx')
-rw-r--r--makima/frontend/src/routes/directives.tsx324
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 />;
}