diff --git a/app/routers/dashboard.py b/app/routers/dashboard.py
index b03f4c6..922afda 100644
--- a/app/routers/dashboard.py
+++ b/app/routers/dashboard.py
@@ -454,3 +454,82 @@ async def remove_member(request: Request, ws_id: int, user_id: int):
)
conn.commit()
return {"status": "ok"}
+
+
+# ═══════════ Local Workspace (file/folder CRUD) ═══════════
+
+@router.get("/local-workspace", response_class=HTMLResponse)
+async def local_workspace_page(request: Request):
+ """Local workspace page with file/folder tree."""
+ from jinja2 import Environment, FileSystemLoader
+ env = Environment(loader=FileSystemLoader("app/templates"))
+ sidebar = _sidebar_data(request, [])
+ user = SessionManager.decode_session(request.cookies.get("flowdeck_session", ""))
+ if not user:
+ return RedirectResponse("/auth/login?provider=local", status_code=302)
+
+ with get_conn() as conn:
+ rows = conn.execute(
+ "SELECT id, title FROM pages WHERE parent_id IS NULL ORDER BY created_at DESC"
+ ).fetchall()
+ items = [{"id": r["id"], "name": r["title"] or "Untitled", "type": "page"} for r in rows]
+
+ ctx = {**sidebar, "user": user, "workspace_name": "My Workspace", "items": items}
+ template = env.get_template("local_workspace.html")
+ return template.render(**ctx)
+
+
+@router.get("/api/local-workspace/tree")
+async def local_workspace_tree(request: Request):
+ """Return the file/folder tree."""
+ with get_conn() as conn:
+ roots = conn.execute(
+ "SELECT id, title FROM pages WHERE parent_id IS NULL ORDER BY created_at DESC"
+ ).fetchall()
+ tree = []
+ for r in roots:
+ with get_conn() as conn:
+ children = conn.execute(
+ "SELECT id, title FROM pages WHERE parent_id=? ORDER BY created_at", (r["id"],)
+ ).fetchall()
+ item = {"id": r["id"], "name": r["title"] or "Untitled", "type": "page",
+ "children": [{"id": c["id"], "name": c["title"] or "Untitled", "type": "page"} for c in children]}
+ tree.append(item)
+ return {"tree": tree}
+
+
+@router.post("/api/local-workspace/items")
+async def create_local_workspace_item(request: Request):
+ """Create a new file."""
+ import json
+ body = await request.json()
+ name = body.get("name", "Untitled").strip()
+ with get_conn() as conn:
+ cursor = conn.execute(
+ "INSERT INTO pages (workspace, title, content, content_format, parent_section) VALUES ('', ?, '', 'blocks', 'Private')",
+ (name,),
+ )
+ conn.commit()
+ pid = cursor.lastrowid
+ return {"id": pid, "name": name, "type": "page"}
+
+
+@router.put("/api/local-workspace/items/{item_id:int}")
+async def rename_local_workspace_item(request: Request, item_id: int):
+ """Rename a file."""
+ import json
+ body = await request.json()
+ name = body.get("name", "Untitled").strip()
+ with get_conn() as conn:
+ conn.execute("UPDATE pages SET title=? WHERE id=?", (name, item_id))
+ conn.commit()
+ return {"status": "ok"}
+
+
+@router.delete("/api/local-workspace/items/{item_id:int}")
+async def delete_local_workspace_item(request: Request, item_id: int):
+ """Delete a file (and its children)."""
+ with get_conn() as conn:
+ conn.execute("DELETE FROM pages WHERE id=? OR parent_id=?", (item_id, item_id))
+ conn.commit()
+ return {"status": "ok"}
diff --git a/app/templates/local_workspace.html b/app/templates/local_workspace.html
new file mode 100644
index 0000000..b1a6f6e
--- /dev/null
+++ b/app/templates/local_workspace.html
@@ -0,0 +1,159 @@
+{% extends "base.html" %}
+{% block page_title %}My Workspace{% endblock %}
+{% block page_icon %}📁{% endblock %}
+
+{% block topbar %}
+
{{ items | length }} item(s)
+Create a new file or folder to get started
+