feat: version display + issues board

Version: CalVer + git hash shown in top-right corner of main page.
Built via Docker ARG BUILD_VERSION at build time. Served via /api-version.

Issues Board: shared notepad window for tracking issues with plugin,
overlord, nav files, macros. Stored in openissues.json on server.
- GET/POST/DELETE /issues endpoints
- Draggable window matching Chat/Radar pattern
- Category tags (plugin, overlord, nav, macro, other) with colors
- Add/resolve issues through the UI

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erik 2026-04-09 12:53:06 +02:00
parent c4856dc701
commit 38ca6ead12
5 changed files with 258 additions and 1 deletions

59
main.py
View file

@ -10,6 +10,7 @@ from datetime import datetime, timedelta, timezone
import json
import logging
import os
import uuid
import sys
import time
from typing import Dict, List, Any
@ -1326,6 +1327,64 @@ async def get_recent_activity():
raise HTTPException(status_code=500, detail="Internal server error")
# ─── Version endpoint ────────────────────────────────────────────
@app.get("/api-version")
async def get_version():
"""Return the application version (CalVer + git hash, set at build time)."""
return {"version": os.environ.get("APP_VERSION", "dev")}
# ─── Issues board endpoints ──────────────────────────────────────
ISSUES_FILE = Path("openissues.json")
def _load_issues():
if ISSUES_FILE.exists():
try:
return json.loads(ISSUES_FILE.read_text())
except (json.JSONDecodeError, IOError):
pass
return []
def _save_issues(issues):
ISSUES_FILE.write_text(json.dumps(issues, indent=2))
@app.get("/issues")
async def get_issues():
"""Return all open issues."""
return {"issues": _load_issues()}
@app.post("/issues")
async def add_issue(issue: dict):
"""Add a new issue."""
issues = _load_issues()
new_issue = {
"id": uuid.uuid4().hex[:8],
"title": issue.get("title", "").strip(),
"description": issue.get("description", "").strip(),
"category": issue.get("category", "other"),
"author": issue.get("author", "Anonymous").strip(),
"created": datetime.utcnow().isoformat(),
}
if not new_issue["title"]:
raise HTTPException(status_code=400, detail="Title is required")
issues.insert(0, new_issue)
_save_issues(issues)
return new_issue
@app.delete("/issues/{issue_id}")
async def delete_issue(issue_id: str):
"""Resolve (delete) an issue."""
issues = _load_issues()
issues = [i for i in issues if i.get("id") != issue_id]
_save_issues(issues)
return {"status": "ok"}
@app.get("/server-health")
async def get_server_health():
"""Return current server health status."""