feat: Full project management application scaffold
Complete architecture for a ClickUp/Notion/Miro-class project management app:
- Turborepo monorepo with Next.js 15, TypeScript, PostgreSQL (Drizzle ORM)
- Object-centered database schema (everything is an Object: tasks, projects, docs, whiteboards)
- NextAuth v5 authentication with credentials + OAuth providers
- tRPC v11 API layer with full CRUD for objects, properties, relations, templates, search
- Three-panel UI: collapsible sidebar, center content area, push-in right panel
- Purple/teal theme with light/dark mode via Shadcn/ui + Tailwind CSS
- Multiple views: List, Kanban board (dnd-kit), Table (spreadsheet), Embedded iframe
- TipTap rich text editor with slash commands, custom blocks (callout, toggle, mention, embed, divider), AI block
- Real-time collaboration via Yjs + Hocuspocus with presence/cursors
- tldraw whiteboard with custom shape cards (task, document, project)
- MCP server exposing all app data/tools for AI agents
- AI chat panel, editor AI slash commands, Cmd+K command palette
- Template system with built-in templates (Bug Report, Meeting Notes, Sprint)
- Full-text search with result highlighting
- Docker Compose for full-stack deployment (web + collab + postgres + redis)
Made-with: Cursor
2026-03-26 23:39:16 -04:00
|
|
|
import { createHash } from "node:crypto";
|
2026-04-28 16:14:03 -04:00
|
|
|
import { Forbidden } from "@hocuspocus/common";
|
feat: Full project management application scaffold
Complete architecture for a ClickUp/Notion/Miro-class project management app:
- Turborepo monorepo with Next.js 15, TypeScript, PostgreSQL (Drizzle ORM)
- Object-centered database schema (everything is an Object: tasks, projects, docs, whiteboards)
- NextAuth v5 authentication with credentials + OAuth providers
- tRPC v11 API layer with full CRUD for objects, properties, relations, templates, search
- Three-panel UI: collapsible sidebar, center content area, push-in right panel
- Purple/teal theme with light/dark mode via Shadcn/ui + Tailwind CSS
- Multiple views: List, Kanban board (dnd-kit), Table (spreadsheet), Embedded iframe
- TipTap rich text editor with slash commands, custom blocks (callout, toggle, mention, embed, divider), AI block
- Real-time collaboration via Yjs + Hocuspocus with presence/cursors
- tldraw whiteboard with custom shape cards (task, document, project)
- MCP server exposing all app data/tools for AI agents
- AI chat panel, editor AI slash commands, Cmd+K command palette
- Template system with built-in templates (Bug Report, Meeting Notes, Sprint)
- Full-text search with result highlighting
- Docker Compose for full-stack deployment (web + collab + postgres + redis)
Made-with: Cursor
2026-03-26 23:39:16 -04:00
|
|
|
import { Database } from "@hocuspocus/extension-database";
|
|
|
|
|
import { Redis as HocuspocusRedis } from "@hocuspocus/extension-redis";
|
|
|
|
|
import { Hocuspocus, type Extension, type onAuthenticatePayload } from "@hocuspocus/server";
|
|
|
|
|
import { db } from "@tasks/database/client";
|
|
|
|
|
import { objects } from "@tasks/database/schema";
|
2026-04-28 16:14:03 -04:00
|
|
|
import { eq } from "drizzle-orm";
|
feat: Full project management application scaffold
Complete architecture for a ClickUp/Notion/Miro-class project management app:
- Turborepo monorepo with Next.js 15, TypeScript, PostgreSQL (Drizzle ORM)
- Object-centered database schema (everything is an Object: tasks, projects, docs, whiteboards)
- NextAuth v5 authentication with credentials + OAuth providers
- tRPC v11 API layer with full CRUD for objects, properties, relations, templates, search
- Three-panel UI: collapsible sidebar, center content area, push-in right panel
- Purple/teal theme with light/dark mode via Shadcn/ui + Tailwind CSS
- Multiple views: List, Kanban board (dnd-kit), Table (spreadsheet), Embedded iframe
- TipTap rich text editor with slash commands, custom blocks (callout, toggle, mention, embed, divider), AI block
- Real-time collaboration via Yjs + Hocuspocus with presence/cursors
- tldraw whiteboard with custom shape cards (task, document, project)
- MCP server exposing all app data/tools for AI agents
- AI chat panel, editor AI slash commands, Cmd+K command palette
- Template system with built-in templates (Bug Report, Meeting Notes, Sprint)
- Full-text search with result highlighting
- Docker Compose for full-stack deployment (web + collab + postgres + redis)
Made-with: Cursor
2026-03-26 23:39:16 -04:00
|
|
|
import IoRedis from "ioredis";
|
|
|
|
|
import { z } from "zod";
|
|
|
|
|
import {
|
|
|
|
|
base64ToYUpdate,
|
|
|
|
|
extractObjectId,
|
|
|
|
|
yDocToBase64,
|
|
|
|
|
} from "./utils.js";
|
|
|
|
|
|
|
|
|
|
const contentSchema = z
|
|
|
|
|
.object({
|
|
|
|
|
yjs: z.string().optional(),
|
|
|
|
|
})
|
|
|
|
|
.passthrough();
|
|
|
|
|
|
|
|
|
|
function parseYjsBase64FromContent(content: unknown): string | null {
|
|
|
|
|
if (content == null) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
if (typeof content === "string" && content.length > 0) {
|
|
|
|
|
return content;
|
|
|
|
|
}
|
|
|
|
|
const parsed = contentSchema.safeParse(content);
|
|
|
|
|
if (!parsed.success || !parsed.data.yjs) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
return parsed.data.yjs;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function tryCreateRedisExtension(): Promise<HocuspocusRedis | null> {
|
|
|
|
|
const url = process.env.REDIS_URL?.trim();
|
|
|
|
|
if (!url) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const client = new IoRedis(url, {
|
|
|
|
|
maxRetriesPerRequest: null,
|
|
|
|
|
enableReadyCheck: true,
|
|
|
|
|
lazyConnect: false,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
await client.ping();
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.warn(
|
|
|
|
|
"[collab] Redis unavailable, continuing without Redis extension:",
|
|
|
|
|
error,
|
|
|
|
|
);
|
|
|
|
|
try {
|
|
|
|
|
client.disconnect();
|
|
|
|
|
} catch {
|
|
|
|
|
/* ignore */
|
|
|
|
|
}
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return new HocuspocusRedis({
|
|
|
|
|
redis: client,
|
|
|
|
|
identifier: `collab-${process.pid}-${Date.now()}`,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function buildDatabaseExtension() {
|
|
|
|
|
return new Database({
|
|
|
|
|
fetch: async ({ documentName, document }) => {
|
|
|
|
|
const objectId = extractObjectId(documentName);
|
|
|
|
|
if (!objectId) {
|
|
|
|
|
console.error(
|
|
|
|
|
`[collab] fetch: invalid document name (expected object:{id}): ${documentName}`,
|
|
|
|
|
);
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const rows = await db
|
|
|
|
|
.select({ content: objects.content })
|
|
|
|
|
.from(objects)
|
|
|
|
|
.where(eq(objects.id, objectId))
|
|
|
|
|
.limit(1);
|
|
|
|
|
|
|
|
|
|
const row = rows[0];
|
|
|
|
|
if (!row) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const base64 = parseYjsBase64FromContent(row.content);
|
|
|
|
|
if (!base64) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
return base64ToYUpdate(base64);
|
|
|
|
|
} catch (e) {
|
|
|
|
|
console.error(
|
|
|
|
|
`[collab] fetch: invalid Yjs base64 for object ${objectId}:`,
|
|
|
|
|
e,
|
|
|
|
|
);
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error(
|
|
|
|
|
`[collab] fetch: database error for object ${objectId}:`,
|
|
|
|
|
error,
|
|
|
|
|
);
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
store: async ({ documentName, document }) => {
|
|
|
|
|
const objectId = extractObjectId(documentName);
|
|
|
|
|
if (!objectId) {
|
|
|
|
|
console.error(
|
|
|
|
|
`[collab] store: invalid document name (expected object:{id}): ${documentName}`,
|
|
|
|
|
);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const base64 = yDocToBase64(document);
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const existing = await db
|
|
|
|
|
.select({ content: objects.content })
|
|
|
|
|
.from(objects)
|
|
|
|
|
.where(eq(objects.id, objectId))
|
|
|
|
|
.limit(1);
|
|
|
|
|
|
|
|
|
|
const prev = existing[0]?.content;
|
|
|
|
|
const merged =
|
|
|
|
|
prev !== null &&
|
|
|
|
|
prev !== undefined &&
|
|
|
|
|
typeof prev === "object" &&
|
|
|
|
|
!Array.isArray(prev)
|
|
|
|
|
? { ...(prev as Record<string, unknown>), yjs: base64 }
|
|
|
|
|
: { yjs: base64 };
|
|
|
|
|
|
|
|
|
|
const updated = await db
|
|
|
|
|
.update(objects)
|
|
|
|
|
.set({
|
|
|
|
|
content: merged,
|
|
|
|
|
updatedAt: new Date(),
|
|
|
|
|
})
|
|
|
|
|
.where(eq(objects.id, objectId))
|
|
|
|
|
.returning({ id: objects.id });
|
|
|
|
|
|
|
|
|
|
if (updated.length === 0) {
|
|
|
|
|
console.warn(
|
|
|
|
|
`[collab] store: object ${objectId} not found, skipping persist`,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error(
|
|
|
|
|
`[collab] store: database error for object ${objectId}:`,
|
|
|
|
|
error,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
feat: ECHODO app shell, Coolify deploy, Authentik + Umami
Bundles in-flight ECHODO work with the Coolify deployment configuration:
App
- New routes: ai, forms, planner, settings (templates/types), teams,
doc detail, whiteboard detail
- New components: app shell rework (icon-rail, top-header), forms
builder/renderer/responses, types manager, objects creation dialog,
card primitive, form + overview views
- New tRPC routers: favorites, forms, types, workspaces; updates to
health and objects routers
- Markdown backlog sync (packages/database) + cursor-sync schema/migrations
- Schema additions: forms, types, favorites, markdown_backlog, cursor_sync
- Initial Drizzle migrations checked in
Deployment
- docker/docker-compose.coolify.yml: drops bundled Postgres/Redis
(uses CT 102 shared services), removes host port mappings, adds
Coolify SERVICE_FQDN_* magic vars for web + collab
- .env.example rewritten as the full ECHODO/Coolify variable manifest
- NextAuth gains an Authentik OIDC provider (gated on env presence)
- Root layout injects Umami tracking script when configured;
metadata title flipped to ECHODO
Security
- .gitignore expanded to exclude AGENT-DEPLOY.md, .env.*, secrets/,
credentials.*, *.key, *.crt, *.pem, ssh keys
Made-with: Cursor
2026-04-26 15:34:34 -04:00
|
|
|
const CURSOR_COLORS = [
|
|
|
|
|
"#958DF1",
|
|
|
|
|
"#F98181",
|
|
|
|
|
"#FBBC88",
|
|
|
|
|
"#FAF594",
|
|
|
|
|
"#70CFF8",
|
|
|
|
|
"#94FADB",
|
|
|
|
|
"#B9F18D",
|
|
|
|
|
"#E8A0BF",
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
function colorFromName(name: string): string {
|
|
|
|
|
let hash = 0;
|
|
|
|
|
for (let i = 0; i < name.length; i++) {
|
|
|
|
|
hash = name.charCodeAt(i) + ((hash << 5) - hash);
|
|
|
|
|
}
|
|
|
|
|
return CURSOR_COLORS[Math.abs(hash) % CURSOR_COLORS.length];
|
|
|
|
|
}
|
|
|
|
|
|
feat: Full project management application scaffold
Complete architecture for a ClickUp/Notion/Miro-class project management app:
- Turborepo monorepo with Next.js 15, TypeScript, PostgreSQL (Drizzle ORM)
- Object-centered database schema (everything is an Object: tasks, projects, docs, whiteboards)
- NextAuth v5 authentication with credentials + OAuth providers
- tRPC v11 API layer with full CRUD for objects, properties, relations, templates, search
- Three-panel UI: collapsible sidebar, center content area, push-in right panel
- Purple/teal theme with light/dark mode via Shadcn/ui + Tailwind CSS
- Multiple views: List, Kanban board (dnd-kit), Table (spreadsheet), Embedded iframe
- TipTap rich text editor with slash commands, custom blocks (callout, toggle, mention, embed, divider), AI block
- Real-time collaboration via Yjs + Hocuspocus with presence/cursors
- tldraw whiteboard with custom shape cards (task, document, project)
- MCP server exposing all app data/tools for AI agents
- AI chat panel, editor AI slash commands, Cmd+K command palette
- Template system with built-in templates (Bug Report, Meeting Notes, Sprint)
- Full-text search with result highlighting
- Docker Compose for full-stack deployment (web + collab + postgres + redis)
Made-with: Cursor
2026-03-26 23:39:16 -04:00
|
|
|
async function main() {
|
|
|
|
|
const port = Number(process.env.PORT) || 1234;
|
|
|
|
|
|
|
|
|
|
const extensions: Extension[] = [buildDatabaseExtension()];
|
|
|
|
|
const redis = await tryCreateRedisExtension();
|
|
|
|
|
if (redis) {
|
|
|
|
|
extensions.push(redis);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const hocuspocus = new Hocuspocus({
|
|
|
|
|
quiet: true,
|
|
|
|
|
stopOnSignals: false,
|
|
|
|
|
address: "0.0.0.0",
|
|
|
|
|
port,
|
|
|
|
|
extensions,
|
|
|
|
|
|
|
|
|
|
async onAuthenticate({ token }: onAuthenticatePayload) {
|
|
|
|
|
if (!token?.trim()) {
|
|
|
|
|
throw Forbidden;
|
|
|
|
|
}
|
|
|
|
|
|
feat: ECHODO app shell, Coolify deploy, Authentik + Umami
Bundles in-flight ECHODO work with the Coolify deployment configuration:
App
- New routes: ai, forms, planner, settings (templates/types), teams,
doc detail, whiteboard detail
- New components: app shell rework (icon-rail, top-header), forms
builder/renderer/responses, types manager, objects creation dialog,
card primitive, form + overview views
- New tRPC routers: favorites, forms, types, workspaces; updates to
health and objects routers
- Markdown backlog sync (packages/database) + cursor-sync schema/migrations
- Schema additions: forms, types, favorites, markdown_backlog, cursor_sync
- Initial Drizzle migrations checked in
Deployment
- docker/docker-compose.coolify.yml: drops bundled Postgres/Redis
(uses CT 102 shared services), removes host port mappings, adds
Coolify SERVICE_FQDN_* magic vars for web + collab
- .env.example rewritten as the full ECHODO/Coolify variable manifest
- NextAuth gains an Authentik OIDC provider (gated on env presence)
- Root layout injects Umami tracking script when configured;
metadata title flipped to ECHODO
Security
- .gitignore expanded to exclude AGENT-DEPLOY.md, .env.*, secrets/,
credentials.*, *.key, *.crt, *.pem, ssh keys
Made-with: Cursor
2026-04-26 15:34:34 -04:00
|
|
|
// Try JWT decode for real user identity
|
|
|
|
|
const authSecret = process.env.AUTH_SECRET;
|
|
|
|
|
if (authSecret) {
|
|
|
|
|
try {
|
|
|
|
|
// Simple JWT decode (base64url decode the payload)
|
|
|
|
|
const parts = token.split(".");
|
|
|
|
|
if (parts.length === 3) {
|
|
|
|
|
const payload = JSON.parse(
|
|
|
|
|
Buffer.from(parts[1], "base64url").toString("utf-8"),
|
|
|
|
|
);
|
|
|
|
|
if (payload.sub || payload.name) {
|
|
|
|
|
return {
|
|
|
|
|
user: {
|
|
|
|
|
id: payload.sub ?? payload.email ?? token.slice(0, 16),
|
|
|
|
|
name: payload.name ?? payload.email ?? "Anonymous",
|
|
|
|
|
color: colorFromName(
|
|
|
|
|
payload.name ?? payload.email ?? "User",
|
|
|
|
|
),
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} catch {
|
|
|
|
|
// Fall through to hash-based identity
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
feat: Full project management application scaffold
Complete architecture for a ClickUp/Notion/Miro-class project management app:
- Turborepo monorepo with Next.js 15, TypeScript, PostgreSQL (Drizzle ORM)
- Object-centered database schema (everything is an Object: tasks, projects, docs, whiteboards)
- NextAuth v5 authentication with credentials + OAuth providers
- tRPC v11 API layer with full CRUD for objects, properties, relations, templates, search
- Three-panel UI: collapsible sidebar, center content area, push-in right panel
- Purple/teal theme with light/dark mode via Shadcn/ui + Tailwind CSS
- Multiple views: List, Kanban board (dnd-kit), Table (spreadsheet), Embedded iframe
- TipTap rich text editor with slash commands, custom blocks (callout, toggle, mention, embed, divider), AI block
- Real-time collaboration via Yjs + Hocuspocus with presence/cursors
- tldraw whiteboard with custom shape cards (task, document, project)
- MCP server exposing all app data/tools for AI agents
- AI chat panel, editor AI slash commands, Cmd+K command palette
- Template system with built-in templates (Bug Report, Meeting Notes, Sprint)
- Full-text search with result highlighting
- Docker Compose for full-stack deployment (web + collab + postgres + redis)
Made-with: Cursor
2026-03-26 23:39:16 -04:00
|
|
|
const id = createHash("sha256").update(token).digest("hex");
|
|
|
|
|
return {
|
feat: ECHODO app shell, Coolify deploy, Authentik + Umami
Bundles in-flight ECHODO work with the Coolify deployment configuration:
App
- New routes: ai, forms, planner, settings (templates/types), teams,
doc detail, whiteboard detail
- New components: app shell rework (icon-rail, top-header), forms
builder/renderer/responses, types manager, objects creation dialog,
card primitive, form + overview views
- New tRPC routers: favorites, forms, types, workspaces; updates to
health and objects routers
- Markdown backlog sync (packages/database) + cursor-sync schema/migrations
- Schema additions: forms, types, favorites, markdown_backlog, cursor_sync
- Initial Drizzle migrations checked in
Deployment
- docker/docker-compose.coolify.yml: drops bundled Postgres/Redis
(uses CT 102 shared services), removes host port mappings, adds
Coolify SERVICE_FQDN_* magic vars for web + collab
- .env.example rewritten as the full ECHODO/Coolify variable manifest
- NextAuth gains an Authentik OIDC provider (gated on env presence)
- Root layout injects Umami tracking script when configured;
metadata title flipped to ECHODO
Security
- .gitignore expanded to exclude AGENT-DEPLOY.md, .env.*, secrets/,
credentials.*, *.key, *.crt, *.pem, ssh keys
Made-with: Cursor
2026-04-26 15:34:34 -04:00
|
|
|
user: {
|
|
|
|
|
id,
|
|
|
|
|
name: `User ${id.slice(0, 8)}`,
|
|
|
|
|
color: colorFromName(id),
|
|
|
|
|
},
|
feat: Full project management application scaffold
Complete architecture for a ClickUp/Notion/Miro-class project management app:
- Turborepo monorepo with Next.js 15, TypeScript, PostgreSQL (Drizzle ORM)
- Object-centered database schema (everything is an Object: tasks, projects, docs, whiteboards)
- NextAuth v5 authentication with credentials + OAuth providers
- tRPC v11 API layer with full CRUD for objects, properties, relations, templates, search
- Three-panel UI: collapsible sidebar, center content area, push-in right panel
- Purple/teal theme with light/dark mode via Shadcn/ui + Tailwind CSS
- Multiple views: List, Kanban board (dnd-kit), Table (spreadsheet), Embedded iframe
- TipTap rich text editor with slash commands, custom blocks (callout, toggle, mention, embed, divider), AI block
- Real-time collaboration via Yjs + Hocuspocus with presence/cursors
- tldraw whiteboard with custom shape cards (task, document, project)
- MCP server exposing all app data/tools for AI agents
- AI chat panel, editor AI slash commands, Cmd+K command palette
- Template system with built-in templates (Bug Report, Meeting Notes, Sprint)
- Full-text search with result highlighting
- Docker Compose for full-stack deployment (web + collab + postgres + redis)
Made-with: Cursor
2026-03-26 23:39:16 -04:00
|
|
|
};
|
|
|
|
|
},
|
|
|
|
|
|
feat: ECHODO app shell, Coolify deploy, Authentik + Umami
Bundles in-flight ECHODO work with the Coolify deployment configuration:
App
- New routes: ai, forms, planner, settings (templates/types), teams,
doc detail, whiteboard detail
- New components: app shell rework (icon-rail, top-header), forms
builder/renderer/responses, types manager, objects creation dialog,
card primitive, form + overview views
- New tRPC routers: favorites, forms, types, workspaces; updates to
health and objects routers
- Markdown backlog sync (packages/database) + cursor-sync schema/migrations
- Schema additions: forms, types, favorites, markdown_backlog, cursor_sync
- Initial Drizzle migrations checked in
Deployment
- docker/docker-compose.coolify.yml: drops bundled Postgres/Redis
(uses CT 102 shared services), removes host port mappings, adds
Coolify SERVICE_FQDN_* magic vars for web + collab
- .env.example rewritten as the full ECHODO/Coolify variable manifest
- NextAuth gains an Authentik OIDC provider (gated on env presence)
- Root layout injects Umami tracking script when configured;
metadata title flipped to ECHODO
Security
- .gitignore expanded to exclude AGENT-DEPLOY.md, .env.*, secrets/,
credentials.*, *.key, *.crt, *.pem, ssh keys
Made-with: Cursor
2026-04-26 15:34:34 -04:00
|
|
|
async onConnect({ documentName, connection }) {
|
|
|
|
|
const user = connection.readOnly ? "read-only" : "editor";
|
|
|
|
|
console.log(`[collab] connect document=${documentName} (${user})`);
|
feat: Full project management application scaffold
Complete architecture for a ClickUp/Notion/Miro-class project management app:
- Turborepo monorepo with Next.js 15, TypeScript, PostgreSQL (Drizzle ORM)
- Object-centered database schema (everything is an Object: tasks, projects, docs, whiteboards)
- NextAuth v5 authentication with credentials + OAuth providers
- tRPC v11 API layer with full CRUD for objects, properties, relations, templates, search
- Three-panel UI: collapsible sidebar, center content area, push-in right panel
- Purple/teal theme with light/dark mode via Shadcn/ui + Tailwind CSS
- Multiple views: List, Kanban board (dnd-kit), Table (spreadsheet), Embedded iframe
- TipTap rich text editor with slash commands, custom blocks (callout, toggle, mention, embed, divider), AI block
- Real-time collaboration via Yjs + Hocuspocus with presence/cursors
- tldraw whiteboard with custom shape cards (task, document, project)
- MCP server exposing all app data/tools for AI agents
- AI chat panel, editor AI slash commands, Cmd+K command palette
- Template system with built-in templates (Bug Report, Meeting Notes, Sprint)
- Full-text search with result highlighting
- Docker Compose for full-stack deployment (web + collab + postgres + redis)
Made-with: Cursor
2026-03-26 23:39:16 -04:00
|
|
|
},
|
|
|
|
|
|
|
|
|
|
async onDisconnect({ documentName }) {
|
|
|
|
|
console.log(`[collab] disconnect document=${documentName}`);
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
async afterUnloadDocument({ documentName }) {
|
|
|
|
|
console.log(`[collab] afterUnloadDocument document=${documentName}`);
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const shutdown = async (signal: NodeJS.Signals) => {
|
|
|
|
|
console.log(`[collab] received ${signal}, shutting down...`);
|
|
|
|
|
try {
|
|
|
|
|
await hocuspocus.destroy();
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error("[collab] error during shutdown:", error);
|
|
|
|
|
}
|
|
|
|
|
process.exit(0);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
process.once("SIGINT", () => void shutdown("SIGINT"));
|
|
|
|
|
process.once("SIGTERM", () => void shutdown("SIGTERM"));
|
|
|
|
|
|
|
|
|
|
await hocuspocus.listen();
|
|
|
|
|
|
|
|
|
|
console.log(`Collaboration server running on port ${port}`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
main().catch((error) => {
|
|
|
|
|
console.error("[collab] fatal error:", error);
|
|
|
|
|
process.exit(1);
|
|
|
|
|
});
|