- Dialog right edge aligns with Share button area (right: 72px)
- z-index raised to 999 to prevent being hidden by other layers
- Max-height reduced to 65vh to fit viewport
- Access menu: !important dark background, proper option styling
- Sidebar Shared section now renders shared_pages dynamically
- board.py: shared_pages added to _sidebar_data() return
Jinja2 tried to evaluate {{ a.email[0].toUpperCase() }} inside Alpine.js
x-for template, causing UndefinedError. Wrapped in {% raw %} block.
Also changed avatars to use x-text binding instead of {{ }} interpolation.
Share panel restructured to match Notion:
- General Access: dropdown with 'Only people invited' / 'Anyone with the link'
- Invite: email + permission selector
- Publish to web: toggle + URL input + Copy link button
- Copy page link at bottom
CSS: .share-access-btn and .share-access-menu for the access dropdown
- base.html topbar wrapped in {% block topbar %} for override
- page_editor.html overrides topbar block with empty content (uses its own)
- Get Started toolbar: fixed to bottom center with gradient fade
- No more double Share/Link/Star buttons
Replicates Notion's page layout from reference images:
- Top bar: Private badge, Share button, Link copy, Star/favorite toggle, ⋮ menu
- Share modal: Publish toggle, Copy link, Invite people with permission select
- Empty page toolbar: Get started with H1/H2/H3/bullet/todo/callout/quote
- Star toggle integrated with favorites API (POST/DELETE /board/api/favorites)
- board.py view_page now passes workspace context + page_favorited
- dashboard.py view_page_root also passes page_favorited
- CSS: .page-topbar, .share-modal, .empty-page-toolbar, toggle switch, more menu
Bug: two separate x-data='{ tab }' scopes (one on tabs, one on table)
→ clicking tabs changed tab in first scope, x-show looked at second scope
Fix: single x-data wrapper around both tabs and table divs
6 files changed:
- db.py: migrate favorites table FK from collection_pages(id) to pages(id)
- board.py: add favorites API (POST/DELETE /board/api/favorites/{id}, GET list)
- board.py: _sidebar_data() now loads favorite_pages from DB via JOIN
- dashboard.py: library_page loads lib_favorites from DB (not parent_section)
- csrf.py: exclude /board/api/favorites from CSRF checks
- base.html: context menu toggles Add/Remove Favorites based on state
- base.html: favoriteIds Alpine set initialized from server-rendered favorites
- test_app.py: test_favorites_crud rewritten for new page-based favorites API
Favorites now work end-to-end:
- Right-click → Add to Favorites (or Remove if already favorited)
- Sidebar Favorites section shows favorited pages
- Library Favorites tab shows the same pages
- API: POST/DELETE /board/api/favorites/{page_id}, GET /board/api/favorites
- Sidebar data (board._sidebar_data()) no longer overwritten by library content
- New variables: lib_recent, lib_favorites, lib_shared, lib_private
- Pages categorized by parent_section column: Private/Favorites/Shared
- Recents tab shows all non-trashed pages ordered by updated_at
- library.html updated to use lib_* variables instead of sidebar data
- Favorites/Shared tabs hidden when empty (lib_has_favorites/lib_has_shared)
3 bugs fixed:
1. dashboard.py /library was registered before board.router and had no workspace context → removed old endpoint, re-added with owner/repo params using board._sidebar_data()
2. base.html navigateTo('/library') lost workspace → now auto-appends ?owner=X&repo=Y from workspaceKey
3. openLibrary(id) used bare window.location.href → now uses this.navigateTo()
Now the Library page:
- Shows pages filtered by workspace when coming from a project
- Preserves the sidebar context (no more Admin Dashboard fallback)
- Tabs (Recents, Favorites, Shared, Private) display project pages from DB
- board.html: ajout X-CSRF-Token header sur fetch /api/move et /api/issues
- card_detail.html: ajout helper getCsrf() + header sur tous les fetch POST/PATCH
- Sans ce fix, drag & drop, création issue, checklists échouent en production
- Ajout des 3 nouvelles tables pour le Database Concept
- collections: base de données abstraite, indépendante de Gitea
- collection_pages: pages génériques avec parent_id et property_values_json
- collection_views: vues configurables par collection
- Mise à jour test_db_tables_exist pour inclure les nouvelles tables
- 28/28 tests passent
- Sidebar: 7 collapsible sections (Meetings, Recents, Favorites, Agents, Shared, Private, Notion apps)
- Nav icon row: Home with text + icon, Chat/Inbox/Search icon-only, collapse on right
- Notion apps: Library, My Tasks, Trash, Help
- Footer: New chat Ctrl+O + New page buttons
- Auto-update: sidebar items reflect title changes immediately on save
- CSS: nav-icon-btn, home-btn, collapse-btn, empty-hint, outline none
- Add sort_order column to pages table for drag & drop reordering
- Rewrite _sidebar_data with recursive _build_page_tree for nested hierarchy
- Add tree toggle buttons (▶ collapsed / ▼ expanded) on parent items
- Alpine.js expandedNodes state with toggleTreeNode() for expand/collapse
- Jinja2 recursive macro render_tree_item for proper nesting + indentation
- SortableJS integration on .sidebar-items for drag & drop repositioning
- New PUT /board/api/pages/{id}/move API for persisting moves
- Auto-set sort_order on page creation (max+1 for same parent)
- CSS: .tree-toggle, .tree-toggle-placeholder, grab cursor on page-icon
- 28/28 tests pass, all endpoints healthy
- Page /accounts: profil utilisateur éditable, comptes connectés, workspace members
- API GET/PUT /api/users/me: lecture/écriture profil local (full_name, email)
- Users list from local DB, auth via Gitea OAuth2
- Imports SessionManager clean dans api.py
- sidebar items: attribut data-page-id ajouté pour ciblage DOM
- save(): après PUT réussi, met à jour .page-name dans le sidebar
- saveTitle(): reload la page (changement de titre = reload ok)
- CSRF middleware: excluded_paths utilise maintenant startswith (pas exact match)
- /board/api/pages/* exempté — les PUT depuis page_editor passent
- saveTitle() reload la page après sauvegarde → sidebar mis à jour
- pages.workspace = owner/repo (ex: bruno/flowdeck) au lieu du nom user
- Sidebar: Kanban board + 4 sous-vues avec URLs distinctes (?view=kanban|table|...)
- Cliquer sur 'Table view' dans sidebar → change la vue à droite
- Dashboard (/): sidebar montre uniquement les projets Gitea
- Board (/board/{o}/{r}): sidebar montre les pages de ce projet
- Changement de projet = changement de contexte (pages isolées)
- newPage/newSubPage passent workspace_key à l'API
- Auto-switch view depuis paramètre URL dans board.html
- /api/move retourne maintenant les labels mis à jour
- Le frontend met à jour les labels de la carte sans recharger la page
- Flash vert sur la carte pour confirmer le succès du déplacement
- Correction: résolution noms→IDs des labels via l'API Gitea (update_issue_labels attend des ints)
- Mise à jour de data-column sur la carte déplacée
- Bouton ⚙ sur chaque colonne pour configurer le mapping label Gitea
- Modal ⚙ Colonnes pour ajouter/renommer/supprimer des colonnes
- API DELETE /api/col-mapping pour supprimer un mapping
- Rafraîchit le board après chaque modification de colonne/mapping
- CSS pour les nouveaux éléments (config panel, column list)
- create_issue: utilise _issue_column() au lieu de hardcoder 'Backlog'
- update_issue_api: recalcule la colonne après changement de labels
- update_issue_api: utilise l'endpoint PUT /labels au lieu de PATCH (Gitea ignore labels dans PATCH)
- webhooks: utilise _issue_column() pour 'opened'/'reopened'
- saveIssueEdit: rafraîchit le board (HTMX) après édition au lieu de rouvrir la modale
- update_issue_labels: corrigé pour utiliser PUT /issues/{id}/labels (liste d'entiers)
Root cause: operator precedence bug with stray ')' in:
'...+(card.priority||"Medium")===p?"selected":"")+'>...
The ')+' created an unmatched parenthesis, causing SyntaxError
that stopped ALL JS event handlers (dblclick, drag-drop, buttons).
Fix: replaced string concatenation with template literals ('')
throughout renderCardDetail, eliminating escape/quoting issues.
- Labels now rendered as checkboxes (not single-select dropdown)
in both create modal and edit form
- ALL_LABELS and ALL_MILESTONES passed as JSON to JS
- Double-click on empty column area opens create modal
(pre-fills with that column name in title placeholder context)
- '+ Issue' button also opens the same modal
- Removed old inline new-issue-form (Alpine x-show), replaced with modal
- Create modal: title, body, multi-label checkboxes, milestone, assignee
- Edit form: title, body, multi-label checkboxes, assignee
- CSS: .label-checkboxes + .label-cb styles
Replaced bare prompt() with proper edit form in card modal:
- showEditForm: renders title input + body textarea + assignee field
- saveIssueEdit: PATCH /api/issues/... with title, body, assignee
- 'Annuler' button returns to card detail view
- Old editIssueInline (prompt only) removed
- Added .form-label CSS style
- base.html: inject CSRF token into HTMX hx-headers via inline script
- htmx:configRequest event updates CSRF on every request
- HTMX loaded synchronously (no defer) so config runs before body parsed
- Notes save now works, issue creation works, all POST protected
1. Refresh button no longer doubles header
- Added ?fragment=1 param to filter/refresh HTMX requests
- Board route returns board_fragment.html (columns only) for fragment requests
- Refresh button uses window.location.reload() for full page reload
2. No more raw JSON in header
- base.html: user info rendered server-side via template variable
- board.py: decodes session cookie, passes user to template
- Dashboard/board templates show Login/Deconnexion based on auth state
3. New issue form works
- Replaced HTMX post with JS fetch (handleNewIssue)
- Adds CSRF token to form submission
- Reloads page on successful creation
- Cleaner error handling
Gitea's /api/v1/user/repos already includes all org repos the user
has access to. Fetching org repos separately added duplicates.
Removed the redundant org-repo loop from dashboard and /api/projects.
Also fixed template to use repo.owner.login instead of _org hack.