Commit Graph
84 Commits
Author SHA1 Message Date
bruno 3bb8e87ef2 fix: A42 terminé — client httpx partagé par boucle (v7.28.0)
FlowDeck CI / lint (push) Canceled after 0s
FlowDeck CI / test (push) Canceled after 0s
FlowDeck CI / docker (push) Canceled after 0s
- `app/services/http_client.py` : `async with shared_client(timeout=15)
  as client:` remplace les 49 créations `async with httpx.AsyncClient(`
  de 14 fichiers (gitea ×21, providers oidc/oauth ×11, calendar ×4,
  automations ×3…) — le pool de connexions est réutilisé au lieu d'être
  recréé à chaque appel. __aexit__ no-op (le client partagé ne se ferme
  pas à la sortie).
- Cache par (boucle d'event, kwargs) en WeakKeyDictionary : un
  AsyncClient n'est JAMAIS partagé entre deux loops (piège des tests
  « Event loop is closed ») — une boucle par test = client propre
  collecté avec la boucle. Clé = kwargs triés, repr() pour les valeurs
  non hashables (`headers=` dict → TypeError rattrapé par la suite).
- Laissés délibérément : github_adapter (transport MockTransport
  injecté), webhook_outbound (client « own_client » fermé par la
  fonction).
- Tests : `test_http_client_shared_and_loop_scoped` (réutilisation mêmes
  kwargs / cloisonné kwargs / cloisonné loop) ; le stub des webhooks
  patche aussi la fabrique `http_client.httpx` + purge du cache (avant :
  webhook_outbound.httpx patché mais la fabrique partagée créait un vrai
  client → réseau réel dans les tests).

suite **1091/1091** · ruff OK · docs à jour
2026-10-01 23:09:45 -04:00
bruno 45e59009c3 fix: A21 phase 2c — 190 routes hors loop, 86 % total (v7.26.0)
FlowDeck CI / lint (push) Successful in 1m55s
FlowDeck CI / test (push) Successful in 15m23s
FlowDeck CI / docker (push) Canceled after 0s
4 passes (283 → 93 routes async sur 667 = 86 % hors loop, avant 61 %) :

A. RACINE AUTH — `get_current_user` (auth/session.py) était `async def`
   SANS aucun await (cookie decode = synchrone) ; idem ses clones :
   `agent._current_user_id/_workspace_id/_current_admin` (34 sites) et
   `sso._require_admin` (corps 0 await, 6 sites) → `def` +
   47 `await` supprimés. Piège : 3 call sites passaient par l'alias `gcu`
   (grep littéral aveugle) — 8 tests en échec → corrigés.

B. Re-scan : 19 routes devenues SANS await → `def` (agent 8, sso 5,
   web_clipper 3, projects 2, auth 1…).

C/D. 155 routes dont les seuls awaits = `request.json()` / événements :
   - try/except `body = {}` → `Body(default={})` (même tolérance)
   - try/except `raise HTTPException(400)` → `Body(...)` REQUIS
     (422 FastAPI — aucun test ne couvrait le 400)
   - forme conditionnelle `request.json() if content-type else {}`
     (54 sites) → défaut `{}` (sans corps = `{}` dans les 2 cas)
   - `await fire_*` → `run_event_sync(...)` ; imports `Body` /
     `run_event_sync` ajoutés aux routers convertis

Reste async (93, justifié) : form/upload/file (22), réseau gitea/llm/oidc,
`_json_body` (9), 2 JSON inline en argument, 1 fallback logique
(capture_frontend_error), 1 lecture conditionnelle (web_clipper), mixtes.

suite **1089/1089** · ruff OK · docs à jour
2026-10-01 22:17:48 -04:00
bruno 224bda74d5 fix: A21 phase 1 — 352 routes async sans await → threadpool (v7.8.0)
FlowDeck CI / lint (push) Canceled after 0s
FlowDeck CI / test (push) Canceled after 0s
FlowDeck CI / docker (push) Canceled after 0s
- Conversion `async def` → `def` de TOUTES les routes dont le corps ne contient
  ni `await`, ni `async with`, ni `async for`, ni `asyncio` (scan automatique
  corps par corps sur app/ : 352 converties, 0 dangereuses, vérifié
  `asyncio`/`run_coroutine`/`.result()` absents). FastAPI exécute ces handlers
  dans son threadpool → tout leur SQLite (`get_conn()` + `conn.execute`) quitte
  l'event loop, sans changer une ligne de logique.
- Répartition : api_v2 60, dashboard 40, collections 25, board 23,
  workspace 19, wiki 17, permissions 14, api 14, main.py 6, + 35 fichiers.
- Les 4 routers prioritaires de l'audit sont couverts par ce lot :
  api_v2 60 + dashboard 40 + collections 25 + board 23 = 148 conversions
  (le reste de leurs routes attend la phase 2 : elles ont de vrais `await`).
- Reste (phase 2) : les 311 routes avec de vrais `await` → enrouler les blocs
  DB dans `await anyio.to_thread.run_sync(...)` ; pas de wrapper partagé livré
  (rien ne l'appellerait — YAGNI jusqu'au premier usage).

suite **1037/1037** (233 s) · `ruff check app tests` OK · docs à jour
2026-10-01 10:53:26 -04:00
bruno 7be96f0618 fix: A29 + A42(partiel) — publish partagé, fuite password_hash, data_dir (v7.5.0)
FlowDeck CI / lint (push) Canceled after 0s
FlowDeck CI / test (push) Canceled after 0s
FlowDeck CI / docker (push) Canceled after 0s
- A29 — `app/services/publish.py` : slugify titré unique (fallback aléatoire),
  404 si la page n'existe pas, événements centralisés. Les 3 paires
  publish/unpublish déléguent (sharing = front, board, v2) :
  · board : mise à jour aveugle → 404 + contrôle de session ajouté
  · board : perd `share_mode='anyone'` en bonus, v2 : perd `is_shared=1` —
    le share dialog reste l'unique propriétaire de ces drapeaux
  · v2 : slug fourni conservé, slug vidé aussi à la dépublication (avant : laissé)
  · `/users/me` ×2 et listings collections ×3 = contrats versionnés distincts,
    décision documentée (on garde)
- Byproduct sécurité — `GET /api/users/me` (v1) et le contexte de `/accounts`
  faisaient `SELECT *` sur users → password_hash / login_attempts / locked_until
  exposés → colonnes whitelistées (liste v2)
- A42 (partiel) — 9 copies de `Path(os.environ.get("FLOWDECK_DATA_DIR", "/data"))`
  → `settings.data_dir` (property : lecture à chaque accès, les tests
  monkeypatchent l'env) ; cache Gitea : évacuation des entrées expirées à chaque
  écriture. Reste : client httpx partagé (52 créations, cache par event loop)

tests : test_publish_service_shared_and_safe, test_users_me_no_secret_columns,
test_gitea_cache_evicts_expired

suite **1034/1034** · `ruff check app tests` OK · OpenAPI 511 chemins / 7.5.0
docs (ROADMAP/CHANGELOG/WORKLOAD/VERSION) à jour
2026-10-01 10:01:39 -04:00
bruno 937ecfc2e0 fix: A30 + A37 + A39 + A40 + A41 — fin du P2/P3 XS/S (v7.4.0)
FlowDeck CI / lint (push) Canceled after 0s
FlowDeck CI / test (push) Canceled after 0s
FlowDeck CI / docker (push) Canceled after 0s
- A30 — `require_scope()` câblé : 69 sites stricts de api_v2.py passent par la
  factory (Bearer + scope en 1 appel, contrôle manuel supprimé) ; sémantique
  alignée sur celle des handlers (pas de default "read" → 0 changement de
  comportement) ; 12 top-level morts supprimés (0 ref app ET tests) :
  unsync_block, find_referring, _b64url, strip_markdown, format_number,
  get_auto_property_value, get_next_unique_id, local_date_in_tz,
  verify_device_token, _get_dynamic_groups, _require_user_gitea,
  validate_upload_request
- A37 — CORS sans `*` : origines = app_base_url + allow_origin_regex
  (localhost/dev, origines d'extension pour le Web Clipper), méthodes et
  entêtes minutées, allow_credentials explicite + test test_cors_no_star
- A39 — htmx : décision « rien » documentée (32 attributs hx-* réels sur 6
  templates, conversion = refonte du view-switching sans test E2E)
- A40 — version d'assets à source unique : ENV.globals["asset_version"] lu au
  boot depuis le fichier VERSION ; littéraux `?v=` de base.html éliminés ;
  test test_asset_version_single_source
- A41 — app.css : 91 règles mortes purgées (-10 274 octets, 121 618 → 111 344),
  scan templates/JS/CSS/Python à 0 référence

suite **1031/1031** · `ruff check app tests` OK · OpenAPI 511 chemins / 7.4.0
docs (ROADMAP/CHANGELOG/WORKLOAD/VERSION) à jour
2026-10-01 09:30:37 -04:00
bruno cb47f5c7f4 fix: A26 + A33 + A34 + A35 + A36 + A43 — bande S/XS du P2 (v7.3.9)
FlowDeck CI / lint (push) Canceled after 0s
FlowDeck CI / test (push) Canceled after 0s
FlowDeck CI / docker (push) Canceled after 0s
- A26 — champ `standalone` mort supprimé (0 lecteur) ; `sqlite:////data/…` ne
  produit plus de chemin UNC sous Windows ; `.env.example` ne promet plus
  PostgreSQL ; raise au boot si `APP_SECRET_KEY` vaut la valeur par défaut
- A33 — rate limit : préfixes manquants (`/scim/v2/`, `/workspace/`, `/db/` +
  non-GET sur `/s/` et `/f/`), limite lue dans `settings.rate_limit_requests`
  (60 annoncés / 100 codés en dur), clé `X-Forwarded-For` seulement derrière un
  proxy local (anti-spoof), `_store` épuré (mémoire bornée) + test dédié
- A34 — `_spawn()` : les 10 schedulers loggent leur exception et redémarrent
  après 10 s au lieu de mourir en silence ; 2 `logger.debug` de scheduler → warning
- A35 — OpenAPI régénéré 439 → 511 chemins (`info.version 7.3.9`), README
  (était v6.7.0), compteur API_GUIDE, titre dupliqué retiré du ROADMAP ;
  le drift Python 3.12/3.13 reste noté (rebuild d'image à valider)
- A36 — 4 dépendances mortes purgées de requirements.txt (aiosqlite, slowapi,
  loguru, packaging = 0 import) ; pyproject reste sans [project] : Docker et la
  CI installent requirements.txt, dupliquer les 22 deps créerait 2 sources
- A43 — 15 `datetime.utcnow()` → `now(UTC).replace(tzinfo=None)` (format ISO
  naïf identique, warnings de dépréciation divisés : 2374 → 926)

suite **1029/1029** · `ruff check app tests` OK · docs/ROADMAP/CHANGELOG/WORKLOAD à jour
2026-10-01 08:50:39 -04:00
bruno ffa1fa89ab fix: A25 + A21 (partiel) — plus d'exception muque, transaction protégée (v7.3.8)
FlowDeck CI / lint (push) Successful in 1m51s
FlowDeck CI / docker (push) Canceled after 0s
FlowDeck CI / test (push) Canceled after 10m12s
- A25 — 84 `except Exception: pass/…` → `logger.exception("<fonction>")`
  (19 fichiers : api_v2 30, dashboard 10, board 7, sites 5, workspace 5,
  api_v2_helpers 5, …) ; `logger` ajouté là où il manquait (api_v2_helpers,
  sites + `import logging`)
- A25 critique — les `try` autour de `materialize_properties` supprimés dans
  `create_collection_v2` ET `apply_db_template_v2` : un échec interrompt la
  transaction au lieu de commiter une collection sans schéma
- test `test_collection_rollback_when_materialize_fails` (Bearer v2, monkeypatch
  qui lève, assertions : RuntimeError + 0 ligne commitée)
- A21 partiel — `PRAGMA busy_timeout=5000` dans `get_conn()` (point d'entrée
  unique) ; commentaire `ponytail:` : le wrapper async + les 510 call sites
  restent à migrer module par module
- suite **1028/1028** · `ruff check app tests` OK
2026-10-01 08:16:42 -04:00
bruno 5a537f5dc3 fix: A12–A24 — SSRF, auth routes legacy, uploads, N+1 et routes doublonnes (v7.3.3)
FlowDeck CI / docker (push) Successful in 1m44s
FlowDeck CI / lint (push) Successful in 1m53s
FlowDeck CI / test (push) Successful in 20m45s
- A12 — `og_fetcher` : GET sans `follow_redirects`, `_is_public_host` revérifié à
  chaque saut (max 5) ; `POST /board/api/og/metadata` → 400 sur hôte privé/loopback
- A13 — router automations sous `Depends(_require_session)` (CRUD, run,
  press-button) + `created_by` sans fallback ; action `webhook` validée par
  `_is_public_host` avant POST (SSRF)
- A15 — webhooks sortants : `_require_admin` sur GET/POST/DELETE + `_is_public_host`
  sur l'URL en création
- A17 — router legacy `/api` sous `Depends(_require_session_or_bearer)` (session ou
  Bearer `/api/v1`), allowlist explicite `/api/health` + `/api/frontend-error`
- A22 — les 2 uploads locales : session exigée (`_require_user_id`) + `validate_upload`
  branché (taille + extension) + `FLOWDECK_DATA_DIR` au lieu de `/data` codé en dur
- A23 — N+1 : COUNT→`GROUP BY` (dashboard), cards→`executemany` (board sync),
  duplicata de propriétés→`executemany` + remap des ids par SELECT (collections)
- A24 — 2 routes écrasées supprimées : `GET /api/projects` (api.py) et
  `GET /workspace` (workspace.py) + test « aucun doublon méthode+chemin »
- Tests : +9 dans `tests/test_audit_p0_fixes.py` (SSRF, 401s, validate_upload,
  doublons de routes) ; tests OG sur hôtes résolubles (la garde fait du DNS)
- suite **1025/1025** · `ruff check app tests` OK
2026-09-30 23:12:20 -04:00
bruno 69a0aceba6 fix: A10 — autoescape Jinja2 activé partout via un Environment partagé
FlowDeck CI / docker (push) Successful in 1m44s
FlowDeck CI / lint (push) Successful in 1m49s
FlowDeck CI / test (push) Successful in 21m54s
- `app/templating.py` : `ENV = Environment(loader=FileSystemLoader("app/templates"), autoescape=select_autoescape(["html"]))`
- les 29 instantiations `Environment(loader=FileSystemLoader("app/templates"))` (9 routers) remplacées par `env = ENV` — plus aucune interpolation `{{ … }}` servie crue, les `|safe` redeviennent efficaces
- re-tri des `|safe` : `card_detail.html` corps d'issue et commentaires échappés (XSS stocké), placeholder de description sorti du ternaire, `sidebar_config` passé en dict + `|tojson` (échappement `</script>` en contexte script)
- `|safe` conservé sur `right_actions` (HTML fabriqué dans les templates, fiable)
- `ruff check app tests` OK · suite **1016/1016 verts**
2026-09-30 22:28:48 -04:00
bruno d125eb399e fix: A3–A8 — bloc « fallback admin » de l'audit sécurité (401 sans session, ACL, CSRF)
FlowDeck CI / lint (push) Successful in 1m49s
FlowDeck CI / test (push) Successful in 21m47s
FlowDeck CI / docker (push) Successful in 1m45s
- A3 : helper `_require_user_id()` (401 sans session) sur profile/password/token/forge ; `PUT /api/user/password` exige `current_password` vérifié ; `/api/user` sorti de la liste CSRF exemptée
- A4 : `POST /api/v1/token` → 401 sans session, chemin legacy `user_id=0` supprimé
- A5 : CRUD membres d'espace (POST/PUT/DELETE) : session + rôle admin de l'espace (ou admin global), placeholder user créé en `is_admin=0`, GET membres sans session → 401
- A6 : `_require_view` → 404 et `_require_edit` → 401 quand il n'y a pas de session (fin du legacy single-user sur les collections)
- A7 : création ET lecture de page → 401 sans session (PermissionManager conservé) ; `/board/api/pages` sorti de `EXCLUDED_PATHS` ; header CSRF manquant ajouté sur setItemIcon (local_workspace)
- A8 : seed admin sans mot de passe codé en dur — aléatoire au premier boot loggé une fois, ou `FLOWDECK_ADMIN_PASSWORD` ; re-seed seulement si absent
- tests : client connecté par défaut via `_TestSessionAuth` (session + CSRF injectés à la volée, jamais dans le cookie jar → plus de CookieConflict), helper `anon()` sur les 40 tests d'anonymat ; 1016/1016 verts, `ruff check app tests` OK
2026-09-30 22:04:13 -04:00
bruno 1706ad1ee9 feat: v7.3.0 — cycle v6.8.0→v7.3.0 (Sites, Search, Automations, Calendar, SCIM, Wiki) + audit A9
FlowDeck CI / lint (push) Successful in 1m48s
FlowDeck CI / test (push) Failing after 21m19s
FlowDeck CI / docker (push) Skipped
- v6.8.0 Sites & Forms publics (migrations 24)
- v6.9.0 Recherche sémantique hybride + Ask AI (migration 25)
- v7.0.0 Automations v2 multi-étapes + Workers sandboxés (migration 26)
- v7.1.0 Calendar sync Google/CalDAV + Meeting Notes (migration 27)
- v7.2.0 Enterprise : SCIM 2.0, 2FA TOTP/passkeys, audit UI, agent approvals (migration 28)
- v7.3.0 Wiki/Teamspaces, verified pages, collab polish, charts, unfurl (migration 29)
- docs V68→V73, ROADMAP/CHANGELOG/WORKLOAD à jour, VERSION 7.3.0
- A9 : flowdeck.db, flowdeck_dev.db, test-commit.md, upload_test.txt et e2e/{node_modules,shots,test-results} désindexés + ignorés (.gitignore/.dockerignore)
2026-09-30 20:02:57 -04:00
bruno 5951c707eb feat: v6.5.0 Synced blocks production — pages contenu par lignes de DB, résolution serveur à chaque lecture, propagation écrite réelle
FlowDeck CI / lint (push) Successful in 1m27s
FlowDeck CI / test (push) Successful in 10m51s
FlowDeck CI / docker (push) Successful in 1m21s
2026-09-24 08:28:25 -04:00
bruno e0237e576f Fire automation events across API routers
FlowDeck CI / lint (push) Failing after 1m12s
FlowDeck CI / test (push) Failing after 3h3m3s
FlowDeck CI / docker (push) Skipped
2026-09-21 20:30:05 -04:00
bruno ce0d561ade feat(share,permissions): partage de page par groupes (page_shares)
FlowDeck CI / lint (push) Failing after 1m10s
FlowDeck CI / test (push) Successful in 9m55s
FlowDeck CI / docker (push) Successful in 1m11s
- app/db.py: ajout colonne shared_with_group_id (FK user_groups, migration
  backfill v5.x) dans page_shares
- app/routers/sharing.py: POST /api/pages/{id}/share accepte group_id
  (upsert, verif FK groupe), GET /shares expose kind/group_name, synchro
  bidirectionnelle avec page_permissions (mirror grant/revoke) pour que
  PermissionManager donne un acces effectif (view/comment/edit) aux membres
  du groupe; PUT/DELETE gardent le miroir a jour
- app/routers/board.py, library.py: received/made incluent les partages via
  groupes (JOIN group_members)
- app/templates/_page_editor_content.html, _page_editor_scripts.html:
  dialogue Share — invite groups (fetch /api/v2/groups, filtre deja partages),
  pickInviteGroup, shareInvite(group_id), rendu accessList avec avatar groupe
- tests/test_share_groups.py: 10 tests (CRUD groupe, kind, miroir ACL)

Chore: inclut evolutions v6.4.0 deja en working copy (webhooks prod,
migrations, sync, config/main) pour garder l'arbre coherent.
2026-09-21 06:38:02 -04:00
bruno 13d5f8625a feat: v6.1.0 granular permissions (page/collection/property ACL + groups + audit)
FlowDeck CI / lint (push) Failing after 1m8s
FlowDeck CI / test (push) Failing after 8m5s
FlowDeck CI / docker (push) Skipped
- Migration 18: 6 tables + 3 colonnes permission_type + indexes
- PermissionManager: heritage page->collection->workspace, least privilege, groups, cache 60s
- API /api/v2: pages/collections/properties/groups/users/audit (401/403/404/400)
- Guards board.py + collections.py (404/403, admin/owner bypass)
- Tests 21/21 (inherit/restricted/private, grant, revoke, batch, group, audit)
- Docs + ROADMAP + CHANGELOG + VERSION 6.1.0
2026-09-19 22:54:16 -04:00
brunoandFlowDeck bf3d1ac0cc feat: v5.14.0 Synced blocks - block created once, edited everywhere
FlowDeck CI / lint (push) Failing after 57s
FlowDeck CI / test (push) Successful in 6m34s
FlowDeck CI / docker (push) Successful in 59s
- Table synced_blocks (source of truth) + page_synced_blocks (references)
- Migration 15 + service app/services/synced_blocks.py
- API endpoints: CRUD synced blocks, add/remove page references
- Editor: /synced slash command, synced block rendering with badge
- Realtime: _propagate_synced broadcasts updates to all referencing rooms
- Export: synced blocks resolved in Markdown/HTML/PDF
- 18 tests in tests/test_v514_synced_blocks.py

Co-authored-by: FlowDeck <[email protected]>
2026-09-17 20:08:09 -04:00
bruno 41c1d315d3 feat(workspace,editor): creation dans le dossier courant + sync live du titre (sidebar/header)
FlowDeck CI / lint (push) Successful in 54s
FlowDeck CI / test (push) Successful in 5m50s
FlowDeck CI / docker (push) Successful in 47s
- creation fichier/dossier a la racine du dossier courant ou d'un dossier cible (context-menu, boutons de survol) via createPage/showCreateFolderModal(parentId)
- instances de modeles en tant qu'enfant d'un dossier (parent_id) ; nom vide -> 'Untitled'
- sidebar de la librairie rafraichi apres delete/move/duplicate/rename (_syncSidebar)
- editeur: le titre se synchronise en direct dans le sidebar, le breadcrumb et l'onglet (pages et fichiers) et persiste via PUT /board/api/pages/{id}
2026-09-14 17:05:03 -04:00
bruno 4038e9bdad fix(editor): repair block identity for template pages (duplicate/reordered lines)
FlowDeck CI / lint (push) Successful in 52s
FlowDeck CI / test (push) Successful in 5m46s
FlowDeck CI / docker (push) Successful in 47s
Template-created pages (weekly report, project doc, meeting notes, to-do
list, ...) were persisted without block ids. The editor assigned ids
client-side, but the realtime room loaded the raw id-less content and sent
it back on 'sync'; applySync then merged id-less server blocks with local
blocks, producing data-bid='undefined' collisions and duplicated/shuffled
lines as soon as the user edited. Editing an empty page was unaffected
because the server state was empty.

Fixes:
- board.use_page_template: materialize unique block ids (recursively) when
  instantiating built-in or user templates.
- realtime_server: unique block_id() (uuid) + ensure_block_ids() on room
  load and on insert ops.
- editor: recursive ensureBlockIds() in init; gtTok() now restores
  [[fddate:...]] tokens (date chips survived as labels before).
- realtime client: applySync() normalizes ids, no longer appends unknown
  local blocks (duplication), and keeps local content when server is empty.

Tests: pytest (templates + realtime) and Playwright e2e covering to-do
list, weekly report date chip, Enter ordering and legacy id-less repair.
2026-09-14 11:45:44 -04:00
bruno 334a937507 fix(templates): persist workspace context so template pages actually save
FlowDeck CI / lint (push) Successful in 51s
FlowDeck CI / test (push) Successful in 5m46s
FlowDeck CI / docker (push) Successful in 46s
Pages created via the page-template picker (weekly report, project doc,
meeting notes, ...) previously got workspace_id=NULL and workspace=login,
so they never appeared in the active local/Gitea workspace tree — they
looked like they 'didn't save' even though the row existed.

Frontend: _useTemplate now sends the active workspace context (workspace_id
or workspace key), mirroring _createPlainPage.
Backend: use_page_template resolves/validates the workspace and persists
both workspace and workspace_id on the new page.
2026-09-14 11:03:21 -04:00
bruno 9dfc38706c feat(wiki,templates): v5.11.0 wiki-links & mentions + v5.12.0 templates & page lock (release 5.12.0)
FlowDeck CI / lint (push) Successful in 50s
FlowDeck CI / test (push) Successful in 5m41s
FlowDeck CI / docker (push) Successful in 45s
v5.11.0 Wiki-links & mentions de page :
- tokens [[fdpage:ID]] / [[fddate:ISO]] dans le texte des blocs,
  service app/services/wiki_links.py (labels, rendu HTML, extraction)
- taper [[ ouvre le picker de pages (recherche floue, clavier) ;
  le menu @ gagne les sections Pages et Date (today/tomorrow/YYYY-MM-DD)
- chips atomiques contenteditable=false relues en tokens par gtTok()
  (autosave/drag/undo preservent les liens) ; renommage propage via
  GET /board/api/wiki/titles ; backlinks reconnaissent les tokens ;
  page publique rend les chips (echopee)

v5.12.0 Templates & verrouillage :
- template picker global sur + New page : 5 built-in
  (app/services/block_templates.py) + templates perso
  (table page_global_templates, migration 13)
- POST /board/api/page-templates (save current page) + /{id}/use
  (instantiate, id 0 = built-in par cle)
- page lock : POST /api/pages/{id}/lock, garde _ensure_page_editable
  -> 423 en ecriture pour les non-privileged, deblocage par
  locked_by ou admin seulement (403 sinon), banniere + read-only UI
- full-width / small text par page (pages.full_width/font_small,
  POST /api/pages/{id}/options, classes CSS)
- migration 13 : is_locked, locked_by, full_width, font_small,
  page_global_templates

Tests : tests/test_v511_v512_wiki_templates.py (15) ; suite complete
538 verte ; ruff OK ; node --check des templates JS OK.
2026-09-14 06:38:09 -04:00
bruno da3e09c215 feat(icon): Notion-style icon picker + custom workspace emojis; fix side peek file content & ctx menu overflow
FlowDeck CI / lint (push) Successful in 45s
FlowDeck CI / test (push) Failing after 3h6m55s
FlowDeck CI / docker (push) Skipped
2026-09-12 20:05:53 -04:00
bruno bdc15c7328 feat(v5.5.0): Embeds & Media riche - universal embeds, bookmark cards, lightbox, inline previews, cover & icon
FlowDeck CI / lint (push) Successful in 44s
FlowDeck CI / test (push) Successful in 4m10s
FlowDeck CI / docker (push) Successful in 37s
- embeds.py: provider detection/rewrite (YouTube, Vimeo, Figma, Maps, Docs,
  Loom, CodePen, Miro, Spotify, SoundCloud, Twitch, X/Twitter, Pinterest,
  Office) + resolve_embed/inline_kind/provider; POST /board/api/embed/resolve
- editor resolves pasted URLs and caches embed_src (persisted); renderer,
  public pages and MD/HTML/PDF exports prefer embed_src
- og_fetcher.py: robust meta parsing (any attribute order), favicon,
  injectable transport, network-safe fallback
- image lightbox with keyboard nav (arrows/Esc) in editor and public pages
- inline PDF/video/audio previews
- cover (URL or upload) & page icon endpoints
- fix broken editor API paths (/api/pages -> /board/api/pages) for cover,
  icon, versions, backlinks, import, move and OG metadata
- 47 tests in tests/test_v55.py; full suite 444 green; ruff clean
- version 5.11.2
2026-09-12 09:40:50 -04:00
bruno ba363eaee9 feat(v5.2.0): finalize Infrastructure & Polish (tests isolation, xdist, lint, CI)
FlowDeck CI / lint (push) Successful in 43s
FlowDeck CI / test (push) Successful in 4m2s
FlowDeck CI / lint (pull_request) Successful in 42s
FlowDeck CI / test (pull_request) Successful in 4m3s
FlowDeck CI / docker (push) Successful in 1m2s
FlowDeck CI / docker (pull_request) Successful in 35s
tests/conftest.py: mutate the settings singleton (instead of rebinding) so DB + backup dir are isolated per test -> pytest-xdist safe.
Real backup tests (snapshot/prune/admin API) and OAuth mock tests (Gitea/GitHub/link) replace the previous skips.
init_db() now also creates webhook_subscriptions (full schema without the FastAPI lifespan).
ruff check is clean; .eslintrc.json migrated to eslint.config.mjs (flat config).
CI: lint job (ruff + eslint), parallel tests (-n auto), run on every branch push.
VERSION 5.11.1.
2026-09-11 23:36:53 -04:00
bruno 6fe5d2f723 feat(v5.10.0): v5.4.0 + v5.5.0 features — backlinks, page/collection duplication, trash purge, version history, markdown import, embed/bookmark/video/audio blocks, cover & icon, OG metadata
- Migration v7: page_versions table + pages.cover_url/page_icon columns
- Trash service (purge_expired 30-day) + daily scheduler
- Board API: duplicate page, backlinks, versions (snapshot/list/restore), cover/icon upload, markdown/file import
- Collections API: duplicate collection (deep copy properties/views/pages/data sources)
- Export service: render embed/bookmark/video/audio in markdown/HTML/PDF/public
- Public page renderer: cover image + icon
- Editor: slash commands for media blocks, lightbox, version history UI, backlinks panel, cover/icon picker, import modal
- OG metadata endpoint for bookmark cards
- 386 tests passing
2026-09-11 08:56:48 -04:00
brunoandBruno 5c350ff8f6 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]>
2026-09-10 23:47:35 -04:00
bruno 3b27c57230 feat: badge emoji partagé (noir & blanc) dans l'arborescence + sidebar «Par moi» inclut is_shared
FlowDeck CI / test (push) Failing after 18s
FlowDeck CI / docker (push) Skipped
- board/dashboard: helper _load_shared_sidebar_pages intégrant les pages marquées is_shared directement
- _workspace_tree_macro: badge 👥 N&B sur les fichiers partagés du tree du sidebar
- local_workspace: badge 👥 en noir & blanc
- static/css/app.css: style .page-shared-badge
2026-09-08 13:57:35 -04:00
bruno 0112a5b5d8 v5.5.0 Partage v2 : fenêtre Share rechargée, sidebar « Par moi »/« Avec moi », Library dir=made/received, badge 👥 + indicateurs
FlowDeck CI / test (push) Failing after 18s
FlowDeck CI / docker (push) Skipped
- Correctif : la fenêtre Share rechargait ses partages à chaque ouverture (before: liste vide après refresh)
- Sidebar « Shared » scindée en sous-groupes « Par moi » (partages nominatifs + liens) et « Avec moi » (partages reçus), sans doublons
- Bibliothèque : filtre « Tous / Par moi / Avec moi » sur l'onglet Shared ; /api/library/shared?dir=made|received|all + share_dir par élément
- Document : bouton barre affiche « 👥 Shared ▾ » quand la page est partagée (membre, lien ou publiée), recalculé à la volée
- Workspace local : emoji 👥 juste avant le nom des fichiers partagés (is_shared exposé par /api/local-workspace/tree)
- Tests +6 (tests/test_sharing.py) : direction made/received/all, fallback dir invalide, rendu pageIsShared ; suite 325 verte (+3 PDF pré-existants)
2026-09-08 11:52:29 -04:00
bruno f8df0e13b5 feat(automations): moteur de regles if-this-then-that (v5.1.0 roadmap, bump v5.2.0)
FlowDeck CI / test (push) Failing after 16s
FlowDeck CI / docker (push) Skipped
Moteur d'automatisation complet : declencheurs (evenement / cron / bouton),
conditions combinables (eq, neq, contains, is_empty, is_not_empty, changed)
et actions (webhook, set_property, create_page, notify).

- Migration v5 : tables `automations` + `automation_runs` (avec index).
- Service `app/services/automations.py` : fire_event, cron_due (`*/N`,
  minute fixe, @hourly/@daily), scheduler de fond dans le lifespan.
- Router `app/routers/automations.py` : CRUD `/workspace/automations`,
  historique des executions, run manuel + run bouton `/api/automations/{id}/run`
  (exempt CSRF, comme `/api/agent`).
- Hooks d'evenements dans collections.py / board.py / workspace.py
  (collection.* et page.* : created/updated/deleted/moved).
- Bloc `button` dans l'editeur (menu slash) : declenche une regle au clic,
  picker d'automation inline, serialisation automations_id/name.
- Panneau Automations dans le Settings (creer/editer/activer/desactiver/
  lancer/supprimer + historique), collection scope + JSON conditions/actions.
- 11 tests `tests/test_automations.py` ; suite complete pytest 289 verte.
- Version bump 5.2.0 (VERSION, app/main.py, CHANGELOG).
2026-09-07 23:12:51 -04:00
bruno 814dbe8c2e feat(content): v4.6.0 Content Blocks enrichis — Callout, TOC, Math (KaTeX), Toggle, Multi-colonnes
FlowDeck CI / test (push) Failing after 20s
FlowDeck CI / docker (push) Skipped
- Ajout blocs enrichis dans l'éditeur: callout (avec sélecteur d'emoji), table of contents (ancres), math (LaTeX/KaTeX), toggle lists (enfants collapsibles), multi-colonnes
- Intégration KaTeX 0.16.11 self-hosté (JS/CSS/fonts) — aucun CDN externe
- Rendu des nouveaux blocs dans les pages publiques (/p/<slug>): TOC, KaTeX, colonnes, toggle <details>
- Persistance 'children' (colonnes/toggle) + export Markdown étendu
- Sidebar customization par utilisateur (config API + colonne sidebar_config)
- Correctif test_views_calendar: parsing des query params year/month (le défaut ouvrait sur le mois courant)
- Tests: 184 passing
2026-09-03 00:05:33 -04:00
bruno c586513b03 feat(database): inline embed + Open button + side peek + title placeholder
FlowDeck CI / test (push) Failing after 4s
FlowDeck CI / docker (push) Has been skipped
- Page title placeholder: new pages created with empty title, CSS :empty::before shows 'New page' in gray
- Database is now inline embed within page (not full-page replacement)
- Embed block 'collection' renders database table via FlowDeckDB.renderInto()
- Open button in column 1 of each row opens page in side peek panel
- Side peek: iframe loading /pages/{id}?embed=1, close/fullscreen/resize
- DB scripts refactored as reusable DBInstance + global FlowDeckDB API
2026-07-22 16:52:19 -04:00
bruno cd6dac9ea6 embed mode: pass embed_mode to context, add conditional body class + CSS
FlowDeck CI / test (push) Failing after 4s
FlowDeck CI / docker (push) Has been skipped
- board.py: pass embed_mode=True to template context when ?embed=1
- base.html: <body class="embed-mode"> when embed_mode is true
- CSS: body.embed-mode hides .sidebar, .topbar, .unified-header,
  .header-actions; removes app-layout margin/padding; adjusts editor
  padding and max-width for peek panel
- Sidebar and header now properly hidden in iframe via CSS
2026-07-22 10:56:29 -04:00
bruno afe670bdd3 peek panels: reusable editor module — shared _page_editor_content.html + embed template
FlowDeck CI / test (push) Failing after 4s
FlowDeck CI / docker (push) Has been skipped
Architecture for zero code duplication:
- Extract page editor HTML into _page_editor_content.html (shared include)
- page_editor.html includes it (same behavior as before)
- page_editor_embed.html includes it with empty topbar (no header in peek)
- board.py: ?embed=1 renders page_editor_embed.html

Peek panels now load /pages/{id}?embed=1 in iframe:
- Editor renders without sidebar or header breadcrumb/actions
- Full editing capability (blocks, markdown, slash commands, save)
- Same code — one change to _page_editor_content.html updates everywhere
2026-07-22 10:50:02 -04:00
bruno 514b1da9a7 Revert "peek panels: load full page editor in iframe + resize handles"
This reverts commit d13f13785b.
2026-07-22 10:32:33 -04:00
bruno d13f13785b peek panels: load full page editor in iframe + resize handles
FlowDeck CI / test (push) Failing after 5s
FlowDeck CI / docker (push) Has been skipped
- Add ?embed=1 to /pages/{id} route, passes embed_mode to template
- base.html: embed-mode class hides sidebar/topbar, removes margins
- Replace peek-body content with iframe loading /pages/{id}?embed=1
  in both library.html and local_workspace.html
- Remove async content loading (now editor handles rendering)
- Add resize handle on left edge of peek panels (both library + local-ws)
  - Click without drag: closes panel
  - Click + drag horizontal: resizes panel width (300px to 90vw)
  - Width persisted in localStorage (fd_peek_width)
- 100% code reuse: editor runs same /pages/{id} with embed flag
2026-07-22 10:10:21 -04:00
bruno 53d31572e6 fix: sidebar workspace tree + pages DB — emojis → outlined icons
Root cause: 3 sources d'icônes colorées dans le sidebar:
1. _workspace_tree_macro.html L28: {{ page.icon }} raw → filtré via valid_icons
2. db.py: DEFAULT '📄' → 'file'
3. board.py _file_icon(): 15 émojis → 'file'/'edit'/'image'
4. board.py/dashboard.py: JSON icon: 📄/📁 → file/folder
5. base.html L931: JS favorites icon → validNames filter
6. base.html render_tree_item: valid_icons list étendue + fallback

Migration: nouvelles pages utilisent 'file'/'folder'. Pages existantes
avec émojis → les templates utilisent le fallback automatiquement.
143 tests passent
2026-07-21 09:28:28 -04:00
bruno 9e45ea8870 feat: version dynamique via fichier VERSION
- Fichier VERSION à la racine (4.0.3)
- _get_app_version() avec cache dans dashboard.py
  - Lecture depuis VERSION (dev) ou /app/VERSION (Docker)
  - Fallback '0.0.0' si fichier absent
- board.py: import _get_app_version depuis dashboard
- Les deux _sidebar_data() remplacent '4.0.3' par _get_app_version()
- Déploiement: changer VERSION → mettre à jour sans toucher le code
- 143 tests passent
2026-07-21 07:58:24 -04:00
bruno b835dd6b5e fix: WORKSPSACES section + remaining emoji icons replaced
FlowDeck CI / test (push) Failing after 4s
FlowDeck CI / docker (push) Has been skipped
- base.html: séparateur entre version et Log out, _local_workspaces_for_user
- board.py: _local_workspaces_for_user helper, app_version, fix emoji icons
- dashboard.py: _local_workspaces_for_user helper
- library.html: 📚📁📄🕒⭐👥🌐🔒📦 → SVG + icon map
- workspaces.html: 📁🔍🗑📝 → SVG
- local_workspace.html: page_icon, empty states, icon functions → SVG
- settings.html: 👤🔔 → SVG nav icons
- _icons.html: +'bell' icon
- CSS: .nav-icon-inline, .empty-state-icon
- 143 tests passent
2026-07-20 20:58:02 -04:00
bruno b2486a6e11 fix: sidebar workspace section — hide create buttons quand aucun workspace actif
- Ajout flag has_active_workspace dans _sidebar_data (dashboard.py + board.py)
- Template base.html: quand has_active_workspace=False:
  - Titre section → '📁 No workspace open'
  - Boutons New File/New Folder cachés
  - Message 'Open a workspace to see your files'
- Guard JS newPageInWorkspace()/newFolderInWorkspace(): toast + redirect si pas de workspace
- 143 tests passent
2026-07-20 08:59:00 -04:00
bruno ed465333e4 feat: v4.0.2 — qualité & robustesse (session expiry UX, validation, mobile, tests)
- Session expiry: redirects ajoutent ?expired=1 → bannière "Session expired" sur login
- Page titles: titre vide → 'Untitled' par défaut (au lieu de chaîne vide)
- Error handling: try/catch dans create_page avec message user-friendly (500)
- Mobile responsive: sidebar slide-in, modales centrées, landing page adaptative
- 10 nouveaux tests de non-régression: landing, register, 404, validation, duplicate
- 143 tests passent (133 + 10)
2026-07-20 08:07:22 -04:00
bruno 37c8e99151 refactor: remove dead code _render_file_viewer from board.py
FlowDeck CI / test (push) Failing after 5s
FlowDeck CI / docker (push) Has been skipped
The standalone file viewer page (with '← Workspace' topbar) was dead code —
never called after the rollback. 192 lines removed, zero references left.
2026-07-18 10:03:09 -04:00
bruno 298617af58 revert: rollback _render_file_viewer wiring and URL encoding
FlowDeck CI / test (push) Failing after 4s
FlowDeck CI / docker (push) Has been skipped
Rollback des commits 2226e5e et 2534e2b — les fichiers continuent
de s'ouvrir dans page_editor.html (layout normal avec sidebar),
pas dans le viewer standalone _render_file_viewer.
2026-07-18 09:44:48 -04:00
bruno 2534e2b425 fix: PDF viewer - wire up _render_file_viewer + URL-encode filenames
FlowDeck CI / test (push) Failing after 5s
FlowDeck CI / docker (push) Has been skipped
- view_page now calls _render_file_viewer for content_format='file' pages
  instead of rendering the full page_editor template (which was the old path)
- URL-encode filenames in file_url using urllib.parse.quote() to handle
  spaces and special characters correctly
- Fixed in both board.py (viewer + page_data) and dashboard.py

The _render_file_viewer was dead code — defined but never called. File pages
were going through page_editor.html which rendered PDFs in an iframe embedded
in the editor UI. Now they get a clean standalone HTML viewer.
2026-07-18 09:34:24 -04:00
bruno d751415308 feat: add Notion-style breadcrumb navigation with dropdown menus
FlowDeck CI / test (push) Failing after 5s
FlowDeck CI / docker (push) Has been skipped
Backend:
- Added _nav_breadcrumb() to build page hierarchy chain (root → current)
- New /api/nav/menu endpoint returns workspace/folder children with has_children flag
- view_page() and view_page_root() now pass breadcrumb_items, nav_workspace_id, nav_page_id to template

Header template (_header.html):
- Replaced static breadcrumb with Alpine.js fdBreadcrumb() component
- Breadcrumb items now clickable with dropdown menus showing
2026-07-17 23:30:59 -04:00
bruno 6f1eabf91d fix: Windows path handling, light theme default, file viewer in editor, first-user admin logic
FlowDeck CI / test (push) Failing after 18s
FlowDeck CI / docker (push) Has been skipped
Config:
- Fixed SQLite path parsing on Windows (handle drive letters without prepending /)
- Changed default theme from dark to light
- Updated OAuth test credentials (gitea_oauth_client_id/secret)

Auth:
- First real user (excluding default admin with no password) becomes admin
- Changed user count query to exclude users without password_hash

File viewer:
- Removed separate _render_file_viewer() —
2026-07-17 20:38:39 -04:00
bruno 3210ac65a6 feat: Move to, Gitea context menu, Activity popover
FlowDeck CI / test (push) Failing after 4s
FlowDeck CI / docker (push) Has been skipped
Move to workspace:
- movePage() opens a dialog listing all workspaces via /api/workspaces
- doMove(wsId) calls PUT /api/pages/{id}/move with workspace_id
- Updated move API to accept JSON body with workspace_id for cross-workspace moves

Gitea workspace context menu:
- Right-click on any file/folder shows Rename + Delete options
- gwRename() prompts for new name, calls PUT /api/gitea/.../file
- gwDelete() confirms then calls DELETE /api/gitea/.../file
- Both trigger refreshTree() after success

Activity popover:
- 'Edited X ago' timestamp is now clickable (▾ indicator)
- Opens popover showing edited time + created date
- formatDate() helper for readable date formatting
2026-07-16 12:53:38 -04:00
bruno 8c0b55635c feat: soft-delete and undo/restore for local-workspace items
FlowDeck CI / test (push) Failing after 4s
FlowDeck CI / docker (push) Has been skipped
Backend:
- DELETE /api/local-workspace/items/{id} now soft-deletes (sets deleted_at
  instead of hard DELETE) — items go to trash, not permanently gone
- New POST /api/local-workspace/items/{id}/restore to undo a delete
- _load_workspace_pages() and _load_children() now filter deleted_at IS NULL
  so soft-deleted items disappear from sidebar and tree queries

Frontend:
- undoDelete() now calls the restore API per item (single or bulk)
- bulkDelete() now shows the same undo banner as context menu delete
- All delete paths (ctxMenu, bulk, modal) use the same soft-delete + undo flow
2026-07-16 10:28:17 -04:00
bruno 0d8507a5c7 feat: Gitea file tree visible in sidebar on ALL pages
FlowDeck CI / test (push) Failing after 4s
FlowDeck CI / docker (push) Has been skipped
- base.html Alpine: added giteaWorkspace, giteaOwner, giteaRepo data
- loadGiteaTree() fetches /api/gitea/.../tree and renders in sidebar
- board._sidebar_data returns gitea_owner/gitea_repo
- dashboard._sidebar_data returns gitea_owner/gitea_repo
- alpine:initialized triggers loadGiteaTree on every page load
2026-07-14 22:05:53 -04:00
bruno b83d9b6d35 fix: board._sidebar_data now handles Gitea workspace cookie — tree persists across all pages
FlowDeck CI / test (push) Failing after 4s
FlowDeck CI / docker (push) Has been skipped
Root cause: board._sidebar_data tried int(gitea:owner:repo) → ValueError → lost context
           Library, Trash, page editor pages showed empty workspace tree

Fix: board._sidebar_data now mirrors dashboard._sidebar_data logic:
     - Detects gitea: prefixed cookie
     - Preserves active_ws_name, workspace_key, gitea_workspace
     - Loads local_ws_id for mirror workspace
     - Tree stays visible on /library, /trash, page editor, etc.
2026-07-14 21:13:37 -04:00
bruno 29ef0fb054 feat(v4.0): GitHub routes + CSRF exemptions for share/publish
FlowDeck CI / test (push) Failing after 3s
FlowDeck CI / docker (push) Has been skipped
- app/routers/github_routes.py: /api/github/status + disconnect
- CSRF: ajout /api/github, /api/pages, /api/recents aux exclusions
- main.py: registration github_router
2026-07-14 12:19:37 -04:00
bruno bd6fd62734 feat(v4.0): Library tabs + Sidebar OAuth badge + Sharing routes
FlowDeck CI / test (push) Failing after 4s
FlowDeck CI / docker (push) Has been skipped
app/routers/library.py: /api/library/recents|favorites|shared|published|private|workspace
app/routers/sharing.py: /api/pages/{id}/share|publish + page_shares
app/templates/base.html: OAuth badge 🦎/🐙, '→ Library' buttons
app/templates/library.html: page avec tabs + filtres source
app/routers/dashboard.py: auth_method + github_linked dans context
tests/test_app.py: tests library + sharing + recents
2026-07-14 12:16:28 -04:00