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 { z } from "zod";
|
|
|
|
|
import { and, desc, eq, inArray, isNull, sql } from "drizzle-orm";
|
|
|
|
|
import { objects } from "@tasks/database/schema";
|
|
|
|
|
import { objectTypes } from "@tasks/shared";
|
|
|
|
|
import type { Context } from "@/server/trpc";
|
multi-tenancy: promote workspaces to top-level table
Block A of the EchoDo plan. Workspaces used to live as `objects(type='workspace')`,
which made it impossible to put a real RLS-friendly tenant boundary on the schema
or to give each workspace a stable URL slug. This commit:
- Adds a top-level `workspaces` table (slug unique, owner FK, plan_tier hook).
- Migrates the 8 anchor tables (objects, workspace_members, object_type_defs,
property_definitions, templates, forms, markdown_backlog_items,
cursor_sync_mappings) to FK into `workspaces.id` instead of `objects.id`,
with a hand-augmented data-copy migration that preserves IDs and slug-collision-
proofs on backfill.
- Introduces a `workspaceProcedure` tRPC middleware + `resolveWorkspace` helper
that take a UUID-or-slug `workspace` handle and expose `ctx.workspace`. All
tenant-scoped routers (objects, types, properties, templates, forms, search,
ai, relations, favorites) now flow through it.
- Updates the web app to pass `workspace` slugs from the URL (or store) instead
of the old `workspaceId`, including a workspace-sync layer that rewrites
/<UUID>/... links to /<slug>/...
- Updates the MCP tools (list_objects, create_object, search_objects) and the
workspace://{handle}/tree resource to accept either a slug or UUID so existing
agents keep working.
- Adds a Create Workspace dialog and a Workspace Settings page (rename + slug
rename with redirect, owner-only archive).
Verified locally against a fresh Postgres: migration applies cleanly, slug
uniqueness holds, tenant data is isolated by workspace_id, slug↔UUID resolution
works in both directions, and ON DELETE CASCADE cleans up child rows in the
correct workspace only.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-07 00:02:55 -04:00
|
|
|
import { router, workspaceProcedure } from "@/server/trpc";
|
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 objectTypeSchema = z.enum(objectTypes);
|
|
|
|
|
|
|
|
|
|
function escapeIlike(value: string): string {
|
|
|
|
|
return value.replace(/\\/g, "\\\\").replace(/%/g, "\\%").replace(/_/g, "\\_");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function makeSnippet(
|
|
|
|
|
description: string | null,
|
|
|
|
|
query: string,
|
|
|
|
|
maxLen = 160,
|
|
|
|
|
): string | null {
|
|
|
|
|
if (!description) return null;
|
|
|
|
|
const t = description.trim();
|
|
|
|
|
if (!t) return null;
|
|
|
|
|
const q = query.trim().toLowerCase();
|
|
|
|
|
if (!q) {
|
|
|
|
|
return t.length > maxLen ? `${t.slice(0, maxLen)}…` : t;
|
|
|
|
|
}
|
|
|
|
|
const lower = t.toLowerCase();
|
|
|
|
|
const idx = lower.indexOf(q);
|
|
|
|
|
if (idx === -1) {
|
|
|
|
|
return t.length > maxLen ? `${t.slice(0, maxLen)}…` : t;
|
|
|
|
|
}
|
|
|
|
|
const pad = 48;
|
|
|
|
|
const start = Math.max(0, idx - pad);
|
|
|
|
|
const end = Math.min(t.length, idx + query.length + pad);
|
|
|
|
|
let s = t.slice(start, end);
|
|
|
|
|
if (start > 0) s = `…${s}`;
|
|
|
|
|
if (end < t.length) s = `${s}…`;
|
|
|
|
|
return s;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type ParentRow = { parentId: string | null; title: string };
|
|
|
|
|
|
|
|
|
|
export type SearchResult = {
|
|
|
|
|
id: string;
|
|
|
|
|
type: string;
|
|
|
|
|
title: string;
|
|
|
|
|
status: string | null;
|
|
|
|
|
parentId: string | null;
|
|
|
|
|
workspaceId: string | null;
|
|
|
|
|
descriptionSnippet: string | null;
|
|
|
|
|
parentBreadcrumb: string | null;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
async function fetchAncestorMap(
|
|
|
|
|
db: Context["db"],
|
|
|
|
|
parentIds: string[],
|
|
|
|
|
): Promise<Map<string, ParentRow>> {
|
|
|
|
|
const map = new Map<string, ParentRow>();
|
|
|
|
|
const seen = new Set<string>();
|
|
|
|
|
let frontier = [...new Set(parentIds)];
|
|
|
|
|
|
|
|
|
|
while (frontier.length > 0) {
|
|
|
|
|
const batch = await db
|
|
|
|
|
.select({
|
|
|
|
|
id: objects.id,
|
|
|
|
|
parentId: objects.parentId,
|
|
|
|
|
title: objects.title,
|
|
|
|
|
})
|
|
|
|
|
.from(objects)
|
|
|
|
|
.where(inArray(objects.id, frontier));
|
|
|
|
|
|
|
|
|
|
const next: string[] = [];
|
|
|
|
|
for (const row of batch) {
|
|
|
|
|
map.set(row.id, { parentId: row.parentId, title: row.title });
|
|
|
|
|
seen.add(row.id);
|
|
|
|
|
if (row.parentId && !seen.has(row.parentId)) {
|
|
|
|
|
next.push(row.parentId);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
frontier = [...new Set(next.filter((id) => !seen.has(id)))];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return map;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function parentBreadcrumb(
|
|
|
|
|
parentId: string | null,
|
|
|
|
|
ancestorMap: Map<string, ParentRow>,
|
|
|
|
|
): string | null {
|
|
|
|
|
if (!parentId) return null;
|
|
|
|
|
const parts: string[] = [];
|
|
|
|
|
let cur: string | null = parentId;
|
|
|
|
|
for (let i = 0; i < 32 && cur; i++) {
|
|
|
|
|
const row = ancestorMap.get(cur);
|
|
|
|
|
if (!row) break;
|
|
|
|
|
parts.unshift(row.title);
|
|
|
|
|
cur = row.parentId;
|
|
|
|
|
}
|
|
|
|
|
return parts.length > 0 ? parts.join(" > ") : null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export const searchRouter = router({
|
multi-tenancy: promote workspaces to top-level table
Block A of the EchoDo plan. Workspaces used to live as `objects(type='workspace')`,
which made it impossible to put a real RLS-friendly tenant boundary on the schema
or to give each workspace a stable URL slug. This commit:
- Adds a top-level `workspaces` table (slug unique, owner FK, plan_tier hook).
- Migrates the 8 anchor tables (objects, workspace_members, object_type_defs,
property_definitions, templates, forms, markdown_backlog_items,
cursor_sync_mappings) to FK into `workspaces.id` instead of `objects.id`,
with a hand-augmented data-copy migration that preserves IDs and slug-collision-
proofs on backfill.
- Introduces a `workspaceProcedure` tRPC middleware + `resolveWorkspace` helper
that take a UUID-or-slug `workspace` handle and expose `ctx.workspace`. All
tenant-scoped routers (objects, types, properties, templates, forms, search,
ai, relations, favorites) now flow through it.
- Updates the web app to pass `workspace` slugs from the URL (or store) instead
of the old `workspaceId`, including a workspace-sync layer that rewrites
/<UUID>/... links to /<slug>/...
- Updates the MCP tools (list_objects, create_object, search_objects) and the
workspace://{handle}/tree resource to accept either a slug or UUID so existing
agents keep working.
- Adds a Create Workspace dialog and a Workspace Settings page (rename + slug
rename with redirect, owner-only archive).
Verified locally against a fresh Postgres: migration applies cleanly, slug
uniqueness holds, tenant data is isolated by workspace_id, slug↔UUID resolution
works in both directions, and ON DELETE CASCADE cleans up child rows in the
correct workspace only.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-07 00:02:55 -04:00
|
|
|
search: workspaceProcedure
|
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
|
|
|
.input(
|
|
|
|
|
z.object({
|
|
|
|
|
query: z.string(),
|
|
|
|
|
type: objectTypeSchema.optional(),
|
|
|
|
|
limit: z.number().int().positive().max(100).optional(),
|
|
|
|
|
}),
|
|
|
|
|
)
|
|
|
|
|
.query(async ({ ctx, input }) => {
|
|
|
|
|
const limit = input.limit ?? 20;
|
|
|
|
|
const raw = input.query.trim();
|
|
|
|
|
if (raw.length === 0) {
|
|
|
|
|
return { results: [] as SearchResult[] };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const pattern = `%${escapeIlike(raw)}%`;
|
|
|
|
|
const matchCondition = sql`(${objects.title} ILIKE ${pattern} ESCAPE '\\' OR ${objects.description} ILIKE ${pattern} ESCAPE '\\')`;
|
|
|
|
|
|
multi-tenancy: promote workspaces to top-level table
Block A of the EchoDo plan. Workspaces used to live as `objects(type='workspace')`,
which made it impossible to put a real RLS-friendly tenant boundary on the schema
or to give each workspace a stable URL slug. This commit:
- Adds a top-level `workspaces` table (slug unique, owner FK, plan_tier hook).
- Migrates the 8 anchor tables (objects, workspace_members, object_type_defs,
property_definitions, templates, forms, markdown_backlog_items,
cursor_sync_mappings) to FK into `workspaces.id` instead of `objects.id`,
with a hand-augmented data-copy migration that preserves IDs and slug-collision-
proofs on backfill.
- Introduces a `workspaceProcedure` tRPC middleware + `resolveWorkspace` helper
that take a UUID-or-slug `workspace` handle and expose `ctx.workspace`. All
tenant-scoped routers (objects, types, properties, templates, forms, search,
ai, relations, favorites) now flow through it.
- Updates the web app to pass `workspace` slugs from the URL (or store) instead
of the old `workspaceId`, including a workspace-sync layer that rewrites
/<UUID>/... links to /<slug>/...
- Updates the MCP tools (list_objects, create_object, search_objects) and the
workspace://{handle}/tree resource to accept either a slug or UUID so existing
agents keep working.
- Adds a Create Workspace dialog and a Workspace Settings page (rename + slug
rename with redirect, owner-only archive).
Verified locally against a fresh Postgres: migration applies cleanly, slug
uniqueness holds, tenant data is isolated by workspace_id, slug↔UUID resolution
works in both directions, and ON DELETE CASCADE cleans up child rows in the
correct workspace only.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-07 00:02:55 -04:00
|
|
|
const conditions = [
|
|
|
|
|
eq(objects.workspaceId, ctx.workspace.id),
|
|
|
|
|
isNull(objects.archivedAt),
|
|
|
|
|
matchCondition,
|
|
|
|
|
];
|
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
|
|
|
|
|
|
|
|
if (input.type !== undefined) {
|
|
|
|
|
conditions.push(eq(objects.type, input.type));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const titleFirst = sql<number>`
|
|
|
|
|
CASE
|
|
|
|
|
WHEN ${objects.title} ILIKE ${pattern} ESCAPE '\\' THEN 0
|
|
|
|
|
ELSE 1
|
|
|
|
|
END
|
|
|
|
|
`.mapWith(Number);
|
|
|
|
|
|
|
|
|
|
const rows = await ctx.db
|
|
|
|
|
.select({
|
|
|
|
|
id: objects.id,
|
|
|
|
|
type: objects.type,
|
|
|
|
|
title: objects.title,
|
|
|
|
|
status: objects.status,
|
|
|
|
|
parentId: objects.parentId,
|
|
|
|
|
workspaceId: objects.workspaceId,
|
|
|
|
|
description: objects.description,
|
|
|
|
|
})
|
|
|
|
|
.from(objects)
|
|
|
|
|
.where(and(...conditions))
|
|
|
|
|
.orderBy(titleFirst, desc(objects.updatedAt))
|
|
|
|
|
.limit(limit);
|
|
|
|
|
|
|
|
|
|
const parentIds = rows
|
|
|
|
|
.map((r) => r.parentId)
|
|
|
|
|
.filter((x): x is string => x != null);
|
|
|
|
|
|
|
|
|
|
const ancestorMap =
|
|
|
|
|
parentIds.length > 0
|
|
|
|
|
? await fetchAncestorMap(ctx.db, parentIds)
|
|
|
|
|
: new Map<string, ParentRow>();
|
|
|
|
|
|
|
|
|
|
const results: SearchResult[] = rows.map((r) => ({
|
|
|
|
|
id: r.id,
|
|
|
|
|
type: r.type,
|
|
|
|
|
title: r.title,
|
|
|
|
|
status: r.status,
|
|
|
|
|
parentId: r.parentId,
|
|
|
|
|
workspaceId: r.workspaceId,
|
|
|
|
|
descriptionSnippet: makeSnippet(r.description, raw),
|
|
|
|
|
parentBreadcrumb: parentBreadcrumb(r.parentId, ancestorMap),
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
return { results };
|
|
|
|
|
}),
|
|
|
|
|
|
multi-tenancy: promote workspaces to top-level table
Block A of the EchoDo plan. Workspaces used to live as `objects(type='workspace')`,
which made it impossible to put a real RLS-friendly tenant boundary on the schema
or to give each workspace a stable URL slug. This commit:
- Adds a top-level `workspaces` table (slug unique, owner FK, plan_tier hook).
- Migrates the 8 anchor tables (objects, workspace_members, object_type_defs,
property_definitions, templates, forms, markdown_backlog_items,
cursor_sync_mappings) to FK into `workspaces.id` instead of `objects.id`,
with a hand-augmented data-copy migration that preserves IDs and slug-collision-
proofs on backfill.
- Introduces a `workspaceProcedure` tRPC middleware + `resolveWorkspace` helper
that take a UUID-or-slug `workspace` handle and expose `ctx.workspace`. All
tenant-scoped routers (objects, types, properties, templates, forms, search,
ai, relations, favorites) now flow through it.
- Updates the web app to pass `workspace` slugs from the URL (or store) instead
of the old `workspaceId`, including a workspace-sync layer that rewrites
/<UUID>/... links to /<slug>/...
- Updates the MCP tools (list_objects, create_object, search_objects) and the
workspace://{handle}/tree resource to accept either a slug or UUID so existing
agents keep working.
- Adds a Create Workspace dialog and a Workspace Settings page (rename + slug
rename with redirect, owner-only archive).
Verified locally against a fresh Postgres: migration applies cleanly, slug
uniqueness holds, tenant data is isolated by workspace_id, slug↔UUID resolution
works in both directions, and ON DELETE CASCADE cleans up child rows in the
correct workspace only.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-07 00:02:55 -04:00
|
|
|
recent: workspaceProcedure
|
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
|
|
|
.input(
|
|
|
|
|
z.object({
|
|
|
|
|
limit: z.number().int().positive().max(50).optional(),
|
|
|
|
|
}),
|
|
|
|
|
)
|
|
|
|
|
.query(async ({ ctx, input }) => {
|
|
|
|
|
const limit = input.limit ?? 10;
|
|
|
|
|
|
multi-tenancy: promote workspaces to top-level table
Block A of the EchoDo plan. Workspaces used to live as `objects(type='workspace')`,
which made it impossible to put a real RLS-friendly tenant boundary on the schema
or to give each workspace a stable URL slug. This commit:
- Adds a top-level `workspaces` table (slug unique, owner FK, plan_tier hook).
- Migrates the 8 anchor tables (objects, workspace_members, object_type_defs,
property_definitions, templates, forms, markdown_backlog_items,
cursor_sync_mappings) to FK into `workspaces.id` instead of `objects.id`,
with a hand-augmented data-copy migration that preserves IDs and slug-collision-
proofs on backfill.
- Introduces a `workspaceProcedure` tRPC middleware + `resolveWorkspace` helper
that take a UUID-or-slug `workspace` handle and expose `ctx.workspace`. All
tenant-scoped routers (objects, types, properties, templates, forms, search,
ai, relations, favorites) now flow through it.
- Updates the web app to pass `workspace` slugs from the URL (or store) instead
of the old `workspaceId`, including a workspace-sync layer that rewrites
/<UUID>/... links to /<slug>/...
- Updates the MCP tools (list_objects, create_object, search_objects) and the
workspace://{handle}/tree resource to accept either a slug or UUID so existing
agents keep working.
- Adds a Create Workspace dialog and a Workspace Settings page (rename + slug
rename with redirect, owner-only archive).
Verified locally against a fresh Postgres: migration applies cleanly, slug
uniqueness holds, tenant data is isolated by workspace_id, slug↔UUID resolution
works in both directions, and ON DELETE CASCADE cleans up child rows in the
correct workspace only.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-07 00:02:55 -04:00
|
|
|
const conditions = [
|
|
|
|
|
eq(objects.workspaceId, ctx.workspace.id),
|
|
|
|
|
isNull(objects.archivedAt),
|
|
|
|
|
];
|
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 rows = await ctx.db
|
|
|
|
|
.select({
|
|
|
|
|
id: objects.id,
|
|
|
|
|
type: objects.type,
|
|
|
|
|
title: objects.title,
|
|
|
|
|
status: objects.status,
|
|
|
|
|
parentId: objects.parentId,
|
|
|
|
|
workspaceId: objects.workspaceId,
|
|
|
|
|
description: objects.description,
|
|
|
|
|
})
|
|
|
|
|
.from(objects)
|
|
|
|
|
.where(and(...conditions))
|
|
|
|
|
.orderBy(desc(objects.updatedAt))
|
|
|
|
|
.limit(limit);
|
|
|
|
|
|
|
|
|
|
const parentIds = rows
|
|
|
|
|
.map((r) => r.parentId)
|
|
|
|
|
.filter((x): x is string => x != null);
|
|
|
|
|
|
|
|
|
|
const ancestorMap =
|
|
|
|
|
parentIds.length > 0
|
|
|
|
|
? await fetchAncestorMap(ctx.db, parentIds)
|
|
|
|
|
: new Map<string, ParentRow>();
|
|
|
|
|
|
|
|
|
|
const results: SearchResult[] = rows.map((r) => ({
|
|
|
|
|
id: r.id,
|
|
|
|
|
type: r.type,
|
|
|
|
|
title: r.title,
|
|
|
|
|
status: r.status,
|
|
|
|
|
parentId: r.parentId,
|
|
|
|
|
workspaceId: r.workspaceId,
|
|
|
|
|
descriptionSnippet: makeSnippet(r.description, ""),
|
|
|
|
|
parentBreadcrumb: parentBreadcrumb(r.parentId, ancestorMap),
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
return { results };
|
|
|
|
|
}),
|
|
|
|
|
});
|