summaryrefslogtreecommitdiff
path: root/makima/frontend
diff options
context:
space:
mode:
Diffstat (limited to 'makima/frontend')
-rw-r--r--makima/frontend/src/components/chains/ChainEditor.tsx207
-rw-r--r--makima/frontend/src/lib/api.ts44
-rw-r--r--makima/frontend/tsconfig.tsbuildinfo2
3 files changed, 216 insertions, 37 deletions
diff --git a/makima/frontend/src/components/chains/ChainEditor.tsx b/makima/frontend/src/components/chains/ChainEditor.tsx
index 9028c3e..49e585c 100644
--- a/makima/frontend/src/components/chains/ChainEditor.tsx
+++ b/makima/frontend/src/components/chains/ChainEditor.tsx
@@ -54,6 +54,7 @@ export function ChainEditor({
const [isStarting, setIsStarting] = useState(false);
const [isStopping, setIsStopping] = useState(false);
const [error, setError] = useState<string | null>(null);
+ const [withSupervisor, setWithSupervisor] = useState(false);
// Load definitions when chain changes
useEffect(() => {
@@ -131,7 +132,22 @@ export function ChainEditor({
[onContractClick, showDefinitions]
);
- const getStatusColor = (status: string) => {
+ const getStatusColor = (status: string, isCheckpoint = false) => {
+ // Checkpoint contracts use purple/violet colors
+ if (isCheckpoint) {
+ switch (status) {
+ case "active":
+ return { bg: "#a78bfa", border: "#8b5cf6", text: "#5b21b6" };
+ case "completed":
+ return { bg: "#818cf8", border: "#6366f1", text: "#3730a3" };
+ case "pending":
+ return { bg: "#c4b5fd", border: "#a78bfa", text: "#6d28d9" };
+ case "failed":
+ return { bg: "#ef4444", border: "#dc2626", text: "#991b1b" };
+ default:
+ return { bg: "#a78bfa", border: "#8b5cf6", text: "#5b21b6" };
+ }
+ }
switch (status) {
case "active":
return { bg: "#4ade80", border: "#22c55e", text: "#166534" };
@@ -158,14 +174,14 @@ export function ChainEditor({
setIsStarting(true);
setError(null);
try {
- await startChain(chain.id);
+ await startChain(chain.id, { withSupervisor });
onRefresh();
} catch (err) {
setError(err instanceof Error ? err.message : "Failed to start chain");
} finally {
setIsStarting(false);
}
- }, [chain.id, onRefresh]);
+ }, [chain.id, onRefresh, withSupervisor]);
const handleStopChain = useCallback(async () => {
if (!confirm("Are you sure you want to stop this chain?")) return;
@@ -245,13 +261,26 @@ export function ChainEditor({
</span>
{/* Chain control buttons */}
{chain.status === "pending" && definitions.length > 0 && (
- <button
- onClick={handleStartChain}
- disabled={isStarting}
- className="px-3 py-1 font-mono text-xs text-[#dbe7ff] bg-green-600 hover:bg-green-700 border border-green-500 transition-colors disabled:opacity-50"
- >
- {isStarting ? "Starting..." : "Start Chain"}
- </button>
+ <>
+ <label className="flex items-center gap-1 font-mono text-[10px] text-[#9bc3ff] cursor-pointer">
+ <input
+ type="checkbox"
+ checked={withSupervisor}
+ onChange={(e) => setWithSupervisor(e.target.checked)}
+ className="w-3 h-3 accent-[#75aafc]"
+ />
+ <span title="Create a Claude supervisor task to monitor chain progress">
+ Supervisor
+ </span>
+ </label>
+ <button
+ onClick={handleStartChain}
+ disabled={isStarting}
+ className="px-3 py-1 font-mono text-xs text-[#dbe7ff] bg-green-600 hover:bg-green-700 border border-green-500 transition-colors disabled:opacity-50"
+ >
+ {isStarting ? "Starting..." : "Start Chain"}
+ </button>
+ </>
)}
{chain.status === "active" && (
<button
@@ -381,10 +410,11 @@ export function ChainEditor({
const pos = nodePositions.get(node.id);
if (!pos) return null;
+ const isCheckpoint = node.contractType === "checkpoint";
const status = node.isInstantiated
? node.contractStatus || "pending"
: "pending";
- const colors = getStatusColor(status);
+ const colors = getStatusColor(status, isCheckpoint);
const isSelected = selectedNode === node.id;
const isHovered = hoveredNode === node.id;
@@ -396,7 +426,9 @@ export function ChainEditor({
onMouseLeave={() => setHoveredNode(null)}
className={`absolute cursor-pointer transition-all duration-150 ${
isSelected
- ? "ring-2 ring-[#75aafc] ring-offset-2 ring-offset-[#050d18]"
+ ? isCheckpoint
+ ? "ring-2 ring-[#a78bfa] ring-offset-2 ring-offset-[#050d18]"
+ : "ring-2 ring-[#75aafc] ring-offset-2 ring-offset-[#050d18]"
: ""
}`}
style={{
@@ -408,9 +440,15 @@ export function ChainEditor({
}}
>
<div
- className="w-full h-full rounded-lg border-2 bg-[#0a1628] overflow-hidden"
+ className={`w-full h-full rounded-lg border-2 overflow-hidden ${
+ isCheckpoint ? "bg-[#0f0a1e]" : "bg-[#0a1628]"
+ }`}
style={{
- borderColor: isSelected ? "#75aafc" : colors.border,
+ borderColor: isSelected
+ ? isCheckpoint
+ ? "#a78bfa"
+ : "#75aafc"
+ : colors.border,
borderStyle: node.isInstantiated ? "solid" : "dashed",
}}
>
@@ -425,7 +463,11 @@ export function ChainEditor({
<span className="font-mono text-xs text-[#dbe7ff] truncate flex-1">
{node.name}
</span>
- <ChainIcon className="w-4 h-4 text-[#75aafc] opacity-50 flex-shrink-0 ml-1" />
+ {isCheckpoint ? (
+ <CheckIcon className="w-4 h-4 text-[#a78bfa] opacity-70 flex-shrink-0 ml-1" />
+ ) : (
+ <ChainIcon className="w-4 h-4 text-[#75aafc] opacity-50 flex-shrink-0 ml-1" />
+ )}
</div>
<div className="flex items-center justify-between">
<span
@@ -435,7 +477,7 @@ export function ChainEditor({
backgroundColor: `${colors.bg}20`,
}}
>
- {node.isInstantiated ? status : "definition"}
+ {node.isInstantiated ? status : isCheckpoint ? "checkpoint" : "definition"}
</span>
<span className="font-mono text-[10px] text-[#8b949e]">
{node.contractType}
@@ -833,16 +875,33 @@ function AddDefinitionModal({
const [contractType, setContractType] = useState("simple");
const [initialPhase, setInitialPhase] = useState("plan");
const [dependsOn, setDependsOn] = useState<string[]>([]);
+ // Checkpoint validation options
+ const [checkDeliverables, setCheckDeliverables] = useState(true);
+ const [runTests, setRunTests] = useState(false);
+ const [checkContent, setCheckContent] = useState("");
+ const [onFailure, setOnFailure] = useState<"block" | "retry" | "warn">("block");
+
+ const isCheckpoint = contractType === "checkpoint";
const handleSubmit = () => {
if (!name.trim()) return;
- onSubmit({
+ const req: AddContractDefinitionRequest = {
name: name.trim(),
description: description.trim() || undefined,
contractType,
- initialPhase,
+ initialPhase: isCheckpoint ? "execute" : initialPhase, // Checkpoints always start in execute
dependsOn: dependsOn.length > 0 ? dependsOn : undefined,
- });
+ };
+ // Add validation config for checkpoint contracts
+ if (isCheckpoint) {
+ req.validation = {
+ checkDeliverables,
+ runTests,
+ checkContent: checkContent.trim() || undefined,
+ onFailure,
+ };
+ }
+ onSubmit(req);
};
const toggleDependency = (depName: string) => {
@@ -901,24 +960,87 @@ function AddDefinitionModal({
<option value="simple">Simple</option>
<option value="specification">Specification</option>
<option value="execute">Execute</option>
+ <option value="checkpoint">Checkpoint (Validation)</option>
</select>
+ {isCheckpoint && (
+ <p className="mt-1 font-mono text-[10px] text-[#a78bfa]">
+ Checkpoint contracts validate outputs before allowing downstream contracts to proceed.
+ </p>
+ )}
</div>
- {/* Initial Phase */}
- <div>
- <label className="block font-mono text-xs text-[#8b949e] uppercase mb-1">
- Initial Phase
- </label>
- <select
- value={initialPhase}
- onChange={(e) => setInitialPhase(e.target.value)}
- className="w-full px-3 py-2 bg-[#0d1b2d] border border-[#3f6fb3] text-[#dbe7ff] font-mono text-sm focus:outline-none focus:border-[#75aafc]"
- >
- <option value="plan">Plan</option>
- <option value="execute">Execute</option>
- <option value="review">Review</option>
- </select>
- </div>
+ {/* Initial Phase - hidden for checkpoints */}
+ {!isCheckpoint && (
+ <div>
+ <label className="block font-mono text-xs text-[#8b949e] uppercase mb-1">
+ Initial Phase
+ </label>
+ <select
+ value={initialPhase}
+ onChange={(e) => setInitialPhase(e.target.value)}
+ className="w-full px-3 py-2 bg-[#0d1b2d] border border-[#3f6fb3] text-[#dbe7ff] font-mono text-sm focus:outline-none focus:border-[#75aafc]"
+ >
+ <option value="plan">Plan</option>
+ <option value="execute">Execute</option>
+ <option value="review">Review</option>
+ </select>
+ </div>
+ )}
+
+ {/* Checkpoint Validation Options */}
+ {isCheckpoint && (
+ <div className="p-3 bg-[#0f0a1e] border border-[#6366f1]/30 space-y-3">
+ <h4 className="font-mono text-xs text-[#a78bfa] uppercase">Validation Options</h4>
+
+ <label className="flex items-center gap-2 cursor-pointer">
+ <input
+ type="checkbox"
+ checked={checkDeliverables}
+ onChange={(e) => setCheckDeliverables(e.target.checked)}
+ className="accent-[#a78bfa]"
+ />
+ <span className="font-mono text-xs text-[#dbe7ff]">Check required deliverables exist</span>
+ </label>
+
+ <label className="flex items-center gap-2 cursor-pointer">
+ <input
+ type="checkbox"
+ checked={runTests}
+ onChange={(e) => setRunTests(e.target.checked)}
+ className="accent-[#a78bfa]"
+ />
+ <span className="font-mono text-xs text-[#dbe7ff]">Run test suite</span>
+ </label>
+
+ <div>
+ <label className="block font-mono text-[10px] text-[#8b949e] uppercase mb-1">
+ Custom Validation Instructions (optional)
+ </label>
+ <textarea
+ value={checkContent}
+ onChange={(e) => setCheckContent(e.target.value)}
+ placeholder="Additional criteria for Claude to check..."
+ rows={3}
+ className="w-full px-2 py-1.5 bg-[#0d1b2d] border border-[#6366f1]/50 text-[#dbe7ff] font-mono text-xs focus:outline-none focus:border-[#a78bfa] resize-none"
+ />
+ </div>
+
+ <div>
+ <label className="block font-mono text-[10px] text-[#8b949e] uppercase mb-1">
+ On Failure
+ </label>
+ <select
+ value={onFailure}
+ onChange={(e) => setOnFailure(e.target.value as "block" | "retry" | "warn")}
+ className="w-full px-2 py-1.5 bg-[#0d1b2d] border border-[#6366f1]/50 text-[#dbe7ff] font-mono text-xs focus:outline-none focus:border-[#a78bfa]"
+ >
+ <option value="block">Block - Stop chain until fixed</option>
+ <option value="retry">Retry - Retry upstream contracts</option>
+ <option value="warn">Warn - Log warning but continue</option>
+ </select>
+ </div>
+ </div>
+ )}
{/* Dependencies */}
{existingNames.length > 0 && (
@@ -980,3 +1102,20 @@ function ChainIcon({ className }: { className?: string }) {
</svg>
);
}
+
+function CheckIcon({ className }: { className?: string }) {
+ return (
+ <svg
+ className={className}
+ viewBox="0 0 24 24"
+ fill="none"
+ stroke="currentColor"
+ strokeWidth="2"
+ strokeLinecap="round"
+ strokeLinejoin="round"
+ >
+ <path d="M22 11.08V12a10 10 0 1 1-5.93-9.14" />
+ <polyline points="22 4 12 14.01 9 11.01" />
+ </svg>
+ );
+}
diff --git a/makima/frontend/src/lib/api.ts b/makima/frontend/src/lib/api.ts
index e5cf1d8..a08cba7 100644
--- a/makima/frontend/src/lib/api.ts
+++ b/makima/frontend/src/lib/api.ts
@@ -3255,6 +3255,20 @@ export interface ChainDeliverableDefinition {
priority?: string;
}
+/** Validation configuration for checkpoint contracts */
+export interface CheckpointValidation {
+ /** Check that all required deliverables from upstream contracts exist */
+ checkDeliverables?: boolean;
+ /** Run tests in the repository */
+ runTests?: boolean;
+ /** Custom validation instructions for Claude */
+ checkContent?: string;
+ /** Action on failure: "block", "retry", "warn" */
+ onFailure?: "block" | "retry" | "warn";
+ /** Max retry attempts for upstream contracts */
+ maxRetries?: number;
+}
+
/** Contract definition stored in chain (before actual contract is created) */
export interface ChainContractDefinition {
id: string;
@@ -3266,6 +3280,8 @@ export interface ChainContractDefinition {
dependsOnNames: string[];
tasks: ChainTaskDefinition[] | null;
deliverables: ChainDeliverableDefinition[] | null;
+ /** Validation config for checkpoint contracts */
+ validation: CheckpointValidation | null;
editorX: number | null;
editorY: number | null;
orderIndex: number;
@@ -3281,6 +3297,8 @@ export interface AddContractDefinitionRequest {
dependsOn?: string[];
tasks?: ChainTaskDefinition[];
deliverables?: ChainDeliverableDefinition[];
+ /** Validation config (for checkpoint contracts) */
+ validation?: CheckpointValidation;
editorX?: number;
editorY?: number;
orderIndex?: number;
@@ -3295,6 +3313,8 @@ export interface UpdateContractDefinitionRequest {
dependsOn?: string[];
tasks?: ChainTaskDefinition[];
deliverables?: ChainDeliverableDefinition[];
+ /** Validation config (for checkpoint contracts) */
+ validation?: CheckpointValidation;
editorX?: number;
editorY?: number;
orderIndex?: number;
@@ -3402,10 +3422,30 @@ export async function getChainDefinitionGraph(
return res.json();
}
-/** Start a chain (creates root contracts and spawns supervisor) */
-export async function startChain(chainId: string): Promise<StartChainResponse> {
+/** Options for starting a chain */
+export interface StartChainOptions {
+ /** Whether to create a supervisor task that monitors chain progress */
+ withSupervisor?: boolean;
+ /** Repository URL for the supervisor task to work with */
+ repositoryUrl?: string;
+}
+
+/** Start a chain (creates root contracts and optionally spawns supervisor) */
+export async function startChain(
+ chainId: string,
+ options?: StartChainOptions
+): Promise<StartChainResponse> {
+ const body = options
+ ? JSON.stringify({
+ withSupervisor: options.withSupervisor ?? false,
+ repositoryUrl: options.repositoryUrl,
+ })
+ : undefined;
+
const res = await authFetch(`${API_BASE}/api/v1/chains/${chainId}/start`, {
method: "POST",
+ headers: body ? { "Content-Type": "application/json" } : undefined,
+ body,
});
if (!res.ok) {
const error = await res.json().catch(() => ({ message: res.statusText }));
diff --git a/makima/frontend/tsconfig.tsbuildinfo b/makima/frontend/tsconfig.tsbuildinfo
index 26dc69a..425babe 100644
--- a/makima/frontend/tsconfig.tsbuildinfo
+++ b/makima/frontend/tsconfig.tsbuildinfo
@@ -1 +1 @@
-{"root":["./src/main.tsx","./src/vite-env.d.ts","./src/components/gridoverlay.tsx","./src/components/japanesehovertext.tsx","./src/components/logo.tsx","./src/components/masthead.tsx","./src/components/navstrip.tsx","./src/components/phaseconfirmationnotification.tsx","./src/components/protectedroute.tsx","./src/components/rewritelink.tsx","./src/components/simplemarkdown.tsx","./src/components/supervisorquestionnotification.tsx","./src/components/chains/chaineditor.tsx","./src/components/chains/chainlist.tsx","./src/components/charts/chartrenderer.tsx","./src/components/contracts/commandmodepanel.tsx","./src/components/contracts/contractcliinput.tsx","./src/components/contracts/contractcontextmenu.tsx","./src/components/contracts/contractdetail.tsx","./src/components/contracts/contractlist.tsx","./src/components/contracts/phasebadge.tsx","./src/components/contracts/phaseconfirmationmodal.tsx","./src/components/contracts/phasedeliverablespanel.tsx","./src/components/contracts/phasehint.tsx","./src/components/contracts/phaseprogressbar.tsx","./src/components/contracts/quickactionbuttons.tsx","./src/components/contracts/repositorypanel.tsx","./src/components/contracts/taskderivationpreview.tsx","./src/components/files/bodyrenderer.tsx","./src/components/files/cliinput.tsx","./src/components/files/conflictnotification.tsx","./src/components/files/elementcontextmenu.tsx","./src/components/files/filedetail.tsx","./src/components/files/filelist.tsx","./src/components/files/reposyncindicator.tsx","./src/components/files/updatenotification.tsx","./src/components/files/versionhistorydropdown.tsx","./src/components/history/checkpointcard.tsx","./src/components/history/checkpointlist.tsx","./src/components/history/conversationmessage.tsx","./src/components/history/conversationview.tsx","./src/components/history/historyfilters.tsx","./src/components/history/resumecontrols.tsx","./src/components/history/timelineeventcard.tsx","./src/components/history/timelinelist.tsx","./src/components/history/index.ts","./src/components/listen/contractpickermodal.tsx","./src/components/listen/controlpanel.tsx","./src/components/listen/speakerpanel.tsx","./src/components/listen/transcriptanalysispanel.tsx","./src/components/listen/transcriptpanel.tsx","./src/components/mesh/branchtaskmodal.tsx","./src/components/mesh/contractcompletequestion.tsx","./src/components/mesh/directoryinput.tsx","./src/components/mesh/gitactionspanel.tsx","./src/components/mesh/inlinesubtaskeditor.tsx","./src/components/mesh/mergeconflictresolver.tsx","./src/components/mesh/overlaydiffviewer.tsx","./src/components/mesh/prpreview.tsx","./src/components/mesh/patcheslistpanel.tsx","./src/components/mesh/subtasktree.tsx","./src/components/mesh/taskdetail.tsx","./src/components/mesh/tasklist.tsx","./src/components/mesh/taskoutput.tsx","./src/components/mesh/tasktree.tsx","./src/components/mesh/unifiedmeshchatinput.tsx","./src/components/mesh/worktreefilespanel.tsx","./src/components/workflow/phasecolumn.tsx","./src/components/workflow/workflowboard.tsx","./src/components/workflow/workflowcontractcard.tsx","./src/contexts/authcontext.tsx","./src/contexts/supervisorquestionscontext.tsx","./src/hooks/usechains.ts","./src/hooks/usecontracts.ts","./src/hooks/usefilesubscription.ts","./src/hooks/usefiles.ts","./src/hooks/usemeshchathistory.ts","./src/hooks/usemicrophone.ts","./src/hooks/usespeakwebsocket.ts","./src/hooks/usetasksubscription.ts","./src/hooks/usetasks.ts","./src/hooks/usetextscramble.ts","./src/hooks/useversionhistory.ts","./src/hooks/usewebsocket.ts","./src/lib/api.ts","./src/lib/listenapi.ts","./src/lib/markdown.ts","./src/lib/supabase.ts","./src/routes/_index.tsx","./src/routes/chains.tsx","./src/routes/contract-file.tsx","./src/routes/contracts.tsx","./src/routes/files.tsx","./src/routes/history.tsx","./src/routes/listen.tsx","./src/routes/login.tsx","./src/routes/mesh.tsx","./src/routes/settings.tsx","./src/routes/speak.tsx","./src/routes/workflow.tsx","./src/types/messages.ts"],"version":"5.9.3"} \ No newline at end of file
+{"root":["./src/main.tsx","./src/vite-env.d.ts","./src/components/gridoverlay.tsx","./src/components/japanesehovertext.tsx","./src/components/logo.tsx","./src/components/masthead.tsx","./src/components/navstrip.tsx","./src/components/phaseconfirmationnotification.tsx","./src/components/protectedroute.tsx","./src/components/rewritelink.tsx","./src/components/simplemarkdown.tsx","./src/components/supervisorquestionnotification.tsx","./src/components/chains/chaineditor.tsx","./src/components/chains/chainlist.tsx","./src/components/charts/chartrenderer.tsx","./src/components/contracts/commandmodepanel.tsx","./src/components/contracts/contractcliinput.tsx","./src/components/contracts/contractcontextmenu.tsx","./src/components/contracts/contractdetail.tsx","./src/components/contracts/contractlist.tsx","./src/components/contracts/phasebadge.tsx","./src/components/contracts/phaseconfirmationmodal.tsx","./src/components/contracts/phasedeliverablespanel.tsx","./src/components/contracts/phasehint.tsx","./src/components/contracts/phaseprogressbar.tsx","./src/components/contracts/quickactionbuttons.tsx","./src/components/contracts/repositorypanel.tsx","./src/components/contracts/taskderivationpreview.tsx","./src/components/files/bodyrenderer.tsx","./src/components/files/cliinput.tsx","./src/components/files/conflictnotification.tsx","./src/components/files/elementcontextmenu.tsx","./src/components/files/filedetail.tsx","./src/components/files/filelist.tsx","./src/components/files/reposyncindicator.tsx","./src/components/files/updatenotification.tsx","./src/components/files/versionhistorydropdown.tsx","./src/components/history/checkpointcard.tsx","./src/components/history/checkpointlist.tsx","./src/components/history/conversationmessage.tsx","./src/components/history/conversationview.tsx","./src/components/history/historyfilters.tsx","./src/components/history/resumecontrols.tsx","./src/components/history/timelineeventcard.tsx","./src/components/history/timelinelist.tsx","./src/components/history/index.ts","./src/components/listen/contractpickermodal.tsx","./src/components/listen/controlpanel.tsx","./src/components/listen/discusscontractmodal.tsx","./src/components/listen/speakerpanel.tsx","./src/components/listen/transcriptanalysispanel.tsx","./src/components/listen/transcriptpanel.tsx","./src/components/mesh/branchtaskmodal.tsx","./src/components/mesh/contractcompletequestion.tsx","./src/components/mesh/directoryinput.tsx","./src/components/mesh/gitactionspanel.tsx","./src/components/mesh/inlinesubtaskeditor.tsx","./src/components/mesh/mergeconflictresolver.tsx","./src/components/mesh/overlaydiffviewer.tsx","./src/components/mesh/prpreview.tsx","./src/components/mesh/patcheslistpanel.tsx","./src/components/mesh/subtasktree.tsx","./src/components/mesh/taskdetail.tsx","./src/components/mesh/tasklist.tsx","./src/components/mesh/taskoutput.tsx","./src/components/mesh/tasktree.tsx","./src/components/mesh/unifiedmeshchatinput.tsx","./src/components/mesh/worktreefilespanel.tsx","./src/components/workflow/phasecolumn.tsx","./src/components/workflow/workflowboard.tsx","./src/components/workflow/workflowcontractcard.tsx","./src/contexts/authcontext.tsx","./src/contexts/supervisorquestionscontext.tsx","./src/hooks/usechains.ts","./src/hooks/usecontracts.ts","./src/hooks/usefilesubscription.ts","./src/hooks/usefiles.ts","./src/hooks/usemeshchathistory.ts","./src/hooks/usemicrophone.ts","./src/hooks/usespeakwebsocket.ts","./src/hooks/usetasksubscription.ts","./src/hooks/usetasks.ts","./src/hooks/usetextscramble.ts","./src/hooks/useversionhistory.ts","./src/hooks/usewebsocket.ts","./src/lib/api.ts","./src/lib/listenapi.ts","./src/lib/markdown.ts","./src/lib/supabase.ts","./src/routes/_index.tsx","./src/routes/chains.tsx","./src/routes/contract-file.tsx","./src/routes/contracts.tsx","./src/routes/files.tsx","./src/routes/history.tsx","./src/routes/listen.tsx","./src/routes/login.tsx","./src/routes/mesh.tsx","./src/routes/settings.tsx","./src/routes/speak.tsx","./src/routes/workflow.tsx","./src/types/messages.ts"],"version":"5.9.3"} \ No newline at end of file