9 Commits
Author SHA1 Message Date
bruno b9a776b626 fix: le favicon est enfin le vrai logo, plus un placeholder (v7.69.2)
FlowDeck CI / lint (push) Successful in 1m34s
FlowDeck CI / test (push) Failing after 3h6m20s
FlowDeck CI / docker (push) Skipped
Le favicon était un carré bleu #2383E2 avec les lettres « FD » écrites en
<text> SVG : génériques, sans aucun lien avec la marque, et rendues avec la
police installée chez le visiteur (donc instables).

static/favicon.ico est désormais généré depuis assets/logo.jpg — la même
source que la bannière et les icônes PWA. 16/32/48 px dans un seul fichier,
un seul <link> pour tous les navigateurs, 4,1 Ko.

Pourquoi un fond sombre et pas le logo nu :
  le logo est blanc à ~82 % (mesuré via Pillow — 50,7 % de blanc pur, plus
  32 % de blancs proches). Posé nu, il se dissoudrait sur un onglet clair,
  qui est le cas nominal de Firefox et de Safari. Il est donc posé sur un
  carré #191919 arrondi, LE MÊME fond que les icônes PWA générées en
  v7.69.0, ce qui garde l'identité cohérente entre onglet, écran d'accueil
  et partage.

Contraste mesuré à 16 px : σ = 106 avec le fond sombre, contre σ = 49 pour
le logo seul — le fond sombre double littéralement la lisibilité à cette
taille. Le test statique verrouille les tailles embarquées dans l'ICO.

Mise à jour des 5 templates qui pointaient vers l'ancien SVG (base, import,
landing, public_page, welcome) + la liste de pré-cache du service worker.
static/favicon.svg SUPPRIMÉ : plus aucune référence dans le dépôt, le test
le vérifie pour éviter qu'il revienne.

pytest 1416 passed / 0 failed · ruff OK · OpenAPI 526 chemins / 7.69.2
2026-10-08 19:41:37 -04:00
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 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 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 c718fe06de fix: A20 (partiel) — CSP nonce par requête, unsafe-inline sort de script-src (v7.7.0)
FlowDeck CI / lint (push) Canceled after 0s
FlowDeck CI / test (push) Canceled after 0s
FlowDeck CI / docker (push) Canceled after 0s
- ContentSecurityPolicyMiddleware : nonce aléatoire par requête dans la
  ContextVar `CSP_NONCE` (posée avant `call_next` → visible des templates),
  `script-src 'self' 'unsafe-eval' 'nonce-…'` — plus aucun script inline
  sans nonce ne tourne (fin des XSS injectés en JS)
- 38 tags `<script>` des templates : `nonce="{{ csp_nonce() }}"` (passage
  scripté, vérifié : 0 restant) ; `LOCAL_LOGIN_HTML` (constante de module) :
  helper `_with_nonce()` au rendu ; collections.py : 3 scripts Python
  (chart/form/map) noncés
- `<meta name="htmx-config" content='{"inlineScriptNonce": …}'>` dans base.html
  : htmx ré-injecte les <script> des réponses boostées avec le bon nonce
- `script-src-attr 'unsafe-inline'` : les 74 handlers `onclick=` inline
  restent couverts (le nonce les aurait désactivés aussi)
- chart.js (cdn.jsdelivr.net) et leaflet (unpkg) ajoutés à script-src/style-src
  : vues chart/map déjà BLOQUÉES par la CSP depuis toujours
  (commentaire ponytail: upgrade = vendoriser puis retirer les hôtes)
- reste d'A20 : unsafe-eval (Alpine x-data → @alpinejs/csp), externalisation
  JS (A27), resserrer img-src/connect-src

test : test_csp_nonce_per_request (page base.html + page hors template,
nonce unique par requête)

suite **1037/1037** · `ruff check app tests` OK · docs à jour
2026-10-01 10:41:39 -04:00
bruno 0861f1fdbf fix: A19 (partiel) — CSRF réservé aux vrais cas d'exemption (v7.3.5)
- Scan de tous les appels `fetch` non-GET du front par préfixe : 12 préfixes
  n'ont AUCUN call site sans `X-CSRF-Token` → sortis de `EXCLUDED_PATHS`
  (`/db/`, `/workspace`, `/workspace/automations`, `/board/api/favorites`,
  `/api/pages`, `/api/recents`, `/api/notifications`, `/api/comments`,
  `/api/automations`, `/api/github`, `/api/admin`, `/api/onboarding`)
- `welcome.html` : les 2 POST `/api/onboarding/*` reçoivent le header
  (`adminFetch` prouve que `/api/admin` était déjà couvert)
- reste A19 (front à équiper) : `/api/workspace` (+`/api/workspaces`),
  `/api/local-workspace`, `/api/settings`, `/api/gitea`, `/api/agent` — 49 fetch
- tests : helper `anon_csrf()` (anonyme + CSRF valide) pour isoler les 401 de
  route du 403 middleware — 4 tests d'anonymat ajustés
- suite **1026/1026** · `ruff check app tests` OK
2026-09-30 23:38:03 -04:00
bruno 3b3e95e23a fix(cloudflare): proteger les scripts inline des pages du Rocket Loader
FlowDeck CI / lint (push) Successful in 48s
FlowDeck CI / test (push) Successful in 5m2s
FlowDeck CI / docker (push) Successful in 42s
Rocket Loader (Cloudflare) reecrit les balises script inline en type=...-text/javascript et les execute de facon differee, ce qui casse l'enregistrement des composants Alpine et l'init des pages lors d'un acces direct via le tunnel (ex. /settings ne se chargeait pas au complet). Ajout de data-cfasync=false sur les scripts inline des pages completes et des partiels du shell (settings, accounts, trash, workspace, board, gitea_workspace, welcome, import, page_editor_collection/embed, _database_table_scripts, _notification_bell, public_page, local_workspace). Les fragments charges via HTMX restent inchanges.
2026-09-13 12:35:28 -04:00
bruno cef01dffaf fix(cloudflare): prevent Rocket Loader from deferring Alpine's inline alpine:init listeners (data-cfasync=false on all Alpine-critical script blocks) — fixes partial page load on mobile behind Cloudflare 2026-09-11 17:32:31 -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