From a0629724a4ebe8589d48696cb72b8196f1223aac Mon Sep 17 00:00:00 2001 From: soryu Date: Thu, 4 Jun 2026 23:24:15 +0100 Subject: WIP: heartbeat checkpoint --- makima/frontend/src/routes/document-directives.tsx | 96 ++++++++++++++++------ 1 file changed, 69 insertions(+), 27 deletions(-) diff --git a/makima/frontend/src/routes/document-directives.tsx b/makima/frontend/src/routes/document-directives.tsx index 801e397..49b9fd0 100644 --- a/makima/frontend/src/routes/document-directives.tsx +++ b/makima/frontend/src/routes/document-directives.tsx @@ -13,6 +13,7 @@ import { type Task, type DirectiveContractTasksResponse as ContractTasksResponse, type DirectiveContractMergeMode as ContractMergeMode, + type RepositoryHistoryEntry, listDirectiveContracts as listContracts, createDirectiveContract as createContract, getDirectiveContract as getContract, @@ -34,6 +35,7 @@ import { pickUpOrders, stopTask, skipDirectiveStep, + getRepositorySuggestions, } from "../lib/api"; import { SidebarContextMenu, type ContextMenuItem } from "../components/SidebarContextMenu"; import { TaskPage } from "../components/directives/TaskPage"; @@ -1678,10 +1680,9 @@ export default function DocumentDirectivesPage() { const [newEphemeralFor, setNewEphemeralFor] = useState(null); const handleSubmitNewContract = useCallback( - async (title: string, body: string, repositoryUrl: string) => { + async (title: string, repositoryUrl: string) => { const d = await createDirective({ title, - contractBody: body, repositoryUrl: repositoryUrl.length > 0 ? repositoryUrl : undefined, }); setShowNewContract(false); @@ -1970,21 +1971,24 @@ export default function DocumentDirectivesPage() { /** * Modal for creating a new directive (= "contract" in the doc-mode UI). - * Title + goal are required; repository_url is optional. On submit calls - * useDirectives.create and navigates the user into the new directive. + * Title is required; repository_url and the contract body are optional + * and can be edited after creation in the document editor. On submit + * calls useDirectives.create and navigates the user into the new + * directive. */ function NewContractModal({ onClose, onSubmit, }: { onClose: () => void; - onSubmit: (title: string, goal: string, repositoryUrl: string) => Promise; + onSubmit: (title: string, repositoryUrl: string) => Promise; }) { const [title, setTitle] = useState(""); - const [goal, setGoal] = useState(""); const [repo, setRepo] = useState(""); const [submitting, setSubmitting] = useState(false); const [err, setErr] = useState(null); + const [repoSuggestions, setRepoSuggestions] = useState([]); + const [showRepoSuggestions, setShowRepoSuggestions] = useState(false); const titleRef = useRef(null); useEffect(() => { @@ -1996,15 +2000,40 @@ function NewContractModal({ return () => document.removeEventListener("keydown", onKey); }, [onClose]); + // Load recent remote-repository suggestions on mount. Swallow errors + // — the suggestions list is purely a convenience and should never + // block contract creation. + useEffect(() => { + let cancelled = false; + getRepositorySuggestions("remote", undefined, 10) + .then((res) => { + if (cancelled) return; + setRepoSuggestions(res.entries); + setShowRepoSuggestions(res.entries.length > 0); + }) + .catch(() => { + if (cancelled) return; + setRepoSuggestions([]); + setShowRepoSuggestions(false); + }); + return () => { + cancelled = true; + }; + }, []); + + const applyRepoSuggestion = useCallback((suggestion: RepositoryHistoryEntry) => { + setRepo(suggestion.repositoryUrl ?? ""); + setShowRepoSuggestions(false); + }, []); + const submit = async (e: React.FormEvent) => { e.preventDefault(); const t = title.trim(); - const g = goal.trim(); - if (!t || !g || submitting) return; + if (!t || submitting) return; setSubmitting(true); setErr(null); try { - await onSubmit(t, g, repo.trim()); + await onSubmit(t, repo.trim()); } catch (caught) { setErr(caught instanceof Error ? caught.message : String(caught)); } finally { @@ -2041,23 +2070,6 @@ function NewContractModal({ className="w-full bg-transparent border border-[rgba(117,170,252,0.2)] focus:border-[#75aafc] outline-none px-3 py-2 text-[13px] text-[#dbe7ff] placeholder-[#445566]" /> -
- -