26 Commits
Author SHA1 Message Date
bruno afbc236cc2 fix: navigation partielle Alpine sans course + scripts de page idempotents (v7.45.2)
FlowDeck CI / docker (push) Successful in 1m51s
FlowDeck CI / lint (push) Successful in 1m57s
FlowDeck CI / test (push) Successful in 15m16s
- app.js : x-ignore sur .main-wrapper au swap fdLoad (nœud remplacé
  uniquement), démontage unique quand tous les <script src> ont exécuté
  (load/error + filet 4s) via Alpine.initTree idempotent (_x_marker) —
  fini les cascades « Undefined variable » (éditeur) et « reading 'has' »
  ($store.fdCtx) : la zone s'initialisait avant composants et stores.
- 5 scripts de page gardés contre la ré-exécution (SyntaxError
  « Identifier 'LW' has already been declared » à la 2e visite
  partielle) : local_workspace, board, settings, database_table,
  page_editor_realtime.
- if (window.Alpine) → Alpine.data immédiat sinon listener alpine:init
  (déjà passé sur swap) : 8 scripts + 7 templates inline.
- app.css : purge des 7 blocs @font-face Inter orphelins (fichiers
  inexistants → 302 HTML → « Failed to decode downloaded font »).
- Gates : e2e/regression_editor_mount + e2e/regression_partial_nav
  (7 pages x complet/1er/2e passage) + regression_logout_dnd + smoke
  = 6/6 verts sur l'image rebuildée · pytest 1094 passed · ruff OK ·
  OpenAPI 7.45.2.
2026-10-02 21:29:46 -04:00
bruno c20aeaada1 fix: logout servi « hors ligne » par le SW + drag&drop upload en 403 CSRF (v7.45.1)
FlowDeck CI / docker (push) Successful in 1m51s
FlowDeck CI / lint (push) Successful in 1m56s
FlowDeck CI / test (push) Successful in 15m11s
- SW : le fetch event de navigation arrive en redirect:'manual' → toute 302
  du serveur (logout → /auth/login) se lisait opaqueredirect (status 0) →
  « bad status » → page hors ligne. networkFirst rejoue la requête en
  redirect:'follow' et sert une 302 synthétique vers l'URL finale (Chromium
  refuse une response 'redirected' servie à une navigation → ERR_FAILED).
  timeoutFetch annule désormais réellement (AbortController branchée).
- Local workspace : _doUpload (upload/upload-folder) et toggleFavorite
  n'envoyaient pas X-CSRF-Token → 403 systématique depuis A19 (derniers
  appels mutants du front, balayage complet refait).
- Porte : e2e/regression_logout_dnd.spec.js (2 tests verts, joués sur
  l'image rebuildée) · pytest 1094 passed · ruff OK · OpenAPI 7.45.1.
2026-10-02 17:41:07 -04:00
bruno 6d7af3fb64 feat: éditeur visuel d'automations (pipeline steps) + fix CSP multi-instructions (v7.45.0)
FlowDeck CI / docker (push) Successful in 1m51s
FlowDeck CI / lint (push) Successful in 1m56s
FlowDeck CI / test (push) Successful in 15m9s
Added — Settings → Automations, pipeline visuel (API steps v7.0) :
- Cartes ordinees (trigger/condition/delay/action) : resume + edition
  TYPÉE par kind/type (datalist evenements, 7 ops, 8 types d'action avec
  leurs champs reels), ajout/edition/suppression/haut-bas via
  POST/PUT/DELETE /workspace/automations[/steps]/...
- ✨ Convertir le JSON en pipeline (legacy → steps ordonnes) ; textareas
  JSON masques des qu'un step existe.
- Gate E2E « editeur visuel de steps » : creation → edition → ajout →
  carte « Action · webhook » sous CSP reel.

Fixed (trouve par le gate) — 51 expressions Alpine MULTI-INSTRUCTIONS
(`a=1; b()`) = interdites par le parseur CSP (une seule expression par
directive ; ';' = token inattendu) — INVISIBLE pour le scan par tokens :
- Conversion en methodes dans 11 fichiers : nav settings x8 (navTo),
  menu section base x7 (closeAndSetCount/Move/...), parts+editeur x13
  (setSharePerm, more*, markAndSave...), breadcrumb x5 (hover*/goClose),
  library/local x6 (menus popup), board x3 (pickStatus/...), ctx-menu x2
  (addTagAndClear), agent/card/gitea/workspaces x6.
- Scanner dedie scan_semi (inventaire ';' hors chaines) ajoute au lot.

Verifs : 39 templates Jinja parse OK · scan expressions = 0 incompatible
(4 faux positifs en chaines) · **E2E 8/8** · suite **1094/1094** ·
ruff OK · docs a jour
2026-10-02 16:56:29 -04:00
bruno de751ffe35 feat: A20 TERMINÉ — Alpine en build CSP, unsafe-eval retiré de la CSP (v7.43.0)
FlowDeck CI / test (push) Successful in 15m24s
FlowDeck CI / lint (push) Successful in 2m1s
FlowDeck CI / docker (push) Successful in 1m52s
La bascule A20 phase 3 :
- static/js/alpine.csp.min.js (build officiel @alpinejs/csp, 0
  eval/new Function, parseur maison) servi partout : base.html,
  import.html, welcome.html + entree sw.js (cache bump v8).
- CSP : script-src 'self' 'nonce-…' — unsafe-eval SUPPRIMÉ (ne servait
  plus qu'Alpine standard). htmx allowEval:false deja pose (v7.37).
- Assertion test inversée : assert "'unsafe-eval'" not in script_src.
- Scan statique final sur TOUS les templates : 0 expression incompatible
  (4 residus = faux positifs dans des chaines de texte).

Pré-requis réunis par les lots 1-3 : 12 surfaces migrées + gateées
(csp_preview), registres Alpine.data, x-html → x-init+Alpine.effect,
délégués window.E, partage d'état lexical, bug topbar corrigé.

Verifs : suite **1094/1094** · ruff OK · eslint 0/0 · **E2E 7/7 sous
CSP reel** (script-src sans unsafe-eval verifie sur l'instance).

Hors gate (scan propre, gitea down) : board/table_view/teamload/
card_detail → à vérifier au premier usage avec gitea remonté (noté
ROADMAP/CHANGELOG).
2026-10-02 16:00:12 -04:00
bruno 6914780f24 feat: A20 phase 3 LOT 3b — gitea + agent + éditeur verts en CSP (v7.41.0)
FlowDeck CI / lint (push) Canceled after 0s
FlowDeck CI / docker (push) Canceled after 0s
FlowDeck CI / test (push) Canceled after 0s
Changed :
- gitea_workspace : x-data="giteaWorkspace" → appel giteaWorkspace(),
  new Date(…) → fmtGwDate(pp), x-html icône arbre → bindGwIcon (x-init +
  Alpine.effect).
- agent_panel : x-html markdown → bindMarkdown($el, m) (effet reactif).
- page_editor : les 12 sites window.E du topbar right_actions →
  délégués appState (edCall('…') x6, edTimeAgo, edCommentCount, edShared,
  bindStar — les 2 branches du ternaire favorited étaient identiques) ;
  + 3 sites dans _page_editor_content (edCall commentOnSelection,
  openBacklink, fmtImportSize, bindIconHtml). Garde Jinja : quotes \' dans
  le set délimité par ' (quote nue = 500).
- Gate éditeur (csp_preview) : création collection → /pages/{id},
  délégués + editorState liés, filet 0-erreur.

Fixed :
- x-html iconHtml() du contenu éditeur = directive INTERDITE sous build
  CSP (attrapé par le filet) → x-init + Alpine.effect.

⚠️ BUG pre-existant identifie (pas introduit ici) : les right_actions du
topbar sont servis ÉCHAPPÉS sur TOUTES les pages (entities &#34;/&lt; —
boutons Share/Star/Settings en texte brut). _header:141 a bien |safe,
ENV standard, rendu local = PARSED ; cause serveur à cerner → suivi
ROADMAP dédié. Le gate éditeur n'asserte donc pas la présence boutons.

suite **1093/1093** · ruff OK · E2E **7/7** (5 csp_preview + 2 smoke)
· docs a jour
2026-10-02 15:15:26 -04:00
bruno 3cab76fed5 feat: A20 phase 3 LOT 2 — settings + local workspace verts en CSP preview (v7.39.0)
FlowDeck CI / lint (push) Canceled after 0s
FlowDeck CI / test (push) Canceled after 0s
FlowDeck CI / docker (push) Canceled after 0s
Ajout :
- 2 gates csp_preview de plus : settings (composant lie, overlay visible)
  et local workspace (recherche focalisee via Alpine.nextTick, chips
  filtre en SVG via bindSvg, 0 erreur) → 3 surfaces vertes sous build
  CSP : library, settings, local workspace.

Changed :
- settings : window.history.back()/new Date(...) → methodes
  historyBack/fmtLastLogin/fmtAuditDate ; ?. → ternaires.
- local workspace : x-data="_wsInitData" → registre wsInitData() ;
  14 x-html → x-init + Alpine.effect (bindSvg/bindFileIcon/bindNodeIcon/
  bindChildren/bindPreview) ; $nextTick+$refs arrow → toggleSearch() ;
  window.FlowDeck.* → createPageAt/createFolderAt ; ?. → ternaires ;
  @contextmenu="_wsInitData.*" → appel de methode.

Piesges resolus (CHANGELOG en details) :
- snapshot ji du build CSP = valeurs globalThis au boot → l'objet mis sur
  window avant Alpine est banni (« Accessing global variables ») → objet
  porte par une CONST LEXICALE (non propriete globalThis) + factory
  Alpine.data → MEME objet partage, reactivite intacte.
- bloc preview hors div racine (structure pre-existante, masquee par le
  fallback window standard) → composant wsPreview DELEGUANT vers
  _wsInitData via Alpine.reactive (wrapper unique : les magics $nextTick
  ne sont redefinissables qu'une fois).
- .env local : RATE_LIMIT_REQUESTS=600 (rafales E2E vs 60/min par IP ;
  defaut produit inchange).

suite **1093/1093** · ruff OK · eslint 0/0 · E2E **5/5** (3 csp_preview + 2
smoke) · CSP preview ET standard = 0 erreur sur /local-workspace · docs
a jour
2026-10-02 13:24:46 -04:00
bruno 6ff88237fc feat: A20 phase 3 LOT 1 — shell + library migres, harnais csp_preview vert (v7.38.0)
FlowDeck CI / lint (push) Successful in 1m56s
FlowDeck CI / test (push) Successful in 15m32s
FlowDeck CI / docker (push) Successful in 1m51s
Ajout :
- e2e/csp_preview.spec.js — apercu CSP strict SANS deployer : le build
  officiel @alpinejs/csp (e2e/fixtures/alpine.csp.js, 0 eval) est servi a
  la place de alpine.min.js par interception Playwright ; toute expression
  que le parseur maison ne digere pas = pageerror (filet). Premiere
  surface VERTE : library (composant lie, icones SVG via Alpine.effect,
  recherche ouverte + focalisee, 0 erreur).

Changed :
- 16 composants x-data="fn()" enregistres via Alpine.data (registre =
  seule resolution du build CSP, probe « Undefined variable » ;
  scripts classiques executes pendant le parsing => alpine:init toujours
  joint) : appState, libraryPage, workspacesPage, editorState, board x4,
  settings/import/table_view/team_load/trash/workspace/welcome/accounts/
  card_detail.
- base.html (shell) migre : x-effect document.* -> syncSidebarClass(),
  $nextTick(arrow) -> initSidebarSort(), window.FlowDeck.* ->
  fdCreatePage/fdCreateFolder/fdGwRefresh, Object.keys/Math.min/
  window.innerWidth dans x-for et :style -> sidebarSections()/
  sectionMenuPos() — tout = simple appel de methode.
- x-html restants du shell -> x-init + Alpine.effect : icone agent,
  carte projet, library x3 ; recherche library -> toggleSearch()
  (Alpine.nextTick) ; openMoveSelected() pour Object.keys en expression.
- eslint : 70 warnings -> 0/0 (globals getCsrf depuis A38 ph1,
  /* exported openCardDetail */ + /* global owner, repo */, 3 ;; residuels).

suite **1093/1093** · ruff OK · eslint 0/0 · E2E 3/3 (csp_preview + smoke x2)
· docs a jour (ROADMAP ph3 LOT 1, CHANGELOG, WORKLOAD, VERSION)
2026-10-02 12:08:18 -04:00
bruno 13dc8fdaad fix: A38 phase 2 — 0 doublon de fonction globale + garde-fou (v7.34.0)
FlowDeck CI / lint (push) Canceled after 0s
FlowDeck CI / test (push) Canceled after 0s
FlowDeck CI / docker (push) Canceled after 0s
- Inventaire exhaustif des 13 noms `function NAME` définis 2+ fois
  (templates + static/js) avec scan de profondeur de brace (strings,
  comments, backticks gérés) : 12 sont déjà scopés dans des IIFEs
  depuis A27 (escHtml/flush/emit/setMeta/initials/up/esc/show/close…) —
  aucun conflit de page possible.
- Seul doublon GLOBALE = openCardDetail (corps byte-identiques ×2 dans
  board_fragment + detailed_board, fragments de vues mutuellement
  exclusifs) → dédupliquée vers static/js/app.js, 2 copies supprimées ;
  les onclick/@click des deux fragments appellent la même définition
  (owner/repo globaux fournis par board.js au moment du clic).
- test_no_duplicate_global_functions : garde-fou 0-doublon entre
  templates et static/js (scanner naïf, plafond ponytail commenté).

Reste A38 : méthodes jumelles library/local_workspace (~9-21 noms
communs, corps divergents) → fusion workspace-tree.js reportée
(réconciliation sans E2E, même logique que A39/A20).

suite **1093/1093** · ruff OK · node --check vert · docs à jour
2026-10-02 09:18:04 -04:00
bruno 770fdc2b68 fix: A43 TERMINÉ + A38 phase 1 — CSRF rendu côté serveur, helper unique (v7.33.0)
FlowDeck CI / lint (push) Successful in 1m57s
FlowDeck CI / test (push) Successful in 15m20s
FlowDeck CI / docker (push) Canceled after 0s
A43 (les 4 items sont clos) :
- `__CSRF_PLACEHOLDER__` supprimé : ContextVar CSRF_TOKEN posée par
  CSRFMiddleware avant call_next (même mécanique que le nonce CSP),
  global `{{ csrf_token() }}` dans templating, base.html rend
  `{"X-CSRF-Token":{{ csrf_token()|tojson }}}` — vide si cookie absent,
  `htmx:configRequest` re-lit le cookie à chaque appel → plus jamais de
  jeton factice servi.
- Palette : `fetch('/api/search…')` SANS header (GET ∈ SAFE_METHODS →
  CSRF inapplicable) — le JSON.parse du body-attr par frappe disparaît.
- utcnow déprécié = 0 dans app/**.py ; health loggé (A25) + booléens db/
  gitea = raison ; probe réseau = voulu (test de connectivité).

A38 phase 1 (CSRF unifié) :
- `window.getCsrf()` unique dans le <head> de base.html.
- 76 lectures brutes du cookie → getCsrf() dans 13 fichiers (47 formes
  `(…||[])[1]||''`, 25 déclarations `const X = match(…)` avec conversion
  de leurs usages `X?X[1]:''` → `X` (noms collectés PAR FICHIER, jamais
  de règle globale), 4 formes espacées).
- Définitions dupliquées supprimées : card_detail.html (multi-lignes),
  database_table.js (1 ligne dans IIFE → chute sur le global).
- Les 3 variantes de base.html (IIFE getCsrf + 2 getCsrfToken) →
  `return getCsrf()`.
- welcome.html garde sa lecture locale (page autonome sans base) ;
  reste exactement 1 raw dans base.html (la déf head) + 2 dans welcome.

Test : test_csrf_server_rendered_no_placeholder (pas de placeholder +
token du hx-headers == cookie csrf_token).

suite **1092/1092** · ruff OK · node --check vert · docs à jour
2026-10-02 08:45:27 -04:00
bruno 069c438aae fix: A20 phase 2 — chart/leaflet vendorisés + connect-src fermé (v7.27.0)
FlowDeck CI / lint (push) Successful in 1m54s
FlowDeck CI / docker (push) Canceled after 0s
FlowDeck CI / test (push) Canceled after 10m41s
- Vendorisation : chart.js 4.5.1 + leaflet 1.9 (leaflet.js, leaflet.css,
  5 images marker/layer) vers static/js/vendor/ (déjà ignoré par eslint) ;
  les 3 URL CDN des vues chart/map (collections.py) pointent en local →
  la CSP n'a plus AUCUN hôte tiers dans script-src ni style-src.
- connect-src fermé : `'self' ws://{host} wss://{host}` — Host de la
  requête (uvicorn rejette déjà les Host invalides) + filtrage des
  caractères hors base URL. Le `https:` universel (canal d'exfil) et les
  ws:/wss: tout-hôtes disparaissent. Grep négatif : 0 fetch cross-origin
  côté front.
- Google Fonts : entrées CSP mortes (0 référence dans le code) retirées
  de style-src/font-src.
- img-src https: CONSERVÉ volontairement (unfurls YouTube/Vimeo… + tuiles
  OSM inénumérables) — ponytail: commenté dans security.py.

Tests : test_csp_no_cdn_and_vendor (CSP sans CDN/Google, connect-src
exact 'self' ws://testserver wss://testserver, 4 assets vendor 200,
source collections.py sans CDN) + test_view_chart_renders mis à jour
(chemin vendor). Suite complète 1090/1090 (1089 + 1).

Reste A20 : unsafe-eval (Alpine x-data + htmx hx-on/hx-vars = eval)
→ build @alpinejs/csp + couverture E2E des vues d'abord (même logique
que la décision A39).

suite **1090/1090** · ruff OK · docs à jour
2026-10-01 22:49:48 -04:00
bruno 8d0d69e7b8 fix: A27 lint terminé — eslint 0/0 (285 warnings nettoyés) (v7.25.0)
FlowDeck CI / test (push) Failing after 3h9m51s
FlowDeck CI / lint (push) Successful in 1m54s
FlowDeck CI / docker (push) Skipped
3 familles, 13 fichiers (+153/−167) :

1. no-empty ×70 = TOUS des `catch (x) {}` vides → `catch { /* volontaire */ }`
   (binding optionnel ES2019 + commentaire : passe no-empty ET
   no-unused-vars, zéro changement de comportement).

2. no-unused-vars ×171 :
   - bindings de catch inutilisés retirés (e/err/ex/e2/e3)
   - 24 lignes mortes déterministes, chaque suppression validée par assert
     sur le texte exact (`var self = this` ×8, `var lang`, `var acc`,
     `var today`, `var path/restored/files/resolved/items/clickEl`,
     `uid()`/`propName()` sans un seul appel, `.then` + `resolved++`
     compteurs jamais lus)
   - `/* exported */` sur les 10 fonctions appelées depuis les attributs
     HTML des templates (vérifiées par grep : 1 template chacune) :
     setActiveTab/kanbanBoard/filterSystem/sortSystem/newIssueForm/
     showNewIssue, importWizard, libraryPage, workspacesPage, settingsInit

3. no-undef ×44 = vrais globaux déclarés dans eslint.config.mjs
   (getSvgIcon = script inline de base.html, TextDecoder = API navigateur,
   Prism = CDN) + 2 vrais correctifs :
   - settings.js : `typeof toast === 'function'` = guard TOUJOURS faux
     (pas de toast global) → les toasts timezone/SAML ne s'affichaient
     jamais → `window.showToast` (2 sites)
   - local_workspace.js : `_wsInitData = window._wsInitData`
     (auto-affectation sans effet, global implicite) supprimé

eslint static/js : **0 erreur / 0 warning** (285 → 0) · node --check vert
sur tous les fichiers · suite **1089/1089** · ruff OK · docs à jour
2026-10-01 21:30:37 -04:00
bruno 103bc57418 fix: A27 phase 2c — database_table 1 314 L, extraction A27 terminée (v7.24.0)
FlowDeck CI / lint (push) Canceled after 0s
FlowDeck CI / test (push) Canceled after 0s
FlowDeck CI / docker (push) Canceled after 0s
- `_database_table_scripts.html` → `static/js/database_table.js` (1 314 L).
  Le Jinja du bloc était confiné à la construction de l'objet de config
  (4 clés + `{% if collection_data %}`) → config JSON `#db-config`
  null-vs-objet : `new DBInstance(container, PAGE_COLLECTION_ID, DB_CONFIG)`
  remplace les 2 branches Jinja (le `else` était déjà un literal null).
- Loader DB_CONFIG : JSON.parse du bloc, `null` si absent (parité stricte
  avec le else d'origine) ; acrlade try corrigée par node --check avant
  commit.
- 2 tests adaptés (lisaient le template source → static/js/database_table.js)
  ; `FlowDeckDB` / `db-board` / `db-cal-grid` / `db-gallery` plus dans le
  HTML → asserts sur le JS extrait.

BILAN A27 : 11 874 L extraites en 4 phases (4 243 + 2 516 + 3 801 + 1 314),
inline 13 904 → 2 022 L (-85 %), 22 fichiers static/js/*.js, node --check
vert partout, eslint 0 erreur / 285 warnings. Reste : base 1 523 L
structurel ({% block %}/{% for %} — inline par nature), ~500 L de petits
blocs hors cibles, nettoyage des 285 warnings.

suite **1089/1089** · ruff OK · docs à jour
2026-10-01 21:10:16 -04:00
bruno 45917c194d fix: A27 phase 2b — +3 801 L extraits (recette config JSON) (v7.23.0)
FlowDeck CI / lint (push) Successful in 1m55s
FlowDeck CI / docker (push) Canceled after 0s
FlowDeck CI / test (push) Canceled after 37m14s
4 blocs interpolés extraits avec la recette de la 2a (config JSON inline +
JS statique, substitutions sur le CORPS du bloc) :
- local_workspace.html → local_workspace.js (2 031 L, lw-config :
  current_folder_id, workspace_id)
- settings.html → settings.js (1 093 L, st-config : avatar, user
  full_name/login/email, is_admin (bool), auth_method — 2 routes rendent ce
  template, expressions « or "" » préservées pour les valeurs Undefined)
- _page_editor_realtime.html → page_editor_realtime.js (531 L, rt-config :
  SELF id/login/full_name/color)
- board.html → board.js (146 L, bd-config : owner/repo/initial_view)

BONUS sécurité : les valeurs passent par |tojson (échappement JSON explicite)
au lieu d'être interpolées dans des strings JS. Tags : config JSON (nonce
conservé) + <script src> ?v={{ asset_version }} ; loaders JSON.parse en tête
(try/catch → {}). Correctif sur le loader (accolade try en trop, caught par
node --check avant tout commit).

Cumul A27 : 10 560 L extraites (13 904 → 3 344 restantes, -76 %).
Reste structurel : base 1 338 ({% block %}/{% for %}) + database_table 1 323
(if/else) + 279 warnings eslint (12 fichiers, 0 erreur).

suite **1089/1089** · ruff OK · node --check ×4 vert · docs à jour
2026-10-01 20:37:07 -04:00
bruno ee1d46e965 fix: A27 phase 2a — éditeur 2 516 L extrait via page-data JSON (v7.22.0)
FlowDeck CI / lint (push) Canceled after 0s
FlowDeck CI / test (push) Canceled after 0s
FlowDeck CI / docker (push) Canceled after 0s
- `_page_editor_scripts.html` : le gros bloc interpolé (2 516 L) part vers
  `static/js/page_editor_scripts.js` — recette « config JSON » : les 8
  interpolations Jinja lisent `PD = JSON.parse(#page-data)`, bloc JSON qui
  EXISTAIT DÉJÀ juste avant le script (même ordre d'exécution), garde
  `__fdEditorScriptsLoaded` préservée, node --check vert.
- Route `view_page_root` : page_data enrichi de updated_at, created_at,
  user_id, is_shared (dérivé HOISTÉ : une seule expression sert le ctx ET le
  JSON) et clip_icon (macro fd_icon rendue côté serveur). workspace_key reste
  vide comme avant (jamais défini dans ce ctx → parité stricte).

8 tests adaptés à l'extraction (ils lisaient le template SOURCE) :
- test_ai_writing ×2 (+ helper _read_js), test_pwa_offline,
  test_v511 front_end_wired, test_v55 ×3 → lisent le JS extrait
- test_page_editor_renders_page_is_shared → parsing du JSON #page-data
  (`is_shared is True`) — la valeur sert toujours à la page

Cumul A27 : 6 759 L extraites (13 904 → 7 145 inline). Reste : local_workspace
2 031, base 1 523 (structurel {% for %}/{% block %}), database_table 1 323,
settings 1 093, realtime 531, board 146 ≈ 6 653 L + 120 warnings eslint.

suite **1089/1089** · ruff OK · docs à jour
2026-10-01 16:12:26 -04:00
bruno 587ec8d61b fix: A27 phase 1 — 4 243 L de JS inline extraites + eslint actif (v7.21.0)
FlowDeck CI / lint (push) Canceled after 0s
FlowDeck CI / test (push) Canceled after 0s
FlowDeck CI / docker (push) Canceled after 0s
Extraction des 7 templates dont le JS n'est PAS interpolé Jinja → 9 fichiers
static/js/*.js (4 243 lignes, -30 % du JS inline : 13 904 → 9 661) :
- agent_panel_1/_2 (bloc de 1 788 L livré sur CHAQUE page), library (1 039),
  gitea_workspace (626), _icon_picker_1/_2, _ctx_menu, import, workspaces
- UN fichier par bloc : ordre/timing identiques (pas de defer, attributs
  conservés dont data-cfasync), cache-busting via ?v={{ asset_version }}
  (source unique A40), scripts externes = 'self' en CSP (pas de nonce requis)
- garde-fou : le script refuse tout bloc contenant {{ ou {%
- vérifs : node --check vert sur les 9, 0 script inline restant dans les
  cibles, suite complète 1089/1089

Lint (la moitié « ajouter les templates à eslint » de l'audit) :
- eslint.config.mjs existait (flat v9, sans dépendances npm) mais AUCUN
  binaire eslint n'était installé → npm i -g eslint
- `eslint static/js` → 0 erreur, 120 warnings (no-unused-vars 69,
  no-empty 36, no-undef 15) sur 8 fichiers = baseline à nettoyer
- les extraits sont couverts d'office par la config (static/js/**/*.js)

Reste A27 : blocs interpolés Jinja (page_editor 2 517, local_workspace 2 031,
base 1 523, database_table 1 323, settings 1 093, realtime 531 ≈ 9 661 L)
→ extraction en 2 temps (config JSON injectée + script statique).

suite **1089/1089** · ruff OK · docs à jour
2026-10-01 15:55:09 -04:00
bruno b5207216f1 feat: v6.0.0 PWA offline support
- manifest + icones, service worker (precache, network-first, Background Sync)

- module client FlowOffline (IndexedDB, queue, delta, flush) + hook editeur

- endpoints /api/v2/sync/{delta,batch,status} + moteur de sync (conflits LWW/orpheline/copie offline)

- migrations offline_sync_queue + sync_version (triggers)

- UI offline (banner, badge sync, toasts, icone dirty) + doc /help

- tests pytest (sync, migrations, SW, offline) + E2E Playwright; bump 6.0.0
2026-09-18 13:05:40 -04:00
bruno 9836c85e24 feat(sidebar): Notion-style collapse/peek, desktop reopen button, Ctrl+\\ toggle
FlowDeck CI / lint (push) Successful in 45s
FlowDeck CI / test (push) Successful in 4m20s
FlowDeck CI / docker (push) Successful in 40s
Stop collapsing the sidebar on navigation (was hiding the nav when opening Shared pages). Show the hamburger in the top-left on desktop when collapsed, add a left-edge hover zone that floats the sidebar in and auto-collapses on mouse leave, and bind Ctrl/Cmd+\\ to toggle it.
2026-09-12 22:41:22 -04:00
bruno 61a33a7891 fix(alpine): resolve console errors on editor and agent panel
FlowDeck CI / lint (push) Successful in 45s
FlowDeck CI / test (push) Failing after 3h0m32s
FlowDeck CI / docker (push) Skipped
Initialize coverOpen/coverLoading in editorState, render import file info with x-if instead of x-show (null deref), split x-if/x-for on the agent mention menu, guard m.steps.length and use unique keys for the icon picker. Also reduce htmx swap responses to .main-wrapper so shell scripts (agent panel, base inline) are not re-executed on partial navigation.
2026-09-12 21:55:22 -04:00
bruno e707becbf8 feat(nav): partial HTMX navigation — swap main content without full reload
FlowDeck CI / lint (push) Successful in 45s
FlowDeck CI / test (push) Successful in 4m17s
FlowDeck CI / docker (push) Successful in 41s
Intercept internal links and load only .main-wrapper via htmx.ajax, keeping the sidebar/header shell in place. Adds history pushState/popstate handling, sidebar active-state resync and document title update. Moves page script blocks inside #main-content so they run on swap, and makes editor/board scripts idempotent. Routes navigateTo/breadcrumb/library/local-workspace through window.fdNavigate and fixes the duplicated navigateTo definition.
2026-09-12 21:26:56 -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 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 921f1b7bc1 fix: servir JS/CSS en local — plus de dépendance CDN externe
FlowDeck CI / test (push) Failing after 4s
FlowDeck CI / docker (push) Has been skipped
Téléchargés et servis depuis /static/js/:
- alpine.min.js (44 KB) — remplace unpkg.com
- htmx.min.js (51 KB) — remplace unpkg.com
- sortable.min.js (45 KB) — remplace cdn.jsdelivr.net
- prism.min.js (19 KB) + prism.css (1.3 KB) — remplace cdnjs

Corrige le bug 'rien ne fonctionne' quand le réseau est lent/absent:
sans CDN, Alpine.js/HTMX/SortableJS ne chargeaient pas →
tous les @click, x-show, x-data, drag-drop inopérants.
2026-07-14 07:44:28 -04:00
bruno ae3f8b473a feat: frontend error capture — diagnostic instantané pour Hermes
FlowDeck CI / test (push) Failing after 5s
FlowDeck CI / docker (push) Has been skipped
- app.js: intercepte window.onerror + unhandledrejection
  Envoie automatiquement au backend via POST /api/frontend-error
  Déduplication, throttling 1/sec, max 50 erreurs buffer local
  window.__flowdeck_errors accessible en console debug

- api.py: 2 nouvelles routes
  POST /api/frontend-error — reçoit erreurs JS, déduplique, logge
  GET /api/frontend-errors?clear=true — Hermes lit les erreurs

- csrf.py: exemption /api/frontend-error du CSRF

Usage Hermes après chaque déploiement:
  curl -s http://localhost:8080/api/frontend-errors | jq .
  → Voir TOUTES les erreurs JS en temps réel
2026-07-13 07:52:52 -04:00
bruno e2a739c4c6 feat: responsive mobile UI/UX — hamburger menu, touch support, adaptive layout
FlowDeck CI / test (push) Failing after 5s
FlowDeck CI / docker (push) Has been skipped
- CSS: 3 breakpoints (480px / 768px / 1024px) avec mobile-first design
- Hamburger button dans la topbar (visible ≤768px)
- Sidebar slide-in avec overlay + bouton close
- Kanban scroll-snap columns full-width sur mobile
- Tables scrollables horizontalement
- Touch targets 44px minimum
- Swipe-left to close sidebar
- Modal full-width sur ≤480px
- Topbar simplifiée sur mobile (cacher breadcrumbs center)
- Filter/toolbar wrap + scroll
- Donut/team-load redimensionnés
- app.js: touch events, htmx sidebar close, modal body scroll lock
2026-07-11 00:15:24 -04:00
bruno cc680a46c2 feat: Refonte UI Notion-complete — sidebar, topbar, multi-vues, dark mode
CI / test (push) Failing after 10s
CI / lint (push) Failing after 4s
- Layout 3 zones (sidebar 240px + topbar 44px + contenu)
- Sidebar: sections hiérarchiques, hover states, menu contextuel
- CSS design system Notion: #191919/#222/#333, Inter, coins 4-6px
- Vues: Kanban (groupes+colonnes), Dashboard Notion-style
- Templates: base.html refondu, card.html partial, board.html v2
- Backend: données sidebar + board Notion (groups/cards)
- Docs: ROADMAP v0.4→v1.0, WORKLOAD.md, README
- Backward compatible: conserve _issue_column pour api.py
- Images de référence + resized (analyse 2026-07-08)
2026-07-08 14:13:41 -04:00
bruno 336c7d8dfe feat: FlowDeck v0.2.0 — Kanban Gitea intégré
- Dashboard projets Gitea (user + orgs)
- Board Kanban 5 colonnes avec drag & drop (SortableJS)
- Sync bidirectionnelle colonnes ↔ labels Gitea
- Filtres milestone/label/assignee
- Notes Markdown par projet
- API REST (/api/health, /api/move, /api/col-mapping…)
- Colonnes custom, WIP limits
- Thème clair/sombre
- Cache Gitea TTL
- Docker + docker-compose
- Tests pytest
- Docs: README, ARCHITECTURE, CHANGELOG, ROADMAP
2026-07-08 08:41:31 -04:00