Skip to content
24 changes: 21 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,10 @@ Writing Mermaid code can sometimes be tedious without immediate visual feedback.

- **Live WYSIWYG Editor:** Write Mermaid code and see the rendered diagram update in real time.
- **Two-Way Visual Editing:** Click nodes, edges, and participants directly on the canvas to rename labels, change shapes/styles, reorder, or delete — all changes write back to the code instantly.
- **Diagram Management:** Create, rename, and delete diagrams from a centralized dashboard, with folder organization support.
- **Capability-Aware Creation:** Start from guided templates, create an intentional blank workspace, or create code-only Mermaid diagram types from the New Diagram flow.
- **Blank Diagram State:** Empty diagrams open as a friendly blank workspace with starter-template actions instead of a Mermaid syntax error.
- **Diagram Management:** Create, rename, delete, star, and organize diagrams from a centralized dashboard, with folder support and All Files / Recent / Starred quick views.
- **Dashboard Workflow:** Open diagrams from the dashboard in a new browser tab so the file browser stays available.
- **Powered by Monaco:** Enjoy a rich code-editing experience with Mermaid syntax highlighting, powered by the Monaco Editor.
- **Dark Mode Support:** Toggle between light and dark themes for comfortable viewing anytime.
- **Modern Stack:** Built on Next.js, React 19, and styled with Tailwind CSS & shadcn/ui.
Expand All @@ -53,11 +56,26 @@ These diagram types support full two-way interaction — you can click and manip
| **Sequence Diagram** | Supported |
| **Class Diagram** | Supported |
| **ER Diagram** | Supported |
| **State Diagram** | Supported (2-way sync) |
| **State Diagram** | Supported |
| **Mindmap** | Supported |

### 🔲 Code Only (render + preview)

All other Mermaid diagram types are fully supported for editing and rendering but do not yet have visual canvas interaction
Code-only diagrams can be created, edited, rendered, searched, starred, organized, saved, and opened in LiveMaid, but do not yet expose visual canvas editing controls.

The New Diagram flow currently exposes these code-only starters:

| Diagram | Support Level |
| ----------------------- | ---------------- |
| **Gantt Chart** | Code-only render |
| **Pie Chart** | Code-only render |
| **Timeline** | Code-only render |
| **User Journey** | Code-only render |
| **Git Graph** | Code-only render |
| **Requirement Diagram** | Code-only render |
| **C4 Context** | Code-only render |

Other Mermaid diagram syntax can still be typed or pasted into the editor as code. If LiveMaid does not have a two-way plugin for that diagram type, it is treated as code-only.

## 🐳 Deployment (Docker)

Expand Down
13 changes: 10 additions & 3 deletions src/app/api/diagrams/[id]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
return NextResponse.json({ error: "Diagram not found" }, { status: 404 });
}
return NextResponse.json(diagram);
} catch (error) {

Check warning on line 13 in src/app/api/diagrams/[id]/route.ts

View workflow job for this annotation

GitHub Actions / Lint, format & types

'error' is defined but never used
return NextResponse.json({ error: "Failed to fetch diagram" }, { status: 500 });
}
}
Expand All @@ -21,10 +21,10 @@
if (IS_DEMO_MODE) {
try {
const body = await request.json();
// Rename operations (name change without code) are rejected in demo mode
if (body.name !== undefined && body.code === undefined) {
// Metadata writes (rename/move/star) are rejected in demo mode.
if (body.code === undefined) {
return NextResponse.json(
{ error: "Demo mode: renaming diagrams is disabled" },
{ error: "Demo mode: editing diagrams is disabled" },
{ status: 403 },
);
}
Expand All @@ -34,7 +34,7 @@
return NextResponse.json({ error: "Diagram not found" }, { status: 404 });
}
return NextResponse.json(existing);
} catch (error) {

Check warning on line 37 in src/app/api/diagrams/[id]/route.ts

View workflow job for this annotation

GitHub Actions / Lint, format & types

'error' is defined but never used
return NextResponse.json({ error: "Failed to fetch diagram" }, { status: 500 });
}
}
Expand All @@ -46,6 +46,11 @@
}

const body = await request.json();
const nextStarred = typeof body.starred === "boolean" ? body.starred : existing.starred;
const nextStarredAt =
typeof body.starredAt === "string" || body.starredAt === null
? body.starredAt
: existing.starredAt;
const requestedHistory = Array.isArray(body.versionHistory) ? body.versionHistory : null;
const baseHistory = requestedHistory ?? existing.versionHistory ?? [];

Expand Down Expand Up @@ -76,12 +81,14 @@
...body,
id, // Protect ID
updatedAt: new Date().toISOString(),
starred: Boolean(nextStarred),
starredAt: nextStarred ? (nextStarredAt ?? new Date().toISOString()) : null,
versionHistory: nextVersionHistory,
};

await saveDiagram(updated);
return NextResponse.json(updated);
} catch (error) {

Check warning on line 91 in src/app/api/diagrams/[id]/route.ts

View workflow job for this annotation

GitHub Actions / Lint, format & types

'error' is defined but never used
return NextResponse.json({ error: "Failed to update diagram" }, { status: 500 });
}
}
Expand All @@ -102,7 +109,7 @@
return NextResponse.json({ error: "Diagram not found" }, { status: 404 });
}
return new NextResponse(null, { status: 204 });
} catch (error) {

Check warning on line 112 in src/app/api/diagrams/[id]/route.ts

View workflow job for this annotation

GitHub Actions / Lint, format & types

'error' is defined but never used
return NextResponse.json({ error: "Failed to delete diagram" }, { status: 500 });
}
}
30 changes: 22 additions & 8 deletions src/app/api/diagrams/route.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import { NextResponse } from "next/server";
import { getDiagrams, saveDiagram, DiagramDocument, IS_DEMO_MODE } from "@/lib/api/storage";
import { nanoid } from "nanoid";
import { DiagramRegistry } from "@/lib/diagrams/registry";
import {
getDiagramCatalogItem,
getDiagramTemplate,
isCreatableDiagramType,
} from "@/lib/diagrams/catalog";

export async function GET(request: Request) {
try {
Expand All @@ -26,7 +30,7 @@
}

return NextResponse.json({ items: diagrams, total });
} catch (error) {

Check warning on line 33 in src/app/api/diagrams/route.ts

View workflow job for this annotation

GitHub Actions / Lint, format & types

'error' is defined but never used
return NextResponse.json({ error: "Failed to fetch diagrams" }, { status: 500 });
}
}
Expand All @@ -41,26 +45,36 @@

try {
const body = await request.json();
const { name, type = "flowchart", code } = body;
const template =
typeof body.templateId === "string" ? getDiagramTemplate(body.templateId) : null;
const requestedType =
template?.type ?? (typeof body.type === "string" ? body.type : "flowchart");
const { name, code } = body;
const folderId = typeof body.folderId === "string" ? body.folderId : null;

if (!name) {
return NextResponse.json({ error: "Name is required" }, { status: 400 });
}

const plugin = DiagramRegistry[type] || DiagramRegistry["flowchart"];
const defaultCode = plugin.defaultCode;
const finalCode = code !== undefined ? code : defaultCode;
if (!isCreatableDiagramType(requestedType)) {
return NextResponse.json({ error: "Unsupported diagram type" }, { status: 400 });
}

const catalogItem = getDiagramCatalogItem(requestedType);
const finalCode = typeof code === "string" ? code : (template?.code ?? catalogItem.defaultCode);
const now = new Date().toISOString();

const newDiagram: DiagramDocument = {
id: nanoid(),
name,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
createdAt: now,
updatedAt: now,
deletedAt: null,
code: finalCode,
type,
type: requestedType,
folderId,
starred: false,
starredAt: null,
subPages: [],
comments: [],
versionHistory: [],
Expand All @@ -68,7 +82,7 @@

await saveDiagram(newDiagram);
return NextResponse.json(newDiagram, { status: 201 });
} catch (error) {

Check warning on line 85 in src/app/api/diagrams/route.ts

View workflow job for this annotation

GitHub Actions / Lint, format & types

'error' is defined but never used
return NextResponse.json({ error: "Failed to create diagram" }, { status: 500 });
}
}
9 changes: 9 additions & 0 deletions src/app/api/folders/[id]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,19 @@
next.parentId = newParentId;
}

if (typeof body.starred === "boolean") {
next.starred = body.starred;
next.starredAt = body.starred
? typeof body.starredAt === "string"
? body.starredAt
: new Date().toISOString()
: null;
}

next.updatedAt = new Date().toISOString();
await saveFolder(next);
return NextResponse.json(next);
} catch (error) {

Check warning on line 84 in src/app/api/folders/[id]/route.ts

View workflow job for this annotation

GitHub Actions / Lint, format & types

'error' is defined but never used
return NextResponse.json({ error: "Failed to update folder" }, { status: 500 });
}
}
Expand All @@ -90,7 +99,7 @@
return NextResponse.json({ error: "Folder not found" }, { status: 404 });
}
return new NextResponse(null, { status: 204 });
} catch (error) {

Check warning on line 102 in src/app/api/folders/[id]/route.ts

View workflow job for this annotation

GitHub Actions / Lint, format & types

'error' is defined but never used
return NextResponse.json({ error: "Failed to delete folder" }, { status: 500 });
}
}
2 changes: 2 additions & 0 deletions src/app/api/folders/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
try {
const folders = await getFolders();
return NextResponse.json(folders);
} catch (error) {

Check warning on line 9 in src/app/api/folders/route.ts

View workflow job for this annotation

GitHub Actions / Lint, format & types

'error' is defined but never used
return NextResponse.json({ error: "Failed to fetch folders" }, { status: 500 });
}
}
Expand Down Expand Up @@ -41,11 +41,13 @@
createdAt: now,
updatedAt: now,
deletedAt: null,
starred: false,
starredAt: null,
};

await saveFolder(folder);
return NextResponse.json(folder, { status: 201 });
} catch (error) {

Check warning on line 50 in src/app/api/folders/route.ts

View workflow job for this annotation

GitHub Actions / Lint, format & types

'error' is defined but never used
return NextResponse.json({ error: "Failed to create folder" }, { status: 500 });
}
}
Loading
Loading