Guest User

Untitled

a guest
Aug 1st, 2026
61
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 11.46 KB | Software | 0 0
  1. # DESIGN — Interactive Story Webapp (Accelerando)
  2.  
  3. This document describes the architecture of the Accelerando interactive
  4. reader so the same pattern can be reused for other stories. Everything
  5. story-specific lives in three hand-authored files; everything else is generic.
  6.  
  7. ## Goals
  8.  
  9. - Static webapp (no server-side logic in production) deployable to any
  10. static host (Deno Deploy, Cloudflare Pages, etc.).
  11. - Lightweight reader first: the book is paginated per scene, nothing loads
  12. until needed.
  13. - Deep linkability: every sentence has a stable anchor, so the wiki and
  14. index can cite the exact sentence that supports a claim.
  15. - All "smarts" run at build time; the runtime is dumb, fast JSON + Vue.
  16.  
  17. ## Stack
  18.  
  19. - **Deno 2** as the toolchain (tasks, npm interop, build scripting).
  20. - **Vite + Vue 3** (`<script setup>` SFCs) with vue-router, hash history
  21. (`/#/read/c3s1`), so any static file server that serves `index.html` at the
  22. root works — deep links never reach the server as paths.
  23. - **No CSS libraries** — one hand-written `src/style.css` with CSS custom
  24. properties; automatic dark mode via `prefers-color-scheme`.
  25. - No runtime dependencies beyond vue + vue-router.
  26.  
  27. ```
  28. deno task dev # rebuild data + vite dev server (hot reload)
  29. deno task build # rebuild data + production build to dist/
  30. deno task data # just regenerate public/data from the source text
  31. ```
  32.  
  33. ## Pipeline: source text → static data
  34.  
  35. `tools/build-data.ts` parses the annotated source HTML (`accelerando.html`)
  36. at build time and emits JSON under `public/data/`:
  37.  
  38. | File | Purpose | Loaded |
  39. | --- | --- | --- |
  40. | `toc.json` | Part/Chapter/Scene tree + word counts | Home view |
  41. | `scenes/<id>.json` | One file per scene: pre-rendered paragraph HTML | Reader, per scene |
  42. | `glossary.json` | id → {name, summary, link} for hover popups | Reader, once |
  43. | `index.json` | entity list + mention counts (small) | Index view |
  44. | `mentions/<id>.json` | per-entity mention refs + snippets | Index, on expand |
  45. | `wiki.json` | wiki articles, infoboxes, resolved citations | Wiki views |
  46. | `timeline.json` | derived chronology: chapter datings + events, cited | Timeline view |
  47.  
  48. Key decisions:
  49.  
  50. - **Scene segmentation**: Parts and chapters come from `<h2>/<h3>` headings;
  51. scenes are split on the `* * *` separator (`<p class="gap">`). Scenes that
  52. are entirely `<blockquote>` (plus a hand-flagged list) are typed
  53. `interlude` and styled italic. Scene ids are `c<chapter>s<scene>`.
  54. - **Sentence anchors**: paragraphs are split into sentences with a
  55. heuristic splitter (abbreviation-aware, quote-aware). Every sentence is
  56. wrapped in `<span class="snt" id="p<para>s<sent>">`. A global sentence
  57. reference is the compact string `"chapter.scene.para.sent"`, rendered as
  58. a link to `/read/c<ch>s<sc>#p<p>s<s>` (rendered under the router hash,
  59. i.e. `/#/read/...` in the address bar).
  60. - **Pre-rendered HTML**: term highlighting and sentence wrapping happen at
  61. build time; the reader just `v-html`s paragraph strings. Only `<em>` and
  62. the generated `<span>`s survive from the source; entities are decoded and
  63. everything else is escaped.
  64. - **Term matching**: entity aliases are compiled to word-boundary regexes
  65. (case-sensitive when the alias contains capitals), matched longest-first
  66. with an occupancy mask so overlapping aliases don't double-claim text.
  67. Entities flagged `glossary: true` become `<span class="term"
  68. data-term="id">` spans; *all* entity matches are recorded as mentions for
  69. the index.
  70. - **Citations**: wiki articles contain `{{cite:some exact phrase}}`. The
  71. build searches all sentences for the phrase and replaces the marker with
  72. `[[ref:c.s.p.i]]`; unresolved citations print a warning, so typos are
  73. caught at build time, not in production.
  74.  
  75. ## Hand-authored inputs (the story-specific part)
  76.  
  77. 1. `tools/scene-meta.ts` (+ `scene-meta-late.ts`) — scene titles, interlude
  78. flags, and an image-generation prompt per scene. Regenerate the naming
  79. skeleton with `deno run --allow-read tools/build-data.ts --skeleton`,
  80. which dumps every scene's first/last line for titling.
  81. 2. `tools/entities-{people,world,tech}.ts` — the entity database:
  82. characters, locations, organizations, species, technologies, concepts.
  83. Each entry: aliases for matching, a one-paragraph summary (used by the
  84. reader hover infobox and wiki lead), an external link (Wikipedia etc.),
  85. optional `glossary` flag (highlight in reader), optional wiki article
  86. with infobox fields and `{{cite:…}}` markers, and an image prompt.
  87. 3. `tools/timeline.ts` — the derived story chronology (see "Timeline"
  88. below): per-chapter date estimates, timeline events, and per-sentence
  89. time annotations, all citing sentence refs.
  90. 4. The source text itself, with part/chapter headings annotated in HTML.
  91.  
  92. Porting to a new story = provide these three inputs; the parser only needs
  93. adjusting if the source markup conventions differ (heading tags, scene
  94. separator).
  95.  
  96. ## App structure
  97.  
  98. ```
  99. src/
  100. main.ts, router.ts # bootstrapping; lazy-loaded route components
  101. style.css # the entire design system
  102. lib/data.ts # typed fetch + in-memory cache for /data JSON
  103. lib/media.ts # media manifest store + dev upload client
  104. components/MediaSlot.vue # generated-art slot (see below)
  105. views/HomeView.vue # cover + Part/Chapter/Scene TOC
  106. views/ReaderView.vue # scene page: prose, anchors, term popups, pager
  107. views/TimelineView.vue # derived chronology: decades, chunks, citations
  108. views/WikiView.vue # wiki landing, cards grouped by entity type
  109. views/WikiEntryView.vue # article + infobox + citation links
  110. views/IndexView.vue # filterable index, lazy mention expansion
  111. ```
  112.  
  113. - **Reader**: keyboard ←/→ for prev/next scene. Term highlighting is pure
  114. CSS (`p:hover .term`); the popup is one absolutely-positioned element
  115. driven by event delegation (`mouseover`/`mouseout` on the prose
  116. container), with a grace timer so the popup itself can be hovered for its
  117. links. On navigation with a `#p…s…` hash, the sentence is scrolled to and
  118. flashed. Gotcha: Vue templates can't call globals like `new URL()` —
  119. compute derived strings in `<script setup>`.
  120. - **Index performance**: `index.json` holds only names/types/counts.
  121. Mention lists (a few hundred refs for main characters) live in per-entity
  122. files fetched on first expand and rendered in pages of 50 with a
  123. "show more" button. Nothing is ever rendered for collapsed entities.
  124. - **Wiki**: infobox is a plain `Record<string, string>` so each story can
  125. invent its own fields (ages, relationships, jobs…). Citations render as
  126. superscript `[n]` links to reader sentence anchors.
  127.  
  128. ## Generated art / LLM prompt workflow
  129.  
  130. Every scene and wiki entity has a **media slot** (`MediaSlot.vue`) keyed by
  131. scene id or entity id. Resolution order:
  132.  
  133. 1. A saved file exists in `public/media/<slot>.<ext>` → shown (dev & prod).
  134. 2. Dev mode + the nav "prompts" toggle on → the image-generation prompt is
  135. shown in a dashed box with a copy button; drag-and-dropping a generated
  136. image onto the box POSTs it to the dev server, which writes it to
  137. `public/media/` and regenerates `public/media/manifest.json`.
  138. 3. Otherwise the slot renders nothing (production never shows prompts).
  139.  
  140. The dev upload endpoint is a small Vite `configureServer` middleware in
  141. `vite.config.ts` (`GET/POST /api/media`). In production the app reads the
  142. static `manifest.json` instead. Because saved media lands in `public/`, it
  143. is committed with the repo and shipped by the normal static build. Prompts
  144. share a style prefix (defined in the scene/entity files) so the art
  145. direction stays consistent; recurring characters have shared description
  146. constants.
  147.  
  148. ## Timeline (derived chronology)
  149.  
  150. The novel almost never dates itself, so the timeline is *derived*: every
  151. temporal reference in the text (decade interludes, "three years later",
  152. character ages, real-world anchors like "KGB … canceled in 1991") was
  153. extracted with its sentence ref, then reconciled into a best-fit chronology.
  154. All of it lives in `tools/timeline.ts` as three hand-authored tables:
  155.  
  156. - `chapterDatings` — one date label per chapter (e.g. "c. 2015 · November")
  157. with a `basis` paragraph, confidence (`high`/`medium`/`low`), and
  158. supporting refs. The build stamps the label into `toc.json` and each
  159. scene's `chapter` object; the ToC and reader crumbs render it as a chip
  160. linking to `/timeline`.
  161. - `timelineEvents` — story events (chapter action, with scene links) and
  162. background events (SETI signals, the singularity, planet dismantling),
  163. each with a year for placement, description, confidence, and refs. The
  164. Timeline view groups them by decade and splits decades into
  165. early/mid/late chunks (everything past 2100 collapses into one "deep
  166. future" group, since the book itself gives up on calendars there).
  167. - `timeAnnotations` — `{ref, phrase, note}` triples. At build time the
  168. phrase is located inside the exact sentence at `ref` and wrapped in
  169. `<span class="time" data-note="…">`; the reader shows the note in a hover
  170. popup ("When is this?"), reusing the term-popup machinery. Time spans win
  171. over term spans when they overlap.
  172.  
  173. Like wiki citations, everything is validated at build time: unknown refs,
  174. unknown scene ids, and phrases that don't occur in their sentence all print
  175. warnings, so the chronology can't silently rot when the text or splitter
  176. changes. Internal contradictions in the book (it was fixed up from nine
  177. novellas — e.g. ch. 4's "fourth decade" vs. Amber's age) are documented in
  178. the entries themselves rather than smoothed over, and the confidence levels
  179. reflect them.
  180.  
  181. ### Image review dashboard (dev-only)
  182.  
  183. `tools/image-reviews.ts` holds a hand-authored (Claude-authored) review per
  184. scene id — a 1-10 rating of how well the generated image represents the
  185. scene, a note, and an optional `newPrompt` (a suggested reprompt) when the
  186. image misses. The build merges these into `public/data/image-reviews.json`
  187. (every scene's prompt, plus rating/note/newPrompt where reviewed). The Home
  188. (ToC) view, in dev only, renders per scene: an image-present indicator, the
  189. rating badge (a `?` if an image exists but hasn't been reviewed yet — which
  190. is how you notice newly-dropped images), a "regen" flag when a reprompt is
  191. suggested, and a copyable prompt panel (the reprompt if flagged, else the
  192. original). A summary bar shows coverage, average rating, and flagged count,
  193. with filters (all / with images / flagged). To review new images: drop them
  194. in, then add entries to `image-reviews.ts` and rebuild.
  195.  
  196. ## Deployment
  197.  
  198. `deno task build` → `dist/` is a fully static site. History-mode routing
  199. needs an SPA fallback: `public/_redirects` covers Cloudflare Pages; for
  200. Deno Deploy serve `dist/` with a 404 → `index.html` rewrite. All data URLs
  201. are absolute (`/data/…`, `/media/…`), so host at the domain root.
  202.  
  203. ## Known gotchas
  204.  
  205. - Deno's fs watcher can crash Vite on transient files created by other
  206. tooling; `server.watch.ignored` in `vite.config.ts` excludes non-source
  207. directories (`.claude`, `.git`, the source HTML).
  208. - The source HTML is Latin-1; the parser decodes it explicitly and maps
  209. named/numeric entities to UTF-8.
  210. - The sentence splitter is heuristic. Anchors are stable as long as the
  211. splitter and source text don't change; treat both as frozen once
  212. citations/mentions exist (or rebuild everything together, which the
  213. single build script guarantees).
  214.  
Advertisement
Add Comment
Please, Sign In to add comment