From 87044a747b47bd83249d61a45842c7f7b2eae56d Mon Sep 17 00:00:00 2001
From: soryu <soryu@soryu.co>
Date: Sun, 11 Jan 2026 05:52:14 +0000
Subject:  Contract system

---
 .../components/contracts/QuickActionButtons.tsx    | 217 +++++++++++++++++++++
 1 file changed, 217 insertions(+)
 create mode 100644 makima/frontend/src/components/contracts/QuickActionButtons.tsx

(limited to 'makima/frontend/src/components/contracts/QuickActionButtons.tsx')

diff --git a/makima/frontend/src/components/contracts/QuickActionButtons.tsx b/makima/frontend/src/components/contracts/QuickActionButtons.tsx
new file mode 100644
index 0000000..4dbb90c
--- /dev/null
+++ b/makima/frontend/src/components/contracts/QuickActionButtons.tsx
@@ -0,0 +1,217 @@
+import { useCallback } from "react";
+
+export type QuickActionType =
+  | "create_file"
+  | "create_task"
+  | "run_task"
+  | "advance_phase"
+  | "derive_tasks"
+  | "update_file";
+
+export interface QuickAction {
+  type: QuickActionType;
+  label: string;
+  description?: string;
+  data?: Record<string, unknown>;
+}
+
+interface QuickActionButtonsProps {
+  actions: QuickAction[];
+  onAction: (action: QuickAction) => void;
+  loading?: boolean;
+}
+
+const ACTION_ICONS: Record<QuickActionType, string> = {
+  create_file: "[+]",
+  create_task: "[T]",
+  run_task: "[>]",
+  advance_phase: "[→]",
+  derive_tasks: "[≡]",
+  update_file: "[*]",
+};
+
+const ACTION_COLORS: Record<QuickActionType, string> = {
+  create_file: "border-blue-400/30 hover:border-blue-400/60 text-blue-400",
+  create_task: "border-green-400/30 hover:border-green-400/60 text-green-400",
+  run_task: "border-yellow-400/30 hover:border-yellow-400/60 text-yellow-400",
+  advance_phase: "border-purple-400/30 hover:border-purple-400/60 text-purple-400",
+  derive_tasks: "border-cyan-400/30 hover:border-cyan-400/60 text-cyan-400",
+  update_file: "border-orange-400/30 hover:border-orange-400/60 text-orange-400",
+};
+
+export function QuickActionButtons({
+  actions,
+  onAction,
+  loading = false,
+}: QuickActionButtonsProps) {
+  const handleClick = useCallback(
+    (action: QuickAction) => {
+      if (!loading) {
+        onAction(action);
+      }
+    },
+    [onAction, loading]
+  );
+
+  if (actions.length === 0) return null;
+
+  return (
+    <div className="flex flex-wrap gap-2 mt-2">
+      {actions.map((action, index) => (
+        <button
+          key={`${action.type}-${index}`}
+          onClick={() => handleClick(action)}
+          disabled={loading}
+          className={`
+            flex items-center gap-1.5 px-2 py-1
+            font-mono text-[10px] uppercase
+            border transition-colors
+            disabled:opacity-50 disabled:cursor-not-allowed
+            ${ACTION_COLORS[action.type]}
+          `}
+          title={action.description}
+        >
+          <span>{ACTION_ICONS[action.type]}</span>
+          <span>{action.label}</span>
+        </button>
+      ))}
+    </div>
+  );
+}
+
+/**
+ * Parse tool call results to extract suggested quick actions.
+ * This is used by ContractCliInput to detect actionable results.
+ */
+export function parseActionsFromToolCalls(
+  toolCalls: { name: string; success: boolean; message: string }[]
+): QuickAction[] {
+  const actions: QuickAction[] = [];
+
+  for (const tc of toolCalls) {
+    if (!tc.success) continue;
+
+    switch (tc.name) {
+      case "derive_tasks_from_file":
+        // When tasks are parsed, offer to create them
+        if (tc.message.includes("task") || tc.message.includes("Found")) {
+          actions.push({
+            type: "derive_tasks",
+            label: "Review & Create Tasks",
+            description: "Review parsed tasks and create them with chaining",
+          });
+        }
+        break;
+
+      case "process_task_completion":
+        // Check for suggested actions in the result
+        if (tc.message.includes("next task")) {
+          actions.push({
+            type: "run_task",
+            label: "Run Next Task",
+            description: "Continue with the next chained task",
+          });
+        }
+        if (tc.message.includes("advance") || tc.message.includes("phase")) {
+          actions.push({
+            type: "advance_phase",
+            label: "Advance Phase",
+            description: "Move to the next contract phase",
+          });
+        }
+        break;
+
+      case "get_phase_checklist":
+        // When checklist shows missing items, offer to create them
+        if (tc.message.includes("missing") || tc.message.includes("not created")) {
+          actions.push({
+            type: "create_file",
+            label: "Create Missing Files",
+            description: "Create files from recommended templates",
+          });
+        }
+        break;
+
+      case "advance_phase":
+        // After phase transition, suggest creating files
+        actions.push({
+          type: "create_file",
+          label: "Create Phase Files",
+          description: "Create recommended files for this phase",
+        });
+        break;
+    }
+  }
+
+  return actions;
+}
+
+/**
+ * Parse LLM response text to detect suggested actions.
+ * Used as a fallback when structured action data isn't available.
+ */
+export function parseActionsFromText(text: string): QuickAction[] {
+  const actions: QuickAction[] = [];
+  const lower = text.toLowerCase();
+
+  // Detect file creation suggestions
+  if (
+    lower.includes("create a file") ||
+    lower.includes("create the file") ||
+    lower.includes("should i create")
+  ) {
+    actions.push({
+      type: "create_file",
+      label: "Create File",
+      description: "Create the suggested file",
+    });
+  }
+
+  // Detect task creation suggestions
+  if (
+    lower.includes("create tasks") ||
+    lower.includes("create these tasks") ||
+    lower.includes("create chained tasks")
+  ) {
+    actions.push({
+      type: "create_task",
+      label: "Create Tasks",
+      description: "Create the suggested tasks",
+    });
+  }
+
+  // Detect phase advancement suggestions
+  if (
+    lower.includes("advance to") ||
+    lower.includes("ready to move to") ||
+    lower.includes("transition to")
+  ) {
+    const phases = ["specify", "plan", "execute", "review"];
+    for (const phase of phases) {
+      if (lower.includes(phase)) {
+        actions.push({
+          type: "advance_phase",
+          label: `Advance to ${phase.charAt(0).toUpperCase() + phase.slice(1)}`,
+          description: `Move to the ${phase} phase`,
+          data: { phase },
+        });
+        break;
+      }
+    }
+  }
+
+  // Detect run task suggestions
+  if (
+    lower.includes("run the task") ||
+    lower.includes("start the task") ||
+    lower.includes("run task")
+  ) {
+    actions.push({
+      type: "run_task",
+      label: "Run Task",
+      description: "Start the suggested task",
+    });
+  }
+
+  return actions;
+}
-- 
cgit v1.2.3

