Replacing Export-Time Sources Chapter with Persistent Back Matter
I needed to replace a synthetic Sources chapter that was being generated at export time with a real, user-controlled back matter chapter stored in the project file.
What Was Happening
The exporter was minting a Sources chapter at generate time in web/pages/index.tsx run(). It collected outline-entry sources, deduped them, and emitted a single matter:"back" chapter into the book body. This chapter was never persisted and not user-editable. Fact-check results lived only on AppState and never reached export.
Front and back matter chapters are created via newChapter({matter}) plus the per-card Section-type dropdown. No dedicated "add back matter" button exists. Ordering is pure chapters[] array order.
What Needed No Changes
The engine already handles matter:"back" correctly in PDF: recto start, arabic numbering continues, TOC entry without a chapter number. The parser regex only accepts front|back. DOCX and EPUB ignore matter entirely, which is a pre-existing inconsistency. No title-based special-casing exists anywhere.
What I Built
I added a new generated?: "sources" marker field to the Chapter interface. This gates a "Generate Sources / Refresh Sources" button that creates or updates a real back-matter chapter stored in chapters[].
The handler collects sources from outline entries. If the collection is empty, it shows a notice. If a chapter with generated:"sources" already exists, it refreshes the body only. Otherwise it appends a new matter:"back" source:"typed" chapter.
Matching happens by the generated marker, never by title. This means retitling to "Bibliography" never breaks refresh and hand-authored chapters are never touched.
I set source:"typed" not "ai" because nothing here is AI-generated. Sources are collected data.
No confirm dialog appears. This mirrors the single-target user-initiated overwrite pattern used in AI "Replace body" and per-chapter Upload.
Files Changed
web/lib/chapters.ts now includes generated?: "sources" in the Chapter interface. newChapter() passes it through, same pattern as entryRefId.
web/lib/project.ts widens ProjectFile.chapters Pick to include entryRefId and generated. toProjectData serializes generated. readChapters validates it, accepting only the literal "sources" or undefined. I also widened the Pick to include entryRefId, which was previously serialized but missing from the type.
web/pages/index.tsx no longer generates a synthetic Sources block in run(). It assembles from chapters directly. I added the generateSources handler, sourcesNotice state, hasSourcesChapter derived flag, the toolbar button, and the inline notice.
Build Notes
The className strings in index.tsx arrive from Read in a compressed placeholder form like disable[P5] and dar[P2] with a legend at the top of the file. I reconstructed the real Tailwind strings from the legend for old_string matching.
Root npm run build only builds the engine workspace, not web. I verified web separately via npm run typecheck --workspace @quire/web and npm run web:build. Both passed.