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>
---
 makima/frontend/src/routes/settings.tsx | 119 ++++++++++++++++++++++++++++++++
 1 file changed, 119 insertions(+)

(limited to 'makima/frontend/src/routes/settings.tsx')

diff --git a/makima/frontend/src/routes/settings.tsx b/makima/frontend/src/routes/settings.tsx
index 7ca40ba..d3f4c1b 100644
--- a/makima/frontend/src/routes/settings.tsx
+++ b/makima/frontend/src/routes/settings.tsx
@@ -11,9 +11,12 @@ import {
   changeEmail,
   deleteAccount,
   listDaemons,
+  listRepositoryHistory,
+  deleteRepositoryHistory,
   type ApiKeyInfo,
   type CreateApiKeyResponse,
   type Daemon,
+  type RepositoryHistoryEntry,
 } from "../lib/api";
 
 // =============================================================================
@@ -304,9 +307,16 @@ export default function SettingsPage() {
   const [daemonsLoading, setDaemonsLoading] = useState(true);
   const [daemonsError, setDaemonsError] = useState<string | null>(null);
 
+  // Repository history state
+  const [repoHistory, setRepoHistory] = useState<RepositoryHistoryEntry[]>([]);
+  const [repoHistoryLoading, setRepoHistoryLoading] = useState(true);
+  const [repoHistoryError, setRepoHistoryError] = useState<string | null>(null);
+  const [deletingRepoId, setDeletingRepoId] = useState<string | null>(null);
+
   useEffect(() => {
     loadApiKey();
     loadDaemons();
+    loadRepoHistory();
   }, []);
 
   // Auto-refresh daemons every 30 seconds
@@ -342,6 +352,30 @@ export default function SettingsPage() {
     }
   };
 
+  const loadRepoHistory = async () => {
+    try {
+      setRepoHistoryError(null);
+      const response = await listRepositoryHistory();
+      setRepoHistory(response.entries);
+    } catch (err) {
+      setRepoHistoryError(err instanceof Error ? err.message : "Failed to load repository history");
+    } finally {
+      setRepoHistoryLoading(false);
+    }
+  };
+
+  const handleDeleteRepoHistory = async (id: string) => {
+    try {
+      setDeletingRepoId(id);
+      await deleteRepositoryHistory(id);
+      setRepoHistory((prev) => prev.filter((entry) => entry.id !== id));
+    } catch (err) {
+      setRepoHistoryError(err instanceof Error ? err.message : "Failed to delete entry");
+    } finally {
+      setDeletingRepoId(null);
+    }
+  };
+
   const handleCreate = async () => {
     try {
       setActionLoading(true);
@@ -692,6 +726,91 @@ export default function SettingsPage() {
                 </div>
               )}
             </section>
+
+            {/* Repository History */}
+            <section className="border border-[rgba(117,170,252,0.25)] bg-[#0d1b2d] p-4">
+              <div className="flex items-center justify-between mb-3 pb-2 border-b border-[rgba(117,170,252,0.15)]">
+                <div className="flex items-center gap-2">
+                  <h2 className="text-[11px] font-mono uppercase tracking-wide text-[#8899aa]">
+                    Repository History
+                  </h2>
+                  {repoHistory.length > 0 && (
+                    <span className="text-[10px] font-mono text-[#556677]">
+                      ({repoHistory.length} saved)
+                    </span>
+                  )}
+                </div>
+                <button
+                  onClick={loadRepoHistory}
+                  disabled={repoHistoryLoading}
+                  className="text-[10px] font-mono text-[#75aafc] hover:text-[#9bc3ff] disabled:opacity-50"
+                  title="Refresh"
+                >
+                  {repoHistoryLoading ? "..." : "↻"}
+                </button>
+              </div>
+              <p className="text-[#7788aa] font-mono text-[10px] mb-3">
+                Previously used repositories will appear as suggestions when adding repos to contracts.
+              </p>
+
+              {repoHistoryError && <ErrorAlert>{repoHistoryError}</ErrorAlert>}
+
+              {repoHistoryLoading && repoHistory.length === 0 ? (
+                <p className="text-[#7788aa] font-mono text-xs">Loading...</p>
+              ) : repoHistory.length === 0 ? (
+                <div className="text-center py-4">
+                  <p className="text-[#7788aa] font-mono text-xs mb-2">No repository history</p>
+                  <p className="text-[#556677] font-mono text-[10px]">
+                    Add repositories to contracts to build history
+                  </p>
+                </div>
+              ) : (
+                <div className="space-y-2 max-h-[300px] overflow-y-auto">
+                  {repoHistory.map((entry) => (
+                    <div
+                      key={entry.id}
+                      className="border border-[rgba(117,170,252,0.15)] bg-[#0a1525] p-3"
+                    >
+                      <div className="flex items-start justify-between gap-2">
+                        <div className="flex-1 min-w-0">
+                          <div className="flex items-center gap-2 mb-1">
+                            <span className="font-mono text-xs text-[#9bc3ff] truncate">
+                              {entry.name}
+                            </span>
+                            <span
+                              className={`text-[10px] font-mono uppercase px-1.5 py-0.5 border ${
+                                entry.sourceType === "remote"
+                                  ? "text-cyan-400 border-cyan-700/50 bg-cyan-900/20"
+                                  : "text-green-400 border-green-700/50 bg-green-900/20"
+                              }`}
+                            >
+                              {entry.sourceType}
+                            </span>
+                          </div>
+                          <div className="font-mono text-[10px] text-[#556677] truncate mb-1">
+                            {entry.repositoryUrl || entry.localPath}
+                          </div>
+                          <div className="flex items-center gap-3 font-mono text-[10px] text-[#7788aa]">
+                            <span>Used {entry.useCount}×</span>
+                            <span>
+                              Last: {new Date(entry.lastUsedAt).toLocaleDateString()}
+                            </span>
+                          </div>
+                        </div>
+                        <button
+                          onClick={() => handleDeleteRepoHistory(entry.id)}
+                          disabled={deletingRepoId === entry.id}
+                          className="p-1 font-mono text-[10px] text-[#555] hover:text-red-400 transition-colors disabled:opacity-50"
+                          title="Delete from history"
+                        >
+                          {deletingRepoId === entry.id ? "..." : "×"}
+                        </button>
+                      </div>
+                    </div>
+                  ))}
+                </div>
+              )}
+            </section>
           </div>
 
           {/* Right Column */}
-- 
cgit v1.2.3

