diff options
| author | soryu <soryu@soryu.co> | 2026-06-04 23:24:15 +0100 |
|---|---|---|
| committer | soryu <soryu@soryu.co> | 2026-06-04 23:24:15 +0100 |
| commit | a0629724a4ebe8589d48696cb72b8196f1223aac (patch) | |
| tree | 87842b0daf3568b4e0be43c2254c5cd41fabcd25 /makima/frontend | |
| parent | f13352a3a5860477957d16b7ce00169d385f56da (diff) | |
| download | soryu-a0629724a4ebe8589d48696cb72b8196f1223aac.tar.gz soryu-a0629724a4ebe8589d48696cb72b8196f1223aac.zip | |
WIP: heartbeat checkpointmakima/soryu---makima--remove-goal-field-and-add-reposito-c0e50717makima/directive-soryu---makima-796b95c3
Diffstat (limited to 'makima/frontend')
| -rw-r--r-- | makima/frontend/src/routes/document-directives.tsx | 96 |
1 files 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<DirectiveSummary | null>(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<void>; + onSubmit: (title: string, repositoryUrl: string) => Promise<void>; }) { const [title, setTitle] = useState(""); - const [goal, setGoal] = useState(""); const [repo, setRepo] = useState(""); const [submitting, setSubmitting] = useState(false); const [err, setErr] = useState<string | null>(null); + const [repoSuggestions, setRepoSuggestions] = useState<RepositoryHistoryEntry[]>([]); + const [showRepoSuggestions, setShowRepoSuggestions] = useState(false); const titleRef = useRef<HTMLInputElement>(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 { @@ -2043,32 +2072,45 @@ function NewContractModal({ </div> <div className="space-y-1"> <label className="text-[10px] font-mono text-[#7788aa] uppercase tracking-wide"> - Goal - </label> - <textarea - value={goal} - onChange={(e) => setGoal(e.target.value)} - onKeyDown={(e) => { - if ((e.metaKey || e.ctrlKey) && e.key === "Enter") { - void submit(e as unknown as React.FormEvent); - } - }} - rows={4} - placeholder="Describe what the contract should achieve" - 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] resize-none" - /> - </div> - <div className="space-y-1"> - <label className="text-[10px] font-mono text-[#7788aa] uppercase tracking-wide"> Repository URL (optional) </label> <input type="text" value={repo} onChange={(e) => setRepo(e.target.value)} + onFocus={() => { + if (repoSuggestions.length > 0) setShowRepoSuggestions(true); + }} placeholder="e.g. https://github.com/owner/repo" 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]" /> + {showRepoSuggestions && repoSuggestions.length > 0 && ( + <div className="space-y-1 pt-1"> + <label className="block text-[10px] font-mono uppercase tracking-wide text-[#7788aa]"> + 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> {err && ( <p className="text-[11px] font-mono text-red-400">{err}</p> @@ -2084,7 +2126,7 @@ function NewContractModal({ </button> <button type="submit" - disabled={!title.trim() || !goal.trim() || submitting} + disabled={!title.trim() || submitting} className="px-3 py-1.5 text-[11px] font-mono uppercase tracking-wide text-emerald-300 border border-emerald-700/60 hover:border-emerald-400 disabled:opacity-40 disabled:cursor-not-allowed" > {submitting ? "Creating…" : "Create contract"} |
