Book Publishing Architecture: MyStoryFlow MVP Implementation
Executive Summary
This document outlines the technical architecture for MyStoryFlow’s book publishing system, designed to transform user stories into print-ready books. The architecture leverages existing TipTap infrastructure with enhanced preview capabilities, following proven patterns from professional publishing tools.
Architecture Overview
System Components
Core Architecture Principles
1. Separation of Concerns
- Content Creation: TipTap editor optimized for writing flow
- Layout Processing: Preview system handles pagination and formatting
- Output Generation: Export pipeline produces print-ready files
2. Content-First Philosophy
- Users focus on story content without pagination distractions
- Rich content blocks maintain semantic integrity
- Layout decisions made during preview/export phase
3. Progressive Enhancement
- MVP delivers core book publishing functionality
- Advanced features added incrementally
- Existing editor system enhanced, not replaced
Technical Implementation
Component Architecture
1. Enhanced TipTap Editor
// Enhanced editor configuration
const bookEditor = useEditor({
extensions: [
// Core editing extensions
StarterKit.configure({
paragraph: { HTMLAttributes: { class: 'book-paragraph' } },
heading: { levels: [1, 2, 3] }
}),
// Typography optimized for books
Typography.configure({
openDoubleQuote: '"',
closeDoubleQuote: '"',
openSingleQuote: ''',
closeSingleQuote: '''
}),
// Rich content blocks for stories
QuoteBlock.configure({
HTMLAttributes: { class: 'story-quote' }
}),
FamilyRecipeBlock.configure({
HTMLAttributes: { class: 'family-recipe' }
}),
ImageBlock.configure({
allowBase64: false, // Require proper image URLs
HTMLAttributes: { class: 'story-image' }
}),
// Pagination extension (preview only)
TipTapPagination.configure({
mode: 'preview-only',
pageFormat: {
width: '6in',
height: '9in',
margins: {
top: '0.75in',
bottom: '0.75in',
left: '0.75in',
right: '0.75in'
}
}
})
],
// Performance optimization
editorProps: {
attributes: {
class: 'book-editor prose max-w-none'
}
}
})2. Content Processing Pipeline
interface ContentProcessor {
// Process raw editor content into book structure
processContent(editorContent: JSONContent): BookContent
// Extract chapters from story structure
extractChapters(content: BookContent): Chapter[]
// Generate table of contents
generateTOC(chapters: Chapter[]): TOCEntry[]
// Prepare rich content for pagination
prepareRichContent(blocks: ContentBlock[]): ProcessedBlock[]
}
class BookContentProcessor implements ContentProcessor {
processContent(editorContent: JSONContent): BookContent {
return {
frontMatter: this.extractFrontMatter(editorContent),
chapters: this.extractChapters(editorContent),
backMatter: this.extractBackMatter(editorContent),
metadata: this.extractMetadata(editorContent)
}
}
private extractChapters(content: JSONContent): Chapter[] {
// Logic to identify chapter breaks (H1 headings)
// Convert content blocks to chapter structure
// Preserve rich content block integrity
}
}3. Smart Pagination Engine
interface PaginationEngine {
// Calculate page breaks for processed content
calculatePageBreaks(content: BookContent): PageBreak[]
// Handle rich content block pagination
paginateRichContent(block: ProcessedBlock): BlockPagination
// Generate page-specific content
generatePageContent(pageNumber: number): PageContent
}
class SmartPaginationEngine implements PaginationEngine {
private paginationRules: PaginationRules = {
// Rich content handling
recipeBlocks: {
keepTogether: ['title', 'ingredients'],
allowBreak: 'ingredients-instructions',
minimumLines: 3
},
quoteBlocks: {
singlePageThreshold: 4,
keepWithAttribution: true,
preserveFormatting: true
},
imageBlocks: {
maxPageHeight: 0.75,
keepWithCaption: true,
fallbackBehavior: 'moveToNextPage'
},
// Typography rules
orphanControl: 2,
widowControl: 2,
// Chapter handling
chapterBreaks: 'always-new-page',
chapterTitles: 'keep-with-first-paragraph'
}
calculatePageBreaks(content: BookContent): PageBreak[] {
// Implementation of smart page break algorithm
// Considers content type, formatting, and readability
}
}4. Book Preview Component
interface BookPreviewProps {
content: BookContent
format: BookFormat
showMode: 'single-page' | 'facing-pages' | 'continuous'
onPageChange?: (pageNumber: number) => void
}
const BookPreview: React.FC<BookPreviewProps> = ({
content,
format,
showMode = 'single-page'
}) => {
const { pages, totalPages } = usePagination(content, format)
const [currentPage, setCurrentPage] = useState(1)
return (
<div className="book-preview">
{/* Book format styling */}
<style jsx>{`
.book-page {
width: ${format.width};
height: ${format.height};
margin: ${format.margins};
font-family: ${format.typography.fontFamily};
font-size: ${format.typography.fontSize};
line-height: ${format.typography.lineHeight};
}
`}</style>
{/* Preview pages */}
<div className="preview-container">
{showMode === 'single-page' ? (
<BookPage
content={pages[currentPage - 1]}
pageNumber={currentPage}
format={format}
/>
) : (
pages.map((page, index) => (
<BookPage
key={index}
content={page}
pageNumber={index + 1}
format={format}
/>
))
)}
</div>
{/* Preview controls */}
<BookPreviewControls
currentPage={currentPage}
totalPages={totalPages}
onPageChange={setCurrentPage}
onFormatChange={/* ... */}
/>
</div>
)
}Book Format Specifications
Standard Trim Sizes
export const BOOK_FORMATS = {
'6x9': {
width: '6in',
height: '9in',
margins: {
top: '0.75in',
bottom: '0.75in',
inner: '0.875in', // Binding side
outer: '0.625in' // Outer edge
},
gutterMargin: '0.25in'
},
'5.5x8.5': {
width: '5.5in',
height: '8.5in',
margins: {
top: '0.75in',
bottom: '0.75in',
inner: '0.75in',
outer: '0.5in'
}
},
'5x8': {
width: '5in',
height: '8in',
margins: {
top: '0.625in',
bottom: '0.625in',
inner: '0.75in',
outer: '0.5in'
}
}
} as constTypography Configuration
export const BOOK_TYPOGRAPHY = {
fonts: {
body: {
family: 'Crimson Text, Georgia, serif',
size: '11pt',
lineHeight: 1.4,
letterSpacing: '0.01em'
},
headings: {
family: 'Source Sans Pro, -apple-system, sans-serif',
chapterTitle: {
size: '18pt',
weight: 600,
letterSpacing: '0.02em',
marginTop: '2in',
marginBottom: '0.5in'
},
sectionHeading: {
size: '14pt',
weight: 500,
marginTop: '1.5em',
marginBottom: '0.75em'
}
},
quotes: {
family: 'inherit',
size: '10pt',
style: 'italic',
indent: '0.5in',
marginTop: '1em',
marginBottom: '1em'
}
},
spacing: {
paragraphSpacing: '0em', // No space between paragraphs
paragraphIndent: '0.25in', // First line indent
chapterSpacing: '2in', // Space before chapter title
sectionSpacing: '1.5em' // Space before section headings
}
}Front Matter & Book Structure
Automatic Front Matter Generation
interface FrontMatter {
titlePage: TitlePageConfig
copyright: CopyrightConfig
dedication?: string
tableOfContents: TOCConfig
acknowledgments?: string
}
class FrontMatterGenerator {
generateTitlePage(bookMetadata: BookMetadata): TitlePageContent {
return {
title: bookMetadata.title,
subtitle: bookMetadata.subtitle,
author: bookMetadata.author,
publisher: bookMetadata.publisher || 'MyStoryFlow Publishing',
publishDate: bookMetadata.publishDate || new Date()
}
}
generateTableOfContents(chapters: Chapter[]): TOCEntry[] {
return chapters.map((chapter, index) => ({
title: chapter.title,
pageNumber: chapter.startPage,
level: chapter.level,
children: chapter.sections?.map(section => ({
title: section.title,
pageNumber: section.startPage,
level: section.level
}))
}))
}
generateCopyrightPage(bookMetadata: BookMetadata): CopyrightContent {
return {
copyrightNotice: `© ${new Date().getFullYear()} ${bookMetadata.author}`,
isbn: bookMetadata.isbn,
publisher: bookMetadata.publisher,
edition: bookMetadata.edition || '1st Edition',
printingInfo: 'Printed in the United States of America',
additionalInfo: bookMetadata.copyrightInfo
}
}
}Rich Content Block Handling
Recipe Block Pagination
class RecipeBlockPaginator {
paginateRecipe(recipe: FamilyRecipeBlock): RecipePageLayout {
const components = {
title: recipe.title,
description: recipe.description,
ingredients: recipe.ingredients,
instructions: recipe.instructions,
notes: recipe.notes,
attribution: recipe.attribution
}
// Calculate space requirements
const spaceNeeds = this.calculateSpaceNeeds(components)
// Apply pagination rules
if (spaceNeeds.total < this.availablePageSpace * 0.8) {
// Keep entire recipe on one page
return { layout: 'single-page', components }
} else {
// Smart break between ingredients and instructions
return {
layout: 'multi-page',
page1: [components.title, components.description, components.ingredients],
page2: [components.instructions, components.notes, components.attribution]
}
}
}
}Quote Block Formatting
class QuoteBlockFormatter {
formatQuote(quote: QuoteBlock): FormattedQuote {
return {
content: quote.content,
attribution: quote.attribution,
formatting: {
indent: '0.5in',
fontSize: '10pt',
fontStyle: 'italic',
marginTop: '1em',
marginBottom: '1em',
// Ensure quote and attribution stay together
pageBreakInside: 'avoid'
}
}
}
}Export Pipeline
PDF Generation
interface PDFExporter {
generatePDF(bookContent: BookContent, format: BookFormat): Promise<Buffer>
generatePrintReady(bookContent: BookContent, printSpecs: PrintSpecs): Promise<Buffer>
}
class BookPDFExporter implements PDFExporter {
async generatePDF(bookContent: BookContent, format: BookFormat): Promise<Buffer> {
// Use puppeteer or similar for high-quality PDF generation
const html = this.renderBookHTML(bookContent, format)
const pdfOptions = {
format: 'A4', // Or custom dimensions
margin: {
top: format.margins.top,
bottom: format.margins.bottom,
left: format.margins.inner,
right: format.margins.outer
},
printBackground: true,
displayHeaderFooter: true,
headerTemplate: this.generateHeaderTemplate(bookContent),
footerTemplate: this.generateFooterTemplate(bookContent)
}
return await this.browser.pdf(pdfOptions)
}
private renderBookHTML(content: BookContent, format: BookFormat): string {
// Render complete book HTML with CSS print styles
return `
<!DOCTYPE html>
<html>
<head>
<title>${content.metadata.title}</title>
${this.generatePrintCSS(format)}
</head>
<body>
${this.renderFrontMatter(content.frontMatter)}
${this.renderChapters(content.chapters)}
${this.renderBackMatter(content.backMatter)}
</body>
</html>
`
}
}Print-Ready Specifications
export const PRINT_SPECIFICATIONS = {
colorSpace: 'CMYK',
resolution: 300, // DPI
bleed: '0.125in',
safetyMargin: '0.125in',
imageRequirements: {
minResolution: 300,
maxFileSize: '10MB',
acceptedFormats: ['JPEG', 'PNG', 'TIFF']
},
fontEmbedding: {
embedAll: true,
subsetFonts: true,
createOutlines: false // Preserve text selectability
}
}Performance Optimization
Lazy Loading Strategy
class BookPreviewOptimizer {
// Only render visible pages plus buffer
private renderBuffer = 2 // Pages before/after current view
getVisiblePages(currentPage: number, totalPages: number): number[] {
const start = Math.max(1, currentPage - this.renderBuffer)
const end = Math.min(totalPages, currentPage + this.renderBuffer)
return Array.from({ length: end - start + 1 }, (_, i) => start + i)
}
// Virtualize page rendering for large books
renderVirtualizedPages(pages: PageContent[]): VirtualizedPageList {
// Implementation similar to react-window
// Only render pages in viewport + buffer
}
}Caching Strategy
class BookRenderCache {
private pageCache = new Map<string, RenderedPage>()
private paginationCache = new Map<string, PageBreak[]>()
// Cache rendered pages by content hash
getCachedPage(contentHash: string): RenderedPage | null {
return this.pageCache.get(contentHash) || null
}
// Cache pagination calculations
getCachedPagination(contentHash: string): PageBreak[] | null {
return this.paginationCache.get(contentHash) || null
}
// Invalidate cache when content changes
invalidateCache(contentId: string): void {
// Remove related cache entries
}
}Integration with Existing System
Database Schema Extensions
-- Extend existing stories table for book publishing
ALTER TABLE stories ADD COLUMN IF NOT EXISTS book_metadata JSONB DEFAULT '{}';
ALTER TABLE stories ADD COLUMN IF NOT EXISTS front_matter JSONB DEFAULT '{}';
ALTER TABLE stories ADD COLUMN IF NOT EXISTS publication_settings JSONB DEFAULT '{}';
-- New table for book formats and templates
CREATE TABLE IF NOT EXISTS book_formats (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name TEXT NOT NULL,
display_name TEXT NOT NULL,
dimensions JSONB NOT NULL, -- { width, height, margins }
typography JSONB NOT NULL, -- Font settings
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- Book export history
CREATE TABLE IF NOT EXISTS book_exports (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
story_id UUID REFERENCES stories(id) ON DELETE CASCADE,
user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE,
format_id UUID REFERENCES book_formats(id),
export_type TEXT NOT NULL, -- 'pdf', 'epub', 'print'
file_url TEXT,
export_settings JSONB DEFAULT '{}',
created_at TIMESTAMPTZ DEFAULT NOW()
);API Endpoints
// Book publishing endpoints
export const bookAPI = {
// Preview endpoints
'/api/books/preview': {
POST: (storyId: string, format: BookFormat) => BookPreview
},
// Export endpoints
'/api/books/export/pdf': {
POST: (storyId: string, options: PDFOptions) => Buffer
},
'/api/books/export/epub': {
POST: (storyId: string, options: EPUBOptions) => Buffer
},
// Format management
'/api/books/formats': {
GET: () => BookFormat[],
POST: (format: BookFormat) => BookFormat
}
}Testing Strategy
Unit Tests
- Content processing functions
- Pagination algorithm accuracy
- Rich content block handling
- Typography calculations
Integration Tests
- End-to-end book publishing workflow
- Preview accuracy vs PDF output
- Performance with large documents
- Cross-browser compatibility
Visual Regression Tests
- Page layout consistency
- Typography rendering
- Rich content formatting
- Print preview accuracy
Deployment Strategy
Feature Flags
export const BOOK_PUBLISHING_FLAGS = {
enableBookPreview: true,
enablePDFExport: true,
enableAdvancedTypography: false, // Future feature
enableCollaborativeEditing: false // Future feature
}Rollout Plan
- Week 1: Internal testing with small documents
- Week 2: Beta testing with selected users
- Week 3: Gradual rollout to all users
- Week 4: Performance monitoring and optimization
Future Enhancements
Advanced Features (Post-MVP)
- Multi-format support: EPUB, MOBI, hardcover specifications
- Advanced typography: Drop caps, pull quotes, custom fonts
- Collaborative editing: Real-time collaboration on book projects
- Professional templates: Genre-specific book layouts
- Print on demand: Integration with printing services
Performance Improvements
- WebAssembly: High-performance pagination calculations
- Service workers: Offline book editing and preview
- CDN optimization: Fast loading of book assets and fonts
This architecture provides a solid foundation for MyStoryFlow’s book publishing capabilities while maintaining flexibility for future enhancements and optimizations.
This document serves as the technical specification for MyStoryFlow’s book publishing system. Last updated: January 2025