Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b2486a6e11 |
+14
-12
@@ -177,16 +177,18 @@ Propriétés custom, AI keywords, sync API, 12 tables DB
|
||||
|
||||
## 🔜 Prochaines versions
|
||||
|
||||
### v4.0.1 — Onboarding & Polish ✅ (2026-07-18)
|
||||
> **Objectif** : rendre l'app utilisable dès la première visite, sans friction. **COMPLETED**.
|
||||
### v4.0.1 — Onboarding & Polish 🎯 (priorité MAX)
|
||||
> **Objectif** : rendre l'app utilisable dès la première visite, sans friction.
|
||||
|
||||
- [x] **Landing page `/` pour visiteurs non-auth** — template `landing.html`, design Notion dark
|
||||
- [x] **Page `/auth/register` GET** — formulaire d'inscription dédié (tab register actif)
|
||||
- [x] **Redirection `/` intelligente** — non-auth → landing, auth sans Gitea → local workspace, auth+Gitea → dashboard
|
||||
- [x] **Empty states** — déjà présents dans local workspace (« Welcome to your Workspace »)
|
||||
- [x] **Page 404 stylisée** — thème Notion sombre, JSON pour `/api/*`, HTML pour le reste
|
||||
- [x] **Unifier les routes API** — `/api/pages` GET/POST → 307 redirect vers `/board/api/pages`
|
||||
- [x] **Page Settings** — lien déjà présent dans le menu utilisateur (Settings → `/accounts/settings`)
|
||||
- [ ] **Landing page `/` pour visiteurs non-auth** — page d'accueil (pas de redirect loop)
|
||||
- [ ] **Page `/auth/register` GET** — formulaire d'inscription dédié (pas juste un tab)
|
||||
- [ ] **Redirection `/` intelligente** — si pas connecté → login, si connecté sans Gitea → local workspace
|
||||
- [ ] **Empty states cohérents** — onboarding dans le local workspace vide
|
||||
- [ ] **Page 404 stylisée** — thème Notion sombre (actuellement HTML brut)
|
||||
- [ ] **Unifier les routes API** — `/board/api/pages` ↔ `/api/pages` (cohérence)
|
||||
- [ ] **Page Settings accessible** → lien dans le menu utilisateur déjà présent, vérifier fonctionnel
|
||||
|
||||
**Effort estimé** : 2-3 sessions | **Impact** : 🔴 Critique pour adoption
|
||||
|
||||
### v4.0.2 — Qualité & Robustesse
|
||||
> **Objectif** : zéro crash, zéro regression, DX impeccable.
|
||||
@@ -273,11 +275,11 @@ Propriétés custom, AI keywords, sync API, 12 tables DB
|
||||
## Résumé des phases
|
||||
|
||||
```
|
||||
v0.x–v1.x ✅ v2.x ✅ v3.0.0 ✅ v4.0.0 ✅ v4.0.1 ✅
|
||||
v0.x–v1.x ✅ v2.x ✅ v3.0.0 ✅ v4.0.0 ✅ v4.0.1 ⬜
|
||||
Base + Kanban Éditeur + Gitea UX Pro MVP Onboarding
|
||||
+ DB + Auth + Workspaces + Multi-select Accounts & Polish
|
||||
+ UI Notion + Tags + Admin + Sharing COMPLETED
|
||||
+ GitHub OAuth + Library
|
||||
+ UI Notion + Tags + Admin + Sharing PRIORITÉ
|
||||
+ GitHub OAuth + Library MAX
|
||||
|
||||
v4.0.2 ⬜ v4.1.0 ⬜ v4.2.0 ⬜ v4.3.0 ⬜ v4.4–4.10 ⬜ v5.0.0 ⬜
|
||||
Quality Blocks Export Collab Notion Pro
|
||||
|
||||
@@ -229,6 +229,7 @@ def _sidebar_data(request: Request, owner: str = "", repo: str = "") -> dict:
|
||||
gitea_workspace = False
|
||||
gitea_owner = ""
|
||||
gitea_repo = ""
|
||||
has_active_workspace = False
|
||||
|
||||
if ws_cookie and ws_cookie.startswith("gitea:"):
|
||||
# Gitea workspace: preserve context across pages
|
||||
@@ -238,6 +239,7 @@ def _sidebar_data(request: Request, owner: str = "", repo: str = "") -> dict:
|
||||
gitea_repo = parts[2]
|
||||
active_ws_name = f"{gitea_owner}/{gitea_repo}"
|
||||
gitea_workspace = True
|
||||
has_active_workspace = True
|
||||
workspace_pages = [] # loaded client-side
|
||||
# Get local workspace ID for mirror
|
||||
if user:
|
||||
@@ -262,6 +264,7 @@ def _sidebar_data(request: Request, owner: str = "", repo: str = "") -> dict:
|
||||
if row:
|
||||
active_ws_name = row["name"]
|
||||
workspace_pages = _load_workspace_pages(ws_cookie)
|
||||
has_active_workspace = True
|
||||
except (ValueError, Exception):
|
||||
pass
|
||||
local_ws_id = 0
|
||||
@@ -376,7 +379,8 @@ def _sidebar_data(request: Request, owner: str = "", repo: str = "") -> dict:
|
||||
"user": user,
|
||||
"auth_method": auth_method,
|
||||
"gitea_linked": gitea_linked,
|
||||
"github_linked": github_linked}
|
||||
"github_linked": github_linked,
|
||||
"has_active_workspace": has_active_workspace}
|
||||
|
||||
|
||||
def _extract_ai_keywords(owner: str, repo: str, labels: list[dict], body: str = ""):
|
||||
|
||||
@@ -73,8 +73,9 @@ def _sidebar_data(request: Request, repos: list[dict], include_workspace: bool =
|
||||
gitea_workspace = False
|
||||
gitea_owner = ""
|
||||
gitea_repo = ""
|
||||
has_active_workspace = False
|
||||
|
||||
if include_workspace and ws_cookie and ws_cookie.startswith("gitea:"):
|
||||
if ws_cookie and ws_cookie.startswith("gitea:"):
|
||||
# Gitea workspace: set owner/repo for client-side tree loading
|
||||
# BUT keep local workspace pages — they go in the "Private" section
|
||||
parts = ws_cookie.split(":", 2)
|
||||
@@ -83,6 +84,7 @@ def _sidebar_data(request: Request, repos: list[dict], include_workspace: bool =
|
||||
gitea_repo = parts[2]
|
||||
active_ws_name = f"{gitea_owner}/{gitea_repo}"
|
||||
gitea_workspace = True
|
||||
has_active_workspace = True
|
||||
# workspace_pages stays as-is (local tree, loaded above)
|
||||
elif include_workspace and ws_cookie and user:
|
||||
try:
|
||||
@@ -96,6 +98,7 @@ def _sidebar_data(request: Request, repos: list[dict], include_workspace: bool =
|
||||
if row:
|
||||
active_ws_name = row["name"]
|
||||
workspace_pages = _load_workspace_pages(ws_cookie)
|
||||
has_active_workspace = True
|
||||
# else: stale cookie from another user — ignore
|
||||
except (ValueError, Exception):
|
||||
pass
|
||||
@@ -169,6 +172,7 @@ def _sidebar_data(request: Request, repos: list[dict], include_workspace: bool =
|
||||
"auth_method": auth_method,
|
||||
"gitea_linked": gitea_linked,
|
||||
"github_linked": github_linked,
|
||||
"has_active_workspace": has_active_workspace,
|
||||
}
|
||||
|
||||
|
||||
|
||||
+29
-3
@@ -202,20 +202,30 @@
|
||||
<div class="sidebar-section-header" @click="toggleSection('workspace')">
|
||||
<div class="sidebar-section-title">
|
||||
<span class="chevron" :class="{ open: sectionsOpen.workspace }" x-text="sectionsOpen.workspace ? '▼' : '▶'"></span>
|
||||
{% if has_active_workspace %}
|
||||
<span>{% if gitea_workspace %}📁 {{ active_ws_name }}{% elif workspace_key and '/' in workspace_key %}🔗{% else %}📁{% endif %} {{ active_ws_name if not gitea_workspace else '' }}</span>
|
||||
{% else %}
|
||||
<span>📁 No workspace open</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if has_active_workspace %}
|
||||
<div class="sidebar-section-actions">
|
||||
<button class="section-action-btn" title="New Page" @click.stop="window.FlowDeck.createPage()">📄</button>
|
||||
<button class="section-action-btn" title="New Folder" @click.stop="window.FlowDeck.showCreateFolderModal()">📁</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% from '_workspace_tree_macro.html' import render_workspace_tree %}
|
||||
<div class="sidebar-section-items" x-show="sectionsOpen.workspace" x-transition>
|
||||
<ul class="sidebar-items" data-section="workspace" id="sidebar-workspace-items">
|
||||
{% if workspace_pages %}
|
||||
{{ render_workspace_tree(workspace_pages) }}
|
||||
{% if has_active_workspace %}
|
||||
{% if workspace_pages %}
|
||||
{{ render_workspace_tree(workspace_pages) }}
|
||||
{% else %}
|
||||
<li class="sidebar-item empty-hint"><span class="page-icon">📄</span><span class="page-name text-dim">No pages yet</span></li>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<li class="sidebar-item empty-hint"><span class="page-icon">📄</span><span class="page-name text-dim">No pages yet</span></li>
|
||||
<li class="sidebar-item empty-hint"><span class="page-icon">📁</span><span class="page-name text-dim">Open a workspace to see your files</span></li>
|
||||
{% endif %}
|
||||
<li class="sidebar-item" style="margin-top:4px;border-top:1px solid var(--border);padding-top:8px;" onclick="window.location='/workspaces'">
|
||||
<span class="page-icon">⚙</span><span class="page-name text-dim">Manage Workspaces</span>
|
||||
@@ -1122,6 +1132,14 @@
|
||||
});
|
||||
},
|
||||
newPageInWorkspace() {
|
||||
// Guard: no workspace active — redirect to workspaces page
|
||||
if (!this.workspaceKey || this.workspaceKey === this.workspaceName) {
|
||||
if (!this.giteaWorkspace && !this.localWsId) {
|
||||
this.toast('No workspace open — create one first', 'info');
|
||||
setTimeout(() => { window.location.href = '/workspaces'; }, 1200);
|
||||
return;
|
||||
}
|
||||
}
|
||||
const name = prompt('File name:');
|
||||
if (!name || !name.trim()) return;
|
||||
const wk = this.workspaceKey || '';
|
||||
@@ -1149,6 +1167,14 @@
|
||||
.catch(err => { this.toast('Failed: ' + err.message, 'error'); });
|
||||
},
|
||||
newFolderInWorkspace() {
|
||||
// Guard: no workspace active — redirect to workspaces page
|
||||
if (!this.workspaceKey || this.workspaceKey === this.workspaceName) {
|
||||
if (!this.giteaWorkspace && !this.localWsId) {
|
||||
this.toast('No workspace open — create one first', 'info');
|
||||
setTimeout(() => { window.location.href = '/workspaces'; }, 1200);
|
||||
return;
|
||||
}
|
||||
}
|
||||
const name = prompt('Folder name:');
|
||||
if (!name || !name.trim()) return;
|
||||
const wk = this.workspaceKey || '';
|
||||
|
||||
Reference in New Issue
Block a user