Problem
src/features/export/ and src/lib/export-core.ts exist, and docx is a dependency, but:
- There is no PDF export — a critical feature for a knowledge studio
- There is no canonical JSON schema for the full knowledge studio state (notes + graph + mind map)
- There is no Markdown import — only export exists, making the format a dead-end
- The CLI (
cli/index.ts) lacks headless export commands
Current Export State
src/features/export/ # exists but incomplete
src/lib/export-core.ts # basic export, no PDF
dependencies:
docx: installed # Word export
# @react-pdf/renderer: NOT installed
# No PDF support
Proposed Implementation
1. PDF Export via @react-pdf/renderer
npm install @react-pdf/renderer
// src/features/export/pdf-exporter.tsx
import { Document, Page, Text, View, StyleSheet, pdf } from '@react-pdf/renderer';
const styles = StyleSheet.create({
page: { padding: 40, fontFamily: 'Helvetica' },
title: { fontSize: 24, fontWeight: 'bold', marginBottom: 16 },
body: { fontSize: 11, lineHeight: 1.6 },
tag: { fontSize: 9, color: '#666', marginTop: 8 },
});
function NotePDF({ note }: { note: Note }) {
return (
<Document>
<Page size="A4" style={styles.page}>
<Text style={styles.title}>{note.title}</Text>
<Text style={styles.body}>{note.content}</Text>
{note.tags?.length && (
<Text style={styles.tag}>Tags: {note.tags.join(', ')}</Text>
)}
</Page>
</Document>
);
}
export async function exportNoteToPDF(note: Note): Promise<Blob> {
const blob = await pdf(<NotePDF note={note} />).toBlob();
return blob;
}
export async function exportAllNotesToPDF(notes: Note[]): Promise<Blob> {
// Multi-page PDF with table of contents
const blob = await pdf(
<Document>
{notes.map(note => (
<Page key={note.id} size="A4" style={styles.page}>
<Text style={styles.title}>{note.title}</Text>
<Text style={styles.body}>{note.content}</Text>
</Page>
))}
</Document>
).toBlob();
return blob;
}
2. Canonical JSON schema
// src/lib/export-core.ts
export interface KnowledgeStudioExport {
version: '1.0';
exportedAt: string; // ISO 8601
metadata: {
title: string;
description?: string;
};
notes: Note[];
graph: {
nodes: GraphNode[];
edges: GraphEdge[];
};
mindMap: MindMapData | null;
tags: string[];
}
export function exportToJson(state: KnowledgeStudioExport): string {
return JSON.stringify(state, null, 2);
}
export function importFromJson(json: string): KnowledgeStudioExport {
const data = JSON.parse(json);
// Validate schema version
if (!data.version || data.version !== '1.0') {
throw new Error(`Unsupported export version: ${data.version}`);
}
return data as KnowledgeStudioExport;
}
3. Markdown import with frontmatter
// src/lib/markdown-importer.ts
import matter from 'gray-matter';
export interface MarkdownImportResult {
notes: Note[];
errors: Array<{ file: string; error: string }>;
}
export function importFromMarkdown(markdownFiles: { name: string; content: string }[]): MarkdownImportResult {
const notes: Note[] = [];
const errors: MarkdownImportResult['errors'] = [];
for (const file of markdownFiles) {
try {
const { data: frontmatter, content } = matter(file.content);
notes.push({
id: frontmatter.id ?? crypto.randomUUID(),
title: frontmatter.title ?? file.name.replace('.md', ''),
content,
tags: frontmatter.tags ?? [],
createdAt: frontmatter.createdAt ? new Date(frontmatter.createdAt).getTime() : Date.now(),
updatedAt: Date.now(),
});
} catch (err) {
errors.push({ file: file.name, error: String(err) });
}
}
return { notes, errors };
}
4. Markdown export with frontmatter
// Ensure exported Markdown is re-importable (round-trip safe)
export function exportNoteToMarkdown(note: Note): string {
const frontmatter = [
'---',
`id: ${note.id}`,
`title: ${note.title}`,
`tags: [${note.tags?.join(', ') ?? ''}]`,
`createdAt: ${new Date(note.createdAt).toISOString()}`,
`updatedAt: ${new Date(note.updatedAt).toISOString()}`,
'---',
'',
].join('\n');
return frontmatter + note.content;
}
5. CLI headless export commands
// cli/index.ts
program
.command('export <format>')
.description('Export all notes (formats: json, markdown, pdf, docx)')
.option('-o, --output <dir>', 'Output directory', './export')
.action(async (format, opts) => {
const notes = await loadNotesFromDb();
switch (format) {
case 'json': /* ... */ break;
case 'markdown': /* ... */ break;
case 'pdf': /* ... */ break;
case 'docx': /* ... */ break;
}
});
program
.command('import <file>')
.description('Import notes from JSON or Markdown')
.action(async (file) => { /* ... */ });
Acceptance Criteria
Problem
src/features/export/andsrc/lib/export-core.tsexist, anddocxis a dependency, but:cli/index.ts) lacks headless export commandsCurrent Export State
Proposed Implementation
1. PDF Export via
@react-pdf/renderer2. Canonical JSON schema
3. Markdown import with frontmatter
4. Markdown export with frontmatter
5. CLI headless export commands
Acceptance Criteria
@react-pdf/rendererinstalled and single-note PDF export workingKnowledgeStudioExportJSON schema v1.0 definedexportToJson()andimportFromJson()with schema validationexportNoteToMarkdown()with frontmatter (round-trip safe)importFromMarkdown()accepting file array, respecting frontmatterexportandimportcommands