diff --git a/app/api/chats/[id]/markdown/route.ts b/app/api/chats/[id]/markdown/route.ts new file mode 100644 index 0000000..98aadc1 --- /dev/null +++ b/app/api/chats/[id]/markdown/route.ts @@ -0,0 +1,37 @@ +/** + * THE SESSION MARKDOWN EXPORT (owner, release polish 2026-10-06): the + * user-facing export the session-markdown engine was built for — the + * same document the extractor reads, minted anchorless (anchors are + * extraction provenance; a learner's export carries no message ids). + * + * GET /api/chats/:id/markdown → the whole session as one markdown file + * (Content-Disposition attachment; filename from the slugified title). + * 404 unknown chat. Both session kinds export — practice sessions ride + * the same engine (problem/conclude results render as tool blocks, + * markers are invisible by the engine's law). + */ +import { sessionMarkdown } from "@/src/core"; +import { getChat } from "@/src/server"; + +/** Filename-safe title (own module scope on purpose: tiny + pure). */ +function slugify(title: string): string { + const slug = title + .toLowerCase() + .normalize("NFKD") + .replace(/[^a-z0-9]+/g, "-") + .replace(/^-+|-+$/g, ""); + return slug.length > 0 ? slug : "session"; +} + +export async function GET(_req: Request, ctx: { params: Promise<{ id: string }> }): Promise { + const { id } = await ctx.params; + const record = getChat(id); + if (!record) return Response.json({ error: "Unknown chat" }, { status: 404 }); + const markdown = sessionMarkdown(record.messages, { title: record.title, anchors: false }); + return new Response(markdown, { + headers: { + "content-type": "text/markdown; charset=utf-8", + "content-disposition": `attachment; filename="${slugify(record.title)}.md"`, + }, + }); +} diff --git a/components/sidebar.tsx b/components/sidebar.tsx index 0cabe1a..503745b 100644 --- a/components/sidebar.tsx +++ b/components/sidebar.tsx @@ -864,6 +864,28 @@ function SessionsSidebar() { > Rename + {/* Export as Markdown (owner, release polish 2026-10-06): the + session-markdown engine's user-facing debut — same-origin + anchor download; the route's Content-Disposition names the + file. Sits with the management actions, above Delete (the + destructive action stays last — the danger-fence law). */} +