feat(capture): sélecteur de dépôt intelligent — suggestions, Tab par segment, validation

- GET /api/repos exhaustif (épinglés, récents, archivés),
  la palette garde son plafond de 60.
- repo-picker.js : liste filtrée sur les deux segments,
  complétion Tab façon shell (pr Tab → Projets/ avec le
  séparateur posé, kan Tab → kanbante), ↑/↓/Entrée/clic,
  avertissement doux si le dépôt n'existe pas.
This commit is contained in:
Spark
2026-10-12 15:47:28 -04:00
parent f5d2d7d716
commit 760fbd8c6d
4 changed files with 309 additions and 1 deletions
+29
View File
@@ -3513,6 +3513,35 @@ async def api_stats():
return get_stats()
@app.get("/api/repos")
async def api_repos():
"""Every repo, for pickers (capture page repo selector, 0.21.0).
Unlike /api/palette (capped at 60 for a launcher), a picker
must be exhaustive — Bruno has 130+ repos. Ranked for the
empty-query case: starred first, archived last, most
recently updated first in between.
"""
repos = await gitea.search_repos()
active, inactive, archived = classify_repos(repos)
try:
starred = {r["full_name"] for r in await gitea.list_starred()}
except Exception:
starred = set()
out = [
{
"full_name": r["full_name"],
"starred": r["full_name"] in starred,
"updated_at": r.get("updated_at") or "",
"archived": bool(r.get("archived")),
}
for r in active + inactive + archived
]
out.sort(key=lambda x: x["updated_at"], reverse=True)
out.sort(key=lambda x: (not x["starred"], x["archived"]))
return {"repos": out}
@app.get("/api/palette")
async def api_palette(q: str = ""):
"""Data for the Ctrl+K command palette (roadmap #10).