- Les skills (menu /, integres et enregistres) s'epinglent comme des bulles dans le composer, au meme titre que les mentions @; plusieurs peuvent coexister sur un meme post
- L'API /run accepte skill_ids (tableau) et l'engine injecte tous leurs prompts dans le system prompt (skill_id conserve pour retro-compat)
- Clic sur une bulle de contexte (@ document/page/base) ouvre l'element comme document courant (/pages/<id> ou /db/<id>); le bouton x retire sans ouvrir
- Envoi possible avec des skills seuls sans texte
- test_engine_multiple_skills_applied
- Composeur contenteditable : mentions inline (pastilles icône+nom), barre de contexte groupée documents/skills, sélecteur @ (icône+titre+chemin), skills intégrés + agent_skills + commandes admin via '/'
- Actions sous chaque réponse agent : copier, insérer dans la page, feedback 👍/👎
- Backend : table agent_feedback, GET /api/agent/mentions, POST /api/agent/feedback, contexte document:<id>/page:<id>/collection:<id> résolu en contenu réel
- Inclut le travail v4.14.0 (documents/espace de travail + outils + auto-titre de conversation)
- Tableaux GFM rendus comme texte brut dans les exports HTML/PDF. Ajout d'un
parseur de tableaux pipe (bloc 'table') + rendu <table> (thead/tbody,
alignement gauche/centre/droite, bordures .ftable) dans blocks_to_html;
blocks_to_markdown reconstruit un tableau pipe valide.
- Emojis 'carrés noirs' en PDF: xhtml2pdf n'embarque que des polices de base
sans glyphes emoji. Moteur PDF -> WeasyPrint (tables CSS + emojis couleur via
pango + fonts-noto-color-emoji installes dans l'image). Repli automatique sur
xhtml2pdf quand weasyprint n'a pas ses libs natives (dev Windows).
- Dockerfile: libs weasyprint (pango/harfbuzz/gdk-pixbuf/shared-mime-info) +
fonts-dejavu-core + fonts-noto-color-emoji. requirements: + weasyprint==69.0.
- Verifie en reel sur README.md: HTML = <table class=ftable> (thead/th, center);
PDF 10 pages, texte de table present, Noto-Color-Emoji embarque + pixels
colores confirmes. 199/199 tests (4 nouveaux).
- Service serveur app/services/export.py: conversion blocs vers Markdown / HTML / PDF
- 4 endpoints: /api/export/markdown|html|pdf|site/{page_id}
- Export Markdown complet (tous les blocs + images + sous-pages recursives)
- Export PDF via xhtml2pdf (pur Python, aucune lib systeme)
- Export HTML standalone self-contained (styles inline)
- Export Site: zip multi-pages (index.html + une page par sous-page)
- UI: menu 'More > Export' dans l'editeur (Markdown, HTML, PDF, Site .zip)
- Tests: 190 passing (6 nouveaux)
Le /auth/login construisait le redirect_uri avec le schéma http:// en dur
et dupliquait l'expression dans login/callback : toute URL d'accès non
enregistrée (https, reverse proxy, hostname, port différent) était rejetée
par Gitea avec 'Unregistered Redirect URI' — les 2 liens 'Connect Gitea'
et 'Register with Gitea' de Workspaces → Gitea Projects étaient touchés.
- nouveau helper get_redirect_uri(request) : override explicite
OAUTH_REDIRECT_URI (si défini), sinon schéma depuis X-Forwarded-Proto
(fallback request scheme) + hôte depuis X-Forwarded-Host (fallback Host)
- redirect_uri stocké en session à l'authorize et réutilisé tel quel dans
l'échange de code (plus de dérive entre les deux étapes)
- même correctif dans GitHubProvider.exchange_code (ignorait le paramètre)
- config : oauth_redirect_uri par défaut vide (dynamique) au lieu de
localhost:8080 en dur
- .env.example documente OAUTH_REDIRECT_URI
- 4 tests de régression (host header, X-Forwarded-Proto/Host, override env,
URL d'authorize) — 178/179 OK, l'échec restant (test_views_calendar) est
pré-existant et dépend de la date
- 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
- Migration: add collection_id column to pages table
- POST /api/pages/{id}/convert-to-database: transforms page into database
- GET /api/collections/{id}/table-data: returns props + pages for table view
- POST /api/collections/{id}/pages: creates new row in collection
- New page_editor_collection.html template for database view
- _database_table.html: Notion-style table with columns, rows, New Page, Add Property
- Alpine.js component for cell editing, new page creation, property mgmt
- CSS: complete database table styling (view bar, table, cells, modals, dropdowns)
- Frontend: createDatabase() now converts page to full-page DB (was inline embed)
Root cause: dashboard.router registered BEFORE board.router in main.py,
so dashboard's /pages/{page_id} handler got all requests and ignored
the embed parameter. board.py's embed-aware route was never reached.
Fix: add embed support to dashboard.py's view_page_root too:
- Detect ?embed=1 query param
- Select page_editor_embed.html when embed=True
- Pass embed_mode to template context
- 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
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
- Add is_task flag on collections (toggle-task API)
- New page_dependencies table with auto_shift config (blocks/blocked_by/related)
- API: GET/POST/DELETE page dependencies
- API: POST auto-shift dates based on blocking dependencies (skip_weekends option)
- 5 new tests (171 total)
- dashboard.py: quote(filename) dans file_url (espaces/caractères spéciaux)
- page_editor.html: iframe PDF → onerror fallback avec lien Open in new tab
- 143 tests passent
- 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
- main.py: lifespan insère toujours le hash de 'FlowDeck2026!' pour admin
- dashboard.py + my_tasks.py: content_html passe via {% block content %} (Jinja)
- Avant: content_html ignoré car base.html utilise des blocs, pas des variables
- 143 tests passent
- Section 'Notion apps' retirée
- Liens statiques en bas du sidebar (toujours visibles): Library, My Tasks, Trash, Help
- Nouvelle route /help avec page d'aide (raccourcis, guide rapide)
- Trash visible pour tous (plus de condition sur auth_method)
- 143 tests passent
- 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
Les pages du workspace local ont workspace='' mais workspace_id=27.
Le filtre workspace != '' les excluait, retournant 0 items.
Ajout de OR workspace_id IS NOT NULL pour inclure les pages locales.
Simplification du frontend: le workspace tab appelle l'API standard avec workspace_id.
- Nouvel endpoint /api/library/local-workspace?workspace_id=X
interroge local_workspace_items et formate pour la Library
- Nouvel endpoint /api/library/local-workspace-children/{id}
pour l'expansion d'arborescence des items du workspace local
- Frontend: détecte workspaceId + !isGiteaActive → appelle local-workspace
- Frontend: toggleExpand route vers le bon endpoint selon source_type
- CSS hover-only: visibility:hidden/visible au lieu de opacity (infaillible)
- Workspace tab: filtré par workspace_id quand un workspace est actif
- Tab Private remplacé par Repository (visible seulement si Gitea workspace actif)
- Nouvel endpoint /api/library/repository pour le contenu Gitea
- dashboard.py passe is_gitea_workspace, owner, repo au template
- Library: ajout /api/library/children/{id} pour charger les enfants à la demande
- Library: drag handle (.drag-handle) et checkbox (.lib-checkbox) toujours visibles
- Library: SortableJS init pour drag-and-drop réordonner les rows
- Library: toggleExpand() async avec chargement des enfants depuis l'API
- Local workspace: drag handle 6-dots + checkbox toujours visible dans les tree items
- Local workspace: renderChildren() inclut checkbox + drag handle + inline rename
- Local workspace: clic sur nom de fichier = inline rename (plus navigation directe)
- Local workspace: bouton Open pour ouvrir le fichier (double-clic aussi)
- CSS: .item-checkbox toujours visible (plus opacity:0)
- CSS: .drag-handle avec opacité .45 par défaut, 1.0 au hover
1. Hover effects fixed: converted table to div-based flexbox layout
(tr/td don't support display:flex). .lib-row now properly shows
drag handle, checkbox, and OPEN button on hover.
2. Recents filter by workspace: /api/library/recents now accepts
workspace_id parameter. Template passes active_workspace_id from
sidebar context to API calls.
3. Tree hierarchy: API now enriches items with has_children via
_enrich_children() batch query — shows expand chevrons for
pages with sub-pages.
4. Rename on click: single click on title starts inline rename
(was opening page). OPEN button still opens side peek.
5. Code cleanup: extracted _enrich_children() helper to eliminate
duplicate code across 6 endpoints.
Complete rewrite of /library page to match Notion's Library design:
1. Table view with columns: Page name (with icon), Created by (avatar),
Source, Last edited time, Last visited time
2. Hover effects showing drag handle (6 dots), checkbox, OPEN button
3. Side peek panel: opens on right, shows page content preview,
close button, favorite toggle, copy link, more menu
4. Multi-selection: checkbox per row, select-all header, 'X selected' bar
with Delete and '...' menu
5. ... menu: Remove from Recents, Copy links to all, Move to, Move to Trash
6. Inline rename: double-click title → edit field
7. 6 tabs: Recents, Favorites, Shared, Published, Private, Workspace
8. Tree expand/collapse for nested pages (has_children support)
9. + Add new row at bottom
10. Search bar toggleable
API additions:
- library.py: enhanced _build_item with icon, source_label, author
- dashboard.py: new /api/pages/{id}/content, /rename, /trash endpoints
- All 133 tests pass
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.
The real route for /pages/{page_id} is in dashboard.py (no prefix router),
not board.py (prefix /board). Both routes now call _render_file_viewer
for content_format='file' pages.
- 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.