From 764bd28d08ceaef03cd4050f9568a62d77bbcfca Mon Sep 17 00:00:00 2001
From: soryu <soryu@soryu.co>
Date: Thu, 15 Jan 2026 03:37:44 +0000
Subject: Add repository history feature to store and suggest previously used
 repositories (#18)

- Add repository_history table migration with repo_type, repo_path, use_count, last_used_at
- Add RepositoryHistoryEntry model and CRUD database functions
- Create API endpoints: GET/POST/DELETE /api/v1/repository-history, GET /api/v1/repository-history/suggestions
- Update add_remote_repository and add_local_repository handlers to automatically track history
- Update frontend API with repository history types and functions
- Add Repository History section to Settings page with list of entries and delete functionality
- Add suggestions dropdown to RepositoryPanel when entering new repository URL/path
- Suggestions filter by repo type (remote vs local) and match on user input

Test plan:
- Add a remote repository to a contract - verify it appears in Settings history
- Add a local repository to a contract - verify it appears in Settings history
- Add same repository again - verify use_count increments, not duplicate
- When adding new repository, verify suggestions appear based on history
- Delete a history entry from Settings - verify it's removed
- Verify suggestions only show matching type (remote for remote, local for local)

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
---
 .../src/components/contracts/RepositoryPanel.tsx   | 69 +++++++++++++++++++++-
 1 file changed, 67 insertions(+), 2 deletions(-)

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

diff --git a/makima/frontend/src/components/contracts/RepositoryPanel.tsx b/makima/frontend/src/components/contracts/RepositoryPanel.tsx
index 4170cfb..e314140 100644
--- a/makima/frontend/src/components/contracts/RepositoryPanel.tsx
+++ b/makima/frontend/src/components/contracts/RepositoryPanel.tsx
@@ -4,8 +4,9 @@ import type {
   RepositorySourceType,
   RepositoryStatus,
   DaemonDirectory,
+  RepositoryHistoryEntry,
 } from "../../lib/api";
-import { getDaemonDirectories } from "../../lib/api";
+import { getDaemonDirectories, getRepositorySuggestions } from "../../lib/api";
 import { DirectoryInput } from "../mesh/DirectoryInput";
 
 interface RepositoryPanelProps {
@@ -53,6 +54,9 @@ export function RepositoryPanel({
   const [isPrimary, setIsPrimary] = useState(false);
   // Daemon directory suggestions for local repositories
   const [suggestedDirectories, setSuggestedDirectories] = useState<DaemonDirectory[]>([]);
+  // Repository history suggestions
+  const [repoSuggestions, setRepoSuggestions] = useState<RepositoryHistoryEntry[]>([]);
+  const [showSuggestions, setShowSuggestions] = useState(false);
 
   // Fetch daemon directories when "local" mode is selected
   useEffect(() => {
@@ -63,6 +67,36 @@ export function RepositoryPanel({
     }
   }, [addMode]);
 
+  // Fetch repository suggestions when mode changes to remote or local
+  useEffect(() => {
+    if (addMode === "remote" || addMode === "local") {
+      getRepositorySuggestions(addMode, undefined, 10)
+        .then((res) => {
+          setRepoSuggestions(res.entries);
+          setShowSuggestions(res.entries.length > 0);
+        })
+        .catch(() => {
+          setRepoSuggestions([]);
+          setShowSuggestions(false);
+        });
+    } else {
+      setRepoSuggestions([]);
+      setShowSuggestions(false);
+    }
+  }, [addMode]);
+
+  // Apply a suggestion to the form
+  const applySuggestion = (suggestion: RepositoryHistoryEntry) => {
+    setName(suggestion.name);
+    if (suggestion.repositoryUrl) {
+      setUrl(suggestion.repositoryUrl);
+    }
+    if (suggestion.localPath) {
+      setPath(suggestion.localPath);
+    }
+    setShowSuggestions(false);
+  };
+
   const handleAdd = () => {
     if (!name.trim()) return;
 
@@ -162,12 +196,43 @@ export function RepositoryPanel({
       {/* Add repository form */}
       {addMode ? (
         <div className="p-3 border border-[rgba(117,170,252,0.3)] space-y-3">
-          <div className="flex items-center gap-2 mb-2">
+          <div className="flex items-center justify-between mb-2">
             <span className="font-mono text-xs text-[#75aafc] uppercase">
               Add {sourceTypeLabels[addMode]} Repository
             </span>
+            {repoSuggestions.length > 0 && (
+              <button
+                onClick={() => setShowSuggestions(!showSuggestions)}
+                className="font-mono text-[10px] text-[#555] hover:text-[#9bc3ff] transition-colors"
+              >
+                {showSuggestions ? "Hide suggestions" : `${repoSuggestions.length} suggestions`}
+              </button>
+            )}
           </div>
 
+          {/* Suggestions dropdown */}
+          {showSuggestions && repoSuggestions.length > 0 && (
+            <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}
+                  onClick={() => applySuggestion(suggestion)}
+                  className="w-full text-left px-3 py-2 font-mono text-xs hover:bg-[rgba(117,170,252,0.1)] transition-colors 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]">
+                      {suggestion.useCount}×
+                    </span>
+                  </div>
+                  <div className="text-[10px] text-[#556677] truncate">
+                    {suggestion.repositoryUrl || suggestion.localPath}
+                  </div>
+                </button>
+              ))}
+            </div>
+          )}
+
           <input
             type="text"
             value={name}
-- 
cgit v1.2.3

