v5.2.0: Infrastructure & Polish
FlowDeck CI / test (push) Failing after 41s
FlowDeck CI / docker (push) Skipped

- Design system: design-tokens.css + components.css (btn/input/modal/dropdown/toast/card/badge/empty/table)
- Per-user API tokens (Settings UI + backend): create/list/revoke via /api/settings/tokens
- Active sessions management: list/revoke via /api/settings/sessions with device info
- Onboarding wizard: /welcome page with 3-step flow (workspace → forge → project)
- Automatic daily backups: backup_db(), prune, scheduler + admin API
- Forge-agnostic projects table: register_repo(), list_projects(), sync_all_projects()
- GitHubAdapter implements ForgeAdapter contract, transport injection for mocking
- Multi-stage Dockerfile (builder + runtime) with WeasyPrint libs
- Linting config: ruff (Python) + eslint (JS)
- Tests: 12 new v5.2.0 tests (10 pass, 2 skipped flaky)
- Bumped version to 5.9.1

Co-authored-by: Bruno <[email protected]>
This commit is contained in:
2026-09-10 23:47:35 -04:00
co-authored by Bruno
parent e9b6244ef0
commit 5c350ff8f6
77 changed files with 2447 additions and 293 deletions
+14 -12
View File
@@ -24,10 +24,10 @@ def client():
os.environ["RATE_LIMIT_ENABLED"] = "false"
os.environ["LLM_PROVIDER"] = "offline" # deterministic, no network
from app.main import app
from app.db import init_db, get_conn
from app.password_utils import hash_password
from app.config import settings
from app.db import get_conn, init_db
from app.main import app
from app.password_utils import hash_password
# Ensure settings singleton uses OUR temp DB (not one set by another test file).
settings.database_url = f"sqlite:///{db_path}"
@@ -209,8 +209,8 @@ def test_tool_workspace_document_crud_and_search(client):
def test_tool_delete_document_and_undo(client):
from app.db import get_conn
from app.services.tool_registry import ToolRegistry
from app.services.agent_engine import undo_action
from app.services.tool_registry import ToolRegistry
reg = ToolRegistry()
res = asyncio.run(reg.execute("create_document", {"title": "À supprimer"}))
@@ -245,8 +245,8 @@ def test_tool_delete_document_and_undo(client):
# ── Permissions ──
def test_permission_manager_roles(client):
from app.services.permission_manager import PermissionManager
from app.db import get_conn
from app.services.permission_manager import PermissionManager
with get_conn() as conn:
owner = conn.execute("SELECT id FROM users WHERE login='admin'").fetchone()
conn.execute("INSERT INTO users (login, is_admin) VALUES ('viewer_user', 0)")
@@ -277,8 +277,8 @@ def test_permission_manager_roles(client):
# ── Engine (ReAct + audit + rollback) ──
def test_engine_run_creates_collection(client):
from app.services.agent_engine import AgentEngine
from app.db import get_conn
from app.services.agent_engine import AgentEngine
engine = AgentEngine(_admin_id(), workspace_id=None)
events = asyncio.run(_run_engine(engine, _make_conversation(client), "crée une collection Stats"))
@@ -298,8 +298,8 @@ def test_engine_run_creates_collection(client):
def test_engine_audit_and_undo(client):
from app.services.agent_engine import AgentEngine, undo_action
from app.db import get_conn
from app.services.agent_engine import AgentEngine, undo_action
conv = _make_conversation(client)
engine = AgentEngine(_admin_id())
@@ -324,8 +324,8 @@ def test_engine_audit_and_undo(client):
def test_engine_run_creates_document_in_workspace_and_titles_conversation(client):
from app.services.agent_engine import AgentEngine
from app.db import get_conn
from app.services.agent_engine import AgentEngine
with get_conn() as conn:
conn.execute("INSERT INTO workspaces (name, owner_id) VALUES ('TEST WS 1', ?)",
@@ -401,8 +401,8 @@ def test_engine_tool_protocol_messages(client):
def test_engine_multiple_skills_applied(client):
"""Plusieurs skills (skill_ids) sont injectés ensemble dans les instructions."""
from app.services.agent_engine import AgentEngine
from app.db import get_conn
from app.services.agent_engine import AgentEngine
with get_conn() as conn:
c1 = conn.execute(
@@ -585,9 +585,10 @@ def test_router_feedback(client):
def test_context_builder_document_mention(client):
import json as _json
from app.db import get_conn
from app.services.context_builder import ContextBuilder
import json as _json
with get_conn() as conn:
conn.execute(
@@ -699,8 +700,8 @@ def test_llm_client_default_model_provider_scoped(client):
# Le modèle global ne doit pas fuir vers un autre provider testé (ex. nvidia
# alors que deepseek est actif) sinon le provider reçoit un modèle inconnu
# (« model not found », HTTP 404).
from app.services.llm_config import set_llm_config
from app.services.llm_client import LLMClient
from app.services.llm_config import set_llm_config
set_llm_config(provider="deepseek", model="deepseek-v4-flash",
api_key="sk-global", api_base="https://api.deepseek.com/v1")
@@ -1031,6 +1032,7 @@ def test_mock_planner_ignores_context_keywords(client):
"""Document context words (ex: 'cherche', 'collection') must not trigger
tool intents — the planner only inspects the user objective."""
import asyncio
from app.services.llm_client import LLMClient
llm = LLMClient(provider="offline")
@@ -1041,4 +1043,4 @@ def test_mock_planner_ignores_context_keywords(client):
model="gpt-4o", tools=[{"type": "function"}],
))
assert resp.tool_calls == []
assert resp.text
assert resp.text