Full-stack Next.js (App Router) book formatting application: manuscript ingest → metadata & cover configuration → build job with step progression → EPUB and PDF artifact generation. Features real-time SSE events, customizable watermarks, and Kindle compatibility.
- Drag-and-drop multi-file uploads with duplicate detection, type/size validation, partial success (207 Multi-Status), JSONL logging, and per-file error surfacing.
- Local filesystem persistence (
web/data/uploads) + file metadata inference. - Upload list persisted client-side (excluding File blobs) with de-duping and drag reordering + removal.
- Per-file wizard flow: auto-advances when each stage (metadata, cover) is saved; final cover save auto-triggers build job.
- Context-driven state for metadata, cover config, and build options with debounced localStorage persistence.
- Controlled forms with validation feedback and toast confirmations.
- Per-file metadata & cover state maps keyed by file ID with route guarding (cannot access Metadata/Cover without at least one file).
- Unified job model (
jobType: ingest | build) stored injobs.jsonwith steps + artifacts. - Worker loop auto-started in Node runtime processing one pending job at a time.
- Build jobs have step timeline (running, done, error, canceled) and emit normalized SSE events.
- Cancellation mid-step updates job + remaining steps and emits events.
- Failure injection + accelerated durations via environment flags.
- Automatic build job creation after final per-file cover save (when user chooses Save & Build / or final wizard step).
- Server-Sent Events (
/api/events) broadcasting normalized envelopes:{ id, type, timestamp, jobId, jobType, ... }for: started, progress, step, artifacts, completed, canceled, failed. - Toast notifications for completion, cancellation, failure, and downloads.
- EPUB and PDF files generated with Puppeteer-based HTML conversion.
- Customizable watermarks with position, opacity, color, and size controls.
- Chapter detection with support for bonus chapters and subtitles.
- Kindle compatibility with proper EPUB3 metadata and TOC structure.
- Files become
readyat build completion and are downloadable via/api/downloads/:jobId/:type.
- Timeline UI for build steps; progress bar for ingest jobs.
- Filtering (type, status, ID search) + sorting (created, progress, status, type).
- Export filtered set as JSON or CSV.
- Accessible controls with persisted filter + sort state.
- Distinct styling for canceled vs failed vs completed; artifact readiness toasts.
- Global toast system with auto-dismiss and variants.
- Theme switching (DaisyUI v5) with persisted preference.
- Responsive layout & accessible labels.
This project uses DaisyUI v5 with an explicit plugin invocation and a single centralized theme definition file: web/daisyThemes.js.
Key points:
- Built-in themes are listed (with optional flags) in
BUILT_IN_THEMES.- Use
--defaultto mark the initial light theme. - Use
--prefersdarkto bind a theme to the user's OS dark preference.
- Use
- A custom
bookishtheme is defined via CSS custom properties (v5 variable naming) inBOOKISH_THEMEand injected by a small Tailwind plugin intailwind.config.js. - The UI consumes
UI_THEMES, which is derived from the build list (strips flags) plus the custom theme (system option intentionally omitted for a fixed explicit list). This guarantees the dropdown always matches what Tailwind compiled. - To add or remove a theme: edit
BUILT_IN_THEMES, restart the dev server (Tailwind must re-scan), and optionally re-order for display priority.
File overview:
web/daisyThemes.js– single source of truth (exportsBUILT_IN_THEMES,BOOKISH_THEME,UI_THEMES).web/tailwind.config.js– invokesdaisyui({ themes: BUILT_IN_THEMES })and injects the custom theme withaddBase.web/components/TopNav.tsx– importsUI_THEMESfor the theme selector.
Why centralization? Avoids mismatch between compiled themes and UI options, simplifies future theme trimming, and cleanly encapsulates the custom palette.
Migration notes (v4 → v5):
- v5 ignores the legacy
daisyui: { themes: [...] }array if flags or plugin options aren’t supplied correctly; we now pass options directly to the plugin call. - Theme selection flags replace old implicit behavior; the project uses explicit
--defaultand--prefersdarkfor deterministic system behavior. tailwind.config.tsnow intentionally delegates totailwind.config.jsso the JS file remains the single source of truth for plugin options and themes.
Environment flags (set before npm run dev or in .env.local):
TEST_MODE=1 # Speeds up step durations & ingest progress loop
FAIL_POINTS=BUILD_STEP2,INGEST_50 # Comma list of injected failuresSupported failure tokens:
- BUILD_STEPN (N = 1-based step index) – marks step as error and job failed.
- INGEST_P (P = integer progress %) – fails ingest job when progress matches.
Behavior under TEST_MODE:
- Build step durations shrink (~150–300ms per step).
- Ingest loop total duration reduced (~600ms base).
Integration tests are under web/__tests__/integration. To run them locally:
Step 1. Install dependencies in web:
cd web
npm installStep 2. Run the integration tests:
npm run test:integrationCI: The workflow .github/workflows/integration-tests.yml installs Playwright browsers (if available) and runs the integration tests in the web package.
For Windows-specific install tips and browser/runtime setup see docs/system-requirements.md.
web/lib/jobStore.tsFile-backed CRUD for jobs & manuscripts.web/lib/jobProcessor.tsWorker loop + step progression, cancellation checks, failure injection.web/lib/events.tsServer event bus.web/app/api/...Route handlers (uploads, build create, jobs list/cancel, downloads, events, admin stubs).web/app/state/*React context providers (AppState, Toasts).web/types/domain.tsShared types (Job, JobStep, Artifact, User stubs, etc.).
npm install
npm run dev
# Visit http://localhost:3000The application supports the following environment variables (set in .env.local or export before running):
JOB_WORKER=1- Enables the background job processor (required for build jobs to execute)
PUPPETEER_EXECUTABLE_PATH- Path to Chrome/Chromium executable (e.g.,/usr/bin/chromium)CHROME_PATH- Alternative to PUPPETEER_EXECUTABLE_PATHCHROME_BIN- Alternative to PUPPETEER_EXECUTABLE_PATH
At least one Chrome path variable should be set on Railway. The app will auto-detect system Chrome on macOS/Linux if not set.
DEPLOYMENT_NOTICE_ENABLED=true- Shows a banner at the top of the appDEPLOYMENT_NOTICE_MESSAGE- Text to display in the banner (e.g., "NOTICE: An update will be deployed...")DEPLOYMENT_NOTICE_LINK- Optional URL for the banner to link to
TEST_MODE=1- Speeds up step durations and ingest progress loop for testingFAIL_POINTS=BUILD_STEP2,INGEST_50- Comma-separated list of injected failure pointsNODE_ENV=test- Automatically enables TEST_MODE
STEP_TIMEOUT_MS- Timeout for each build step in milliseconds (default: 30000, TEST_MODE: 1000)STEP_MAX_RETRIES- Number of retries for failed steps (default: 1, TEST_MODE: 0)ARTIFACT_GEN_RETRIES- Number of retries for artifact generation (default: 1, TEST_MODE: 0)PDF_ENGINE=chromium- PDF generation engine (currently only chromium supported)NEXT_DIST_DIR- Custom Next.js build output directory (useful to avoid sync client locks on Windows)
JOB_WORKER=1
PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium
DEPLOYMENT_NOTICE_ENABLED=falseJOB_WORKER=1
TEST_MODE=1
NODE_ENV=developmentWhen deploying to Railway, configure the following settings in your service:
- Custom Build Command:
npm run build --workspace=book-formatter-web - Watch Paths:
/web/**(triggers deployment only when web files change)
- Custom Start Command:
npm run start --workspace=book-formatter-web
Required for PDF generation with Puppeteer:
chromium- Chrome/Chromium browser for PDF rendering
To add in Railway dashboard:
- Go to your service → Settings → Deploy
- Find "Apt Packages" section
- Add:
chromium
Set these in Railway dashboard under Variables:
JOB_WORKER=1(required)PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium(or leave blank for auto-detection)DEPLOYMENT_NOTICE_ENABLED=false(optional)
Railway automatically provides:
RAILWAY_ENVIRONMENT_NAME- Environment name (production, staging, etc.)RAILWAY_PROJECT_ID- Your project IDPORT- Port for the app to listen on (default: 8080)
- Railway uses Railpack (v0.13.0) which auto-detects Node.js workspace monorepos
- The build process automatically runs in the workspace root with proper npm workspace detection
- PDF generation requires Chromium to be installed via apt packages (not just as a build dependency)
Optional with simulation flags:
TEST_MODE=1 FAIL_POINTS=BUILD_STEP3 npm run devOn Windows (PowerShell) you can enable the background job worker in two ways:
- Persist across runs: create
web/.env.localwithJOB_WORKER=1(already added in this repo). - One-off in PowerShell: run:
$env:JOB_WORKER = '1'; npm run devNote: set JOB_WORKER=1 is a cmd.exe style assignment and won't export to PowerShell child processes. Use the PowerShell form above or .\web\.env.local for next dev to pick it up.
Generate an up-to-date dependency summary (with resolved versions) into docs/dependencies.md.
npm run docs:deps- Output:
docs/dependencies.md - Lockfile: uses
package-lock.jsonif present to show resolved versions
Core Pipeline: ✅ Complete - Full workflow from upload through metadata/cover configuration to artifact generation (EPUB + PDF with watermarks).
Artifact Generation: ✅ Complete - Real EPUB and PDF generation with Puppeteer, customizable watermarks, chapter detection, and Kindle compatibility.
Job Management: ✅ Complete - SSE events, cancellation, filtering/sorting, JSON/CSV export, timeline UI.
Pending Work: Auth integration, retention/cleanup policies, batch retry operations, progress smoothing.
- Retry / restart flows (single + batch, lineage via parentJobId) for failed/canceled jobs.
- Retention: prune old jobs, uploads, logs (configurable TTL + sweep utility).
- Auth integration + per-user scoped events & entitlement gating.
- Step-level heuristics (ETA + progress smoothing) and richer diagnostics panel.
- Artifact metadata enrichment (checksum, size, content-type for generated files).
- Structured JSONL event logging with correlation IDs.
- Accessibility: keyboard-based upload reordering, high-contrast theme audit.
See web/lib/auth.ts, web/lib/userStore.ts, web/lib/entitlements.ts and admin API stubs. Enable after choosing provider:
- Implement
getSession. - Provision user + initial entitlement sync.
- Gate admin + worker endpoints.
- Filter SSE events by
userId.
- All interactive controls have labels or
aria-label. - Color/status indicators use icon + text.
- LocalStorage keys namespaced and debounced to reduce churn.
From Jobs page: export filtered set as JSON or CSV for manual inspection or test artifacts.
- Keep new events normalized (extend SSE normalization switch only).
- Avoid introducing new step statuses without updating UI & docs.
- Prefer additive file-based persistence; migrations unnecessary during prototype.
| Symptom | Cause | Fix |
|---|---|---|
ENOTEMPTY / EPERM during npm install on Windows |
Sync clients (OneDrive/Dropbox) or open editors locking files inside node_modules or temp build folders |
Pause or disable sync clients, close editors/terminals that may hold handles, remove node_modules (root and web/node_modules), run npm cache clean --force, then cd web && npm install --legacy-peer-deps. See docs/system-requirements.md for PowerShell commands. |
UNKNOWN error opening server-reference-manifest.json or react-loadable-manifest.json (Windows + Dropbox) |
Sync client locking build artifacts | Use a non-synced build dir. EITHER keep web/next.config.js with distDir: 'node_modules/.next' OR keep web/next.config.ts and set NEXT_DIST_DIR to a non-synced path. Ensure only one config file remains. |
| EPERM rename errors when starting dev on Windows with Dropbox | Dropbox or other sync service locking webpack cache files in .next |
Run the included cache-clear script: pwsh -File web\scripts\clear-next-cache.ps1 and restart dev (npm run dev). The project also places webpack's dev cache in your OS temp dir to avoid future locks (see web/next.config.js). |
| Stale job list after restart | Worker not yet picked next job | Wait for interval or trigger a new upload/build |
| SSE disconnects on long idle | Browser/network timeout | Page auto-reconnects; refresh if no events after several minutes |
| Artifact buttons disabled | Artifacts not yet marked ready |
Wait for build completion; ensure no injected failure |
Optional: set a custom build output directory (outside synced folders) by exporting NEXT_DIST_DIR before starting dev.
set NEXT_DIST_DIR=.next-cache && npm run dev # Windows PowerShell (example)See CHANGELOG.md for detailed version history.
PDF Generation Overhaul - Replaced PDFKit with Puppeteer-core for HTML-to-PDF conversion with full watermark support (customizable text, position, opacity, color, size). Includes PDF validation with magic byte checking to prevent corrupted files. Optimized rendering from 160 DOM watermark elements to 15-element CSS grid for faster processing.
Performance Optimization - Increased PDF generation timeout from 30s to 60s for large documents. Changed page load strategy from 'networkidle0' to 'domcontentloaded' for faster rendering without waiting for all network requests.
EPUB Improvements - Removed cover page from table of contents to match standard EPUB conventions. Improved chapter detection for bonus chapters with subtitles. Enhanced watermark compatibility for Kindle readers.
Documentation - Added comprehensive environment variables guide, Railway deployment configuration with Chromium apt package requirements, and complete changelog of all 171 commits since project inception.
Accessibility & Fixes - Added aria-labels to all watermark form controls. Fixed duplicate chapter headings in PDF output. Fixed TypeScript errors in Next.js 15 DELETE routes.
Prototype / internal evaluation. Licensing TBD.