qino-chronicles System Reference
Complete reference for the qino-chronicles publishing and display system.
System Overview
qino-chronicles transforms markdown-based chronicles from source repositories into a web reading experience with generated images. The system spans three repositories and uses Cloudflare infrastructure (Workers, R2, Queues, KV).
High-Level Architecture
The complete system: source repos publish to backend, which stores content and queues image generation. Frontend serves markdown while browsers fetch images directly from public R2.
┌─────────────────────┐ ┌──────────────────────┐
│ Source Repos │ GitHub Action │ Backend Worker │
│ (qino-claude, │ ──────────────────────▶│ │
│ concepts-repo) │ POST /publish │ ┌────────────────┐ │
└─────────────────────┘ │ │ Publish Route │ │
│ └───────┬────────┘ │
│ │ │
│ ┌───────▼────────┐ │
│ │ Image Queue │ │
│ └───────┬────────┘ │
│ │ │
│ ┌───────▼────────┐ │
│ │ Queue Consumer │──┼──▶ AI Image APIs
│ └────────────────┘ │ (Gemini, FLUX)
└──────────┬───────────┘
│
┌────────────────────────────┼────────────────────────────┐
│ │ │
▼ ▼ ▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ R2: Content │ │ R2: Images │ │ KV: Status │
│ (markdown) │ │ (public URL) │ │ (generation) │
└────────┬────────┘ └────────┬────────┘ └─────────────────┘
│ │
│ │ direct browser access
▼ ▼
┌─────────────────────────────────────────────────────────┐
│ Frontend Worker │
│ (TanStack Start) │
└─────────────────────────────────────────────────────────┘
│
▼
┌─────────┐
│ Browser │
└─────────┘
Publishing Flow
When chapter content changes in a source repo, a GitHub Action posts it to the backend, which stores markdown in R2 and queues image generation jobs.
┌──────────────┐ ┌─────────────────┐ ┌─────────────┐ ┌─────────────┐
│ Git Push │───▶│ GitHub Action │───▶│ Backend │───▶│ R2 Content │
│ chronicle/** │ │ │ │ /publish │ │ │
└──────────────┘ └─────────────────┘ └──────┬──────┘ └─────────────┘
│
▼
┌─────────────┐
│ Image Queue │
└─────────────┘
Image Generation Flow
Queue consumer processes jobs asynchronously: classifies tokens, builds prompts from chapter context, calls AI APIs, and stores results in public R2.
┌─────────────┐ ┌─────────────────┐ ┌─────────────┐ ┌─────────────┐
│ Image Queue │───▶│ Queue Consumer │───▶│ AI APIs │───▶│ R2 Images │
│ │ │ (prompt build, │ │ (Gemini, │ │ (public) │
│ │ │ classification)│ │ FLUX) │ │ │
└─────────────┘ └─────────────────┘ └─────────────┘ └─────────────┘
Reading Flow
Browser requests go to frontend worker for markdown content. Images load directly from public R2 URL, bypassing the worker for better performance.
┌─────────┐ ┌─────────────────┐ ┌─────────────┐
│ Browser │───▶│ Frontend Worker │───▶│ R2 Content │ (markdown via worker)
│ │ │ │ └─────────────┘
│ │ └─────────────────┘
│ │
│ │─────────────────────────▶ R2 Images (public URL, direct access)
└─────────┘
Repositories
1. Source Repositories (e.g., qino-claude)
Location: ~/Code/qinolabs/qino-claude/
Chronicle content lives here. Each source repo contains:
chronicle/
├── manifest.json # Chronicle metadata and chapter index
├── world-seed.md # The world's seed/setting with Setting Foundation frontmatter
├── world.md # Living world-state (characters, locations, pressures)
├── arcs.md # All arcs (in motion + completed) with chapter ranges
└── chapters/
├── 001-the-arrival/
│ ├── chapter.md # Chapter content
│ ├── world.md # Snapshot at write time
│ └── arcs.md # Snapshot at write time
└── ...
Key Files:
| File | Purpose |
|---|---|
manifest.json | Chronicle title, chapter list with slugs/titles/dates/git refs |
world-seed.md | Setting Foundation (YAML frontmatter) + prose atmosphere (seed for generation) |
world.md | Characters, locations, pressures, wanderer state, season, current breath |
arcs.md | Narrative arcs with chapter ranges for temporal display |
chapters/*/chapter.md | Individual chapter content with World Tokens sections |
chapters/*/world.md | Snapshot of world.md at the time the chapter was written |
chapters/*/arcs.md | Snapshot of arcs.md at the time the chapter was written |
Example (qino-claude): chapters/001-what-the-water-brings/ holds the first chapter where The Mender token is introduced; its world.md snapshot does not yet include upstream locations that appear in later chapters.
GitHub Action: .github/workflows/chronicle-publish.yml
Triggers on push to main when chronicle/** changes. Reads all chronicle files and POSTs to backend.
2. Backend (qino-chronicles-backend)
Location: ~/Code/qinolabs/qinolabs-repo/apps/qino-chronicles-backend/
Cloudflare Worker that receives published content, stores in R2, and manages image generation.
Key Files:
| File | Purpose |
|---|---|
src/routes/publish.ts | Receives chronicle content, stores in R2, queues image jobs |
src/routes/status.ts | Returns image generation status for chapters |
src/routes/manifest.ts | Returns merged image manifests for chapters |
src/queue-handler.ts | Processes image generation jobs from queue |
wrangler.jsonc | Cloudflare bindings (R2, KV, Queue) |
Cloudflare Bindings:
| Binding | Type | Purpose |
|---|---|---|
CONTENT_BUCKET | R2 | Stores markdown content (world.md, arcs.md, chapters) |
IMAGES_BUCKET | R2 | Stores generated images |
IMAGE_STATUS | KV | Tracks image generation status per chapter |
IMAGE_QUEUE | Queue | Queues image generation jobs |
3. Frontend (qino-chronicles)
Location: ~/Code/qinolabs/qinolabs-repo/apps/qino-chronicles/
TanStack Start app that reads from R2 and displays chronicles.
Key Files:
| File | Purpose |
|---|---|
src/server/get-chronicles.ts | Server functions to fetch/parse R2 content |
src/lib/chronicles.ts | Helper functions for parsing world.md |
src/routes/$repoName/$chapterSlug/index.tsx | Chapter display route |
src/components/chapter-end-matter.tsx | Arcs and tokens display |
src/components/journal-book.tsx | Arc details dialog |
Data Flow
Phase 1: Publishing (Source → Backend → R2)
┌──────────────┐ ┌─────────────────┐ ┌─────────────┐ ┌─────┐
│ Git Push to │───▶│ GitHub Action │───▶│ POST │───▶│ R2 │
│ main branch │ │ chronicle- │ │ /publish │ │ │
│ │ │ publish.yml │ │ │ │ │
└──────────────┘ └─────────────────┘ └─────────────┘ └─────┘
Step-by-step:
1. Git push to source repo's main branch with changes in chronicle/**
2. GitHub Action (chronicle-publish.yml) triggers and reads files:
chronicle/world.md → WORLD_CONTENT
chronicle/world-seed.md → THEME_CONTENT
chronicle/arcs.md → ARCS_CONTENT
chronicle/manifest.json → MANIFEST_JSON
chronicle/chapters/*/chapter.md → CHAPTERS_JSON array (with optional world/arcs snapshots)
Example payload rows:
- `chapters[0]`: slug `001-the-arrival` (concepts-repo) with world/arcs snapshots taken after Chapter 1 edits
- `chapters[1]`: slug `001-what-the-water-brings` (qino-claude) with tokens **The Mender**, **The Meeting Point**
3. POST to backend /publish endpoint:
{
"repoName": "qino-claude",
"world": "# The World\n...",
"theme": "# Theme\n...",
"arcs": "# Arcs\n...",
"manifest": { "title": "...", "lastGitRef": "..." },
"chapters": [
{ "slug": "001-first", "content": "# Title\n..." }
],
"force": false
}
4. Backend processes (publish.ts):
- Validates API key and repo allowlist
- Extracts metadata from chapters (title, dates, git refs)
- Updates
repos.jsonindex - Queues image generation jobs
- Stores in R2:
chronicle-content/
└── qino-claude/
├── manifest.json (generated with chapter metadata)
├── world.md
├── world-seed.md
├── arcs.md
└── chapters/
├── 001-first/
│ ├── chapter.md (chapter content)
│ ├── world.md (snapshot)
│ └── arcs.md (snapshot)
└── ...
Phase 2: Reading (Frontend ← R2)
┌──────────┐ ┌─────────────────────┐ ┌─────┐
│ Browser │───▶│ Frontend Worker │───▶│ R2 │
│ Request │ │ get-chronicles.ts │ │ │
└──────────┘ └─────────────────────┘ └─────┘
Step-by-step:
1. Browser requests /{repoName}/{chapterSlug}/
2. Route loader calls server functions:
// get-chronicles.ts
const result = await getChapterData({
data: { repoName, chapterSlug }
});
3. Server functions fetch from R2:
// Fetches in parallel:
// - manifest.json → chapter list, metadata
// - world.md → characters, locations, pressures
// - arcs.md → all arcs with chapter ranges
// - chapters/{slug}/chapter.md → chapter content (snapshots stored alongside)
Example (concepts-repo Chapter 2): Loader pulls canonical `world.md` (now includes "The Alcove"), but hover cards favor Chapter 2 tokens for **Selin** ("Eyes like deep water. Watches what appears in adjacency.") over the canonical description.
4. Parsing:
// get-chronicles.ts
const arcs = parseArcs(arcsContent); // → Arc[]
// chronicles.ts
const world = parseWorld(worldContent); // → { characters, locations } with descriptions
5. Entity highlighting (builds map for hover cards):
// highlighted-markdown.tsx
const entityInfoMap = buildEntityInfoMap(
world.characters, // from world.md with descriptions
world.locations, // from world.md with descriptions
chapter.tokens, // current chapter's World Tokens
historicalTokens, // tokens from previous chapters
);
6. Arc filtering (in route loader):
// Filter arcs for current chapter
const currentChapterNum = chapterIndex + 1;
const { arcsInMotion, completedArcs } = filterArcsForChapter(
chronicle.arcs,
currentChapterNum
);
Example (qino-claude, Chapter 6): arcsInMotion includes "The Journey Upstream" (6-) and "The Wanderer's Hands" (1-); completedArcs includes "The Pattern-Keeper's Question" (2-3).
7. Render with filtered data
Key Interfaces
arcs.md Format
# Arcs
*Narrative threads — in motion and complete.*
---
## [Arc Name]
*Shape:* [hidden / motion / building / between / calling outward]
*Chapters:* [start]-[end] or [start]- (ongoing)
**In motion:**
*Holds:* [the question this arc explores]
*Moves toward:* [direction, not script]
**Completed:** (only for completed arcs)
*Resolved:* [how it ended]
*Yielded:*
- [outcome 1]
- [outcome 2]
*Changed between people:*
- [relational shift]
Chapter range syntax:
*Chapters:* 2-5— Completed arc (chapters 2 through 5)*Chapters:* 6-— In-motion arc (started chapter 6, ongoing)
Example (qino-claude): ## The Journey Upstream — Shape: calling outward, Chapters: 6-, holds "What opened in the interior? What is the pouch?" and moves toward "Arrival at the coordinates — and whatever waits there."
Arc TypeScript Interface
// get-chronicles.ts
export interface Arc {
name: string;
shape: string;
startChapter: number;
endChapter: number | null; // null = in motion
// In motion fields (always present)
holds: string;
movesTo: string;
// Completed fields (only if endChapter !== null)
resolved?: string;
yielded?: string[];
changedBetweenPeople?: string[];
}
Chronicle Interface
export interface Chronicle {
repoName: string;
manifest: ChronicleManifest;
world: string;
arcs: Arc[]; // Unified arcs from arcs.md
journal: JournalEntry[]; // Legacy, kept for compatibility
chapters: Chapter[];
}
Publish Payload Schema
// publish.ts
const publishPayloadSchema = z.object({
repoName: z.string().min(1),
chapters: z.array(chapterSchema).min(1),
world: z.string().optional().default(""),
theme: z.string().optional().default(""),
journal: z.string().optional().default(""), // Legacy
arcs: z.string().optional().default(""), // Unified arcs
manifest: sourceManifestSchema.optional(),
removedChapters: z.array(z.string()).optional(),
force: z.boolean().optional().default(false),
});
Arc Filtering Logic
The frontend filters arcs based on chapter number for temporal display:
function filterArcsForChapter(
arcs: Arc[],
chapterNum: number
): { arcsInMotion: Arc[]; completedArcs: Arc[] } {
const arcsInMotion: Arc[] = [];
const completedArcs: Arc[] = [];
for (const arc of arcs) {
// In motion: started at/before this chapter AND not yet completed
const isInMotion =
arc.startChapter <= chapterNum &&
(arc.endChapter === null || arc.endChapter > chapterNum);
// Completed: has endChapter at or before this chapter
const isCompleted =
arc.endChapter !== null && arc.endChapter <= chapterNum;
if (isInMotion) {
arcsInMotion.push(arc);
} else if (isCompleted) {
completedArcs.push(arc);
}
}
return { arcsInMotion, completedArcs };
}
Example: For a chronicle with these arcs:
- Arc A: Chapters 1-3 (completed)
- Arc B: Chapters 2-5 (completed)
- Arc C: Chapters 4- (in motion)
| Reading Chapter | Arcs in Motion | Completed Arcs |
|---|---|---|
| Chapter 1 | A | - |
| Chapter 2 | A, B | - |
| Chapter 3 | B | A |
| Chapter 4 | B, C | A |
| Chapter 5 | C | A, B |
| Chapter 6 | C | A, B |
Entity Highlighting & Hover Cards
The frontend highlights character and location names in chapter text and displays hover cards with contextual information.
Data Sources
| Source | Scope | Contains |
|---|---|---|
world.md Characters/Locations | World | Canonical list with descriptions |
| Chapter World Tokens | Chapter | Evocative presence text for this chapter |
| Historical Tokens | Cumulative | Tokens from previous chapters |
Presence Priority Chain
When determining what to show in a hover card, the system uses this priority:
1. Current chapter's token presence (most specific)
↓ if not found
2. Historical token presence (from previous chapters)
↓ if not found
3. world.md description (canonical fallback)
Example:
| Entity | Has Token? | Hover Card Shows |
|---|---|---|
| Selin | Yes (Ch2 token) | "Eyes like deep water. Watching what the wanderer will do next." |
| The Entrance Hall | Yes (Ch1 token) | "Where the work happens after dark..." |
| The Mountain Spaceport | No token | world.md description: "A single berth carved into the mountainside..." |
Concrete content:
- Concepts-repo Chapter 1 token for The Entrance Hall → hover text shows warm arguments and cold tea; reading Chapter 2 still uses this unless overridden.
- qino-claude Chapter 1 token for The Mender → hover text stays "Works where river meets sea..." even when Chapter 3 adds inland arcs, until a new token updates her presence.
- Presence shifts over time: if concepts-repo Chapter 3 later adds a token for The Entrance Hall about midnight quiet, that new token would take precedence for that chapter, while earlier chapters remain anchored to the Chapter 1 presence.
How It Works
1. parseWorld() extracts characters and locations from world.md with their descriptions:
interface WorldEntity {
name: string;
content?: string; // Description text from world.md
}
2. buildEntityInfoMap() creates a lookup map:
- Adds all characters and locations from world.md
- For each entity, resolves presence using the priority chain
- Assigns highlight colors by type (warm for characters, cool for locations)
3. HighlightedMarkdown component:
- Scans text for entity name matches
- Wraps matches in colored spans
- If presence exists, adds hover card trigger
world.md Format (pressures, breath, season, entities)
Top sections carry ambient context that accumulates:
- Pressures: What's building/calling. Example (concepts-repo): The Dark Below → "Something moved down there, patient as stone."
- The World's Breath: Current atmosphere snapshot. Example: "Morning light through narrow windows. The crystals have shifted their alignment..."
- The Season: Temporal anchor. Example: "Early in the wanderer's time... Something is beginning — not yet visible, but present..."
- The Wanderer: State and carries.
- Characters / Locations: Canonical descriptions (see parsing below).
## Characters
### Selin
First appeared: Chapter 1 (as "the woman by the window"), named Chapter 2
Cartographer, but not of distances. Eyes like deep water — a color
that shifts with the light. Studies arrivals the way the crystals
study the sky.
### The Tea-Bearer
First appeared: Chapter 1, in the entrance hall
Young or ageless — hard to categorize. Moves between groups with
a tray, tending the nightly ritual.
## Locations
### The Mountain Spaceport
A single berth carved into the mountainside. Amber lamps. The path
to the observatory is visible as a pale line against darker rock.
Notes:
### Nameheaders define entity names- "First appeared:" lines are stripped from descriptions
- All text after the header until the next
###becomes the description
Chapter World Tokens Format
## World Tokens
**The Entrance Hall**
Where the work happens after dark. Clusters of conversation,
arguments over charts, tea grown cold during debate.
**Selin**
Eyes like deep water. Watching what the wanderer will do next.
World Tokens provide chapter-specific "presence" — how an entity feels in this moment, not its canonical description.
Key Files
| File | Purpose |
|---|---|
src/lib/chronicles.ts | parseWorld() — extracts entities with descriptions |
src/components/highlighted-markdown.tsx | buildEntityInfoMap() — presence resolution |
src/components/entity-hover-card.tsx | Hover card UI component |
Image Generation
The backend generates images for World Tokens in each chapter using AI image generation.
What Gets Images
| Image Type | Source | Trigger |
|---|---|---|
| Hero | Chapter content analysis | New/changed chapter |
| Wanderer | ## The Wanderer section | New/changed wanderer section |
| Atmosphere | Mid-chapter breath point | New/changed chapter |
| Tokens | ## World Tokens section | New/changed tokens |
Differential Regeneration
The backend uses content hashing to avoid regenerating unchanged images:
// Content hashes stored in manifest
contentHashes: {
chapter: "abc123", // Hash of chapter content
wanderer: "def456", // Hash of wanderer section
theme: "ghi789", // Hash of world-seed.md
world: "jkl012", // Hash of world.md
tokens: { // Hash per token
"selin": "mno345",
"the-entrance-hall": "pqr678"
}
}
Regeneration triggers:
- Theme or world changes → regenerate everything (affects visual style)
- Chapter content changes → regenerate hero + atmosphere
- Wanderer section changes → regenerate wanderer image
- Token presence changes → regenerate that specific token
- New token → generate it
- Removed token → clean up image
Token-to-Image Flow
Chapter published with World Tokens
│
▼
Backend extracts tokens from chapter.md
│
▼
Compare token hashes with existing manifest
│
├─► New token (no hash) → Generate image
├─► Changed token (hash differs) → Regenerate
└─► Unchanged token → Skip (keep existing)
Key implication: For an entity to get an image, it must appear as a World Token in some chapter. Entities only in world.md (no token) get hover cards but no images.
Example (qino-claude): The Mender token in Chapter 1 triggers image generation using Chapter 1's snapshots; even though later chapters mention inland coordinates, the token image sticks to the harbor context unless a new token updates it.
Prompt Building Pipeline
The backend uses a multi-stage pipeline to build image prompts:
world-seed.md ──────────────────────────────────────────────────────────────┐
├── YAML frontmatter → SettingFoundation (13 structured fields) │
└── prose content → visualLanguage (AI-distilled rendering style) │
▼
world.md ───► worldBreath (current atmosphere) ┌─────────────────┐
│ synthesize │
chapter.md ──► heroDirection / tokenVisualization (AI-classified) │ SceneContext │
└────────┬────────┘
│
▼
┌─────────────────────┐
│ Prompt Builder │
│ (deterministic) │
└──────────┬──────────┘
│
▼
Image API
Setting Foundation (world-seed.md YAML frontmatter):
13 fields that capture the world's visual vocabulary:
| Field | Purpose |
|---|---|
genre | Primary classification (fantasy, science-fiction, contemporary, horror, hybrid) |
subgenre | Specific tone (e.g., "gentle secondary world — patient wonder, age of sail") |
scale | Narrative scope (e.g., "intimate and local — a single coastal town") |
geography | Terrain, atmosphere, sky |
era | Visual time period/aesthetic |
architecture | Built environment vocabulary |
lighting | Dominant light quality |
technology | Technology visual vocabulary |
transport | Vehicles/movement (critical for avoiding anachronisms) |
flora | Plants/vegetation |
fauna | Animals/creatures |
inhabitants | Who populates this world |
magic | How magic LOOKS (or "none — hard science fiction") |
Token Classification:
Before generating a token image, AI classifies the visualization type:
| Type | Approach |
|---|---|
character-portrait | Person rendered as portrait with face, body, human presence |
location-establishing | Place rendered as establishing shot of the space |
object-study | Item rendered as intimate object study |
abstract-concept | Idea/force rendered as abstract visualization |
Scene Context Synthesis:
To prevent context contamination (e.g., ships appearing in a ledger close-up), the backend synthesizes per-image context:
interface SceneContext {
renderingStyle: string; // Filtered for THIS subject (~50-80 chars)
environmentHint: string; // Relevant environment only (~30-50 chars)
atmosphereHint: string; // Grounding for THIS scene (~30-50 chars)
}
The AI filters full world context (visualLanguage, foundation, worldBreath) to extract only what's relevant for depicting the specific subject. Intimate close-ups exclude transport and geography; establishing shots include full environmental context.
Chapter Snapshots
Each chapter directory contains snapshots of world.md and arcs.md:
chapters/001-the-arrival/
├── chapter.md # Chapter content
├── world.md # Snapshot at write time
└── arcs.md # Snapshot at write time
Why snapshots exist:
- Image generation uses the world state from when the chapter was written
- Ensures visual consistency (characters appear as they were described then)
- Enables accurate retroactive regeneration
Snapshot timing: Created AFTER Phase 6 of scribe workflow (after world.md is updated with new characters/locations from this chapter).
R2 Storage Structure
chronicle-content/ # CONTENT_BUCKET
├── repos.json # Index of all chronicles
├── qino-claude/
│ ├── manifest.json # Generated with chapter metadata
│ ├── world.md
│ ├── world-seed.md # (renamed from theme.md)
│ ├── arcs.md
│ └── chapters/
│ ├── 001-what-the-water-brings/ # qino-claude Chapter 1
│ │ ├── chapter.md # Chapter content
│ │ ├── world.md # Snapshot of world.md for this chapter
│ │ └── arcs.md # Snapshot of arcs.md for this chapter
│ └── ...
└── concepts-repo/
└── chapters/001-the-arrival/ # concepts-repo Chapter 1
├── chapter.md
├── world.md
└── arcs.md
chronicle-images/ # IMAGES_BUCKET
├── qino-claude/
│ └── 001-first-chapter/
│ ├── manifest.json # Image manifest for chapter
│ ├── hero.webp
│ ├── the-wanderer.webp
│ └── ...
└── ...
qino-scribe Integration
The qino-scribe tool (in source repos) generates chronicle content using a three-agent architecture.
Agent Architecture
PREP AGENT (scribe-prep)
│
│ Reads: world-seed.md, world.md, arcs.md, git diff, recent chapters
│ Produces: prep.md (through 3 interactive checkpoints)
│
├── World Layer → scene seeds → user chooses
├── Disturbance Layer → world behavior → user chooses
└── Beat Layer → story lens + beat → user chooses
│
│ [hard cut — prep agent context ends]
│
PROSE AGENT (scribe-prose)
│
│ Reads ONLY: prep.md + world-seed.md
│ Produces: chapter.md (draft)
│
│ [hard cut — prose agent context ends]
│
EDITORIAL AGENT (scribe-editorial)
│
│ Reads ONLY: draft chapter file + voice.md patterns
│ Evaluates: execution quality, not story decisions
│ Returns: APPROVED or NEEDS REVISION (N issues)
│
└── If issues: prose agent revises specific lines
This staged architecture forces fresh invention — the prose agent can't recycle descriptions since it only sees prep.md constraints. The editorial agent evaluates execution independently, without access to prep context.
Reference Files
| File | Purpose |
|---|---|
layers.md | Staged architecture flow and checkpoint formats |
voice.md | Unified craft patterns (line-level and chapter-level) |
story-lenses.md | Twelve story lenses with sensitivities and territories |
disturbance.md | How git diffs become world behavior |
principles.md | Relational principles (mutual revealing, what remains unspoken) |
foundation.md | Setting Foundation guide (13 fields for visual vocabulary) |
calibration-notes.md | Craft devices discovered through prose exploration |
craft.md | Format specs, arcs.md structure |
world-seed.md Configuration
The world-seed.md file now includes:
- YAML frontmatter — Setting Foundation (13 fields: genre, subgenre, scale, geography, era, architecture, lighting, technology, transport, flora, fauna, inhabitants, magic)
- disposition field — World hospitality (hospitable, guarded, indifferent, hostile)
- Prose content — Atmosphere and generative grammar
Arc Workflow (in layers.md)
Arc updates happen automatically after chapter completion:
- New arc: Add with
*Chapters:* N- - Advanced: Update "In motion" section
- Completed: Change to
*Chapters:* N-M, add "Completed" section
Token Sync Workflow (World Update Layer)
For each World Token written in a chapter:
- Is this a character? → Add to world.md
## Charactersif not already there - Is this a location? → Add to world.md
## Locationsif not already there - Is this something else (object, concept, phenomenon)? → No sync needed
This ensures entities mentioned meaningfully in one chapter get hover cards in all future chapters, even without new tokens.
Commands
| Command | Purpose |
|---|---|
/qino-scribe:chapter | Write next chapter (or retroactive with from_ref/to_ref) |
/qino-scribe:survey | Assess git history before writing chapters |
/qino-scribe:rewind | Restore world state to before last chapter |
/qino-scribe:diagnose | Assess system integrity after changes |
Environment Configuration
Backend (wrangler.jsonc)
{
"name": "qino-chronicles-backend-local",
"r2_buckets": [
{ "binding": "CONTENT_BUCKET", "bucket_name": "chronicle-content" },
{ "binding": "IMAGES_BUCKET", "bucket_name": "chronicle-images" }
],
"kv_namespaces": [
{ "binding": "IMAGE_STATUS", "id": "..." }
],
"queues": {
"producers": [{ "binding": "IMAGE_QUEUE", "queue": "chronicle-images" }]
},
"vars": {
"ALLOWED_REPOS": "qino-claude,concepts-repo"
}
}
Frontend (wrangler.jsonc)
{
"name": "qino-chronicles-local",
"r2_buckets": [
{ "binding": "CONTENT_BUCKET", "bucket_name": "chronicle-content-local" }
],
"vars": {
"R2_IMAGES_URL": "https://<public-r2-bucket-url>",
"BACKEND_URL": "http://localhost:4006",
"ALLOWED_REPOS": "qino-claude,concepts-repo"
}
}
Note: Images are served via public R2 URL (R2_IMAGES_URL) rather than a bucket binding, allowing direct browser access without worker proxy overhead.
Source Repo Secrets
GitHub repository secrets needed:
CHRONICLE_PUBLISH_KEY— API key for backend authentication
Debugging
Check R2 content
# From qino-chronicles-backend directory
pnpm exec wrangler r2 object get chronicle-content/qino-claude/arcs.md --remote
pnpm exec wrangler r2 object list chronicle-content --prefix=qino-claude/ --remote
Check image generation status
curl -s "https://qino-chronicles-backend.philhradecs.workers.dev/status/qino-claude/001-what-the-water-brings"
Manual R2 upload
pnpm exec wrangler r2 object put chronicle-content/qino-claude/arcs.md \
--file=/path/to/arcs.md \
--content-type="text/markdown" \
--remote
Force image regeneration
Trigger GitHub action with force: true or POST directly:
curl -X POST "$BACKEND_URL/publish" \
-H "Content-Type: application/json" \
-H "X-API-Key: $API_KEY" \
-d '{"repoName":"qino-claude",...,"force":true}'
Content Scopes & Context
- Canonical (persists/accumulates): Root
world.mdandarcs.mdhold the ever-growing, current state. Scribes append new entities/arcs here after each chapter. Stored in R2 at{repo}/world.mdand{repo}/arcs.md; used by loaders for metadata and as the last-resort context for hover cards. - Chapter snapshot (frozen per-chapter): Each
chapters/{slug}/directory containschapter.md,world.md, andarcs.mdcaptured at publish time. These freeze the state that shaped the chapter so image prompts and retroactive regeneration stay historically correct even if the canonical world changes later. Stored in R2 at{repo}/{slug}/.... - World Tokens (momentary presence): The
## World Tokenssection inchapter.mdexpresses how entities feel in that chapter. When tokens introduce new characters/locations, qino-scribe syncs just the entity entry into canonicalworld.md; the vivid presence text stays scoped to the chapter. Tokens drive both hover-card specificity and token image prompts. - Context resolution for display: On read, presence is resolved in strict order: (1) tokens from the current chapter, (2) historical tokens from earlier chapters, (3) canonical
world.mddescription. This lets the UI show chapter-specific tone while retaining fallbacks. - Image prompt context: The queue consumer builds prompts from
chapter.mdplus its snapshotworld.md/arcs.md(andworld-seed.md). Hashes govern regeneration, but the textual context always comes from the chapter's frozen scope, not today's canonical files. This ensures visuals align with the world as it was when written.
Scope Relationships (what persists vs. what freezes)
Canonical (grows) Chapter snapshot (frozen per chapter)
world.md arcs.md world-seed.md ──┐
│ publish
▼
chapters/{slug}/
├─ chapter.md (momentary)
├─ world.md snapshot (frozen)
└─ arcs.md snapshot (frozen)
World Tokens live in chapter.md only (per-chapter presence).
New character/location tokens ⇒ add entity entry to canonical world.md (persists),
but the token text itself stays in the chapter scope (does not persist).
Display Context Resolution
1) Current chapter tokens ┐ highest precedence for hover cards
2) Historical tokens ┴ carry presence forward
3) Canonical world.md → fallback description
Image Prompt Inputs
chapter.md ─┐
world.md (snapshot) ├─► Prompt builder ─► AI image APIs ─► R2 images/{chapter}
arcs.md (snapshot) ┘
world-seed.md (canonical)
Concrete Examples (concepts-repo)
- Canonical world entry:
world.mdholds durable descriptions, e.g. Selin → "Cartographer, but not of distances... keeps an alcove of objects that don't fit on charts." (used when no chapter token is available). - Chapter token presence: Chapter 2
chapters/002-the-alcove/chapter.mddefines**Selin**→ "Eyes like deep water. Watches what appears in adjacency." This overrides canonical presence when reading Chapter 2. - Historical token reuse: Chapter 1 tokens include
**The Tea-Bearer** ... moves between the groups with a tray...When reading Chapter 2, hover cards for The Tea-Bearer pull this historical presence if Chapter 2 has no fresh token. - Snapshot freeze:
chapters/001-the-arrival/world.mdcaptures the world at Chapter 1; even after canonicalworld.mdadds "The Alcove" in Chapter 2, any regeneration for Chapter 1 still uses its frozen snapshot. - Prompt context: The token image for "The Ring" (introduced in Chapter 2) is built from Chapter 2's
chapter.md+ its snapshotworld.md/arcs.md+ canonicalworld-seed.md, ensuring the image reflects the moment the ring was placed between clusters. - qino-claude harbor scope: Chapter 1 tokens
**The Mender**and**The Meeting Point**define a harbor baseline; Chapter 3 reuses that harbor presence for hover cards unless it provides new tokens for those entities.
File Reference
Source Repo Files
| Path | Purpose |
|---|---|
chronicle/manifest.json | Chapter index and metadata |
chronicle/world-seed.md | Setting Foundation (frontmatter) + prose atmosphere |
chronicle/world.md | Characters, locations, pressures |
chronicle/arcs.md | All arcs with chapter ranges |
chronicle/chapters/*/chapter.md | Chapter content |
chronicle/chapters/*/world.md | Chapter-specific snapshot of world.md |
chronicle/chapters/*/arcs.md | Chapter-specific snapshot of arcs.md |
.github/workflows/chronicle-publish.yml | Publishing workflow |
Backend Files
| Path | Purpose |
|---|---|
src/routes/publish.ts | Content ingestion, R2 storage, job queuing |
src/routes/status.ts | Image generation status |
src/routes/manifests.ts | Image manifest retrieval |
src/queue/image-generator.ts | Queue consumer, differential regeneration |
src/services/token-classifier.ts | AI classification of token types |
src/services/prompt-builder.ts | Builds image generation prompts |
src/services/image-generator.ts | Calls Gemini/FLUX for image generation |
Frontend Files
| Path | Purpose |
|---|---|
src/server/get-chronicles.ts | R2 fetching, Arc/Chronicle types, parseArcs() |
src/lib/chronicles.ts | parseWorld() for characters/locations with descriptions |
src/routes/$repoName/$chapterSlug/index.tsx | Chapter display, arc filtering |
src/components/highlighted-markdown.tsx | Entity highlighting, buildEntityInfoMap() |
src/components/entity-hover-card.tsx | Hover card UI with presence text |
src/components/chapter-end-matter.tsx | Arcs and tokens display |
src/components/chapter-images.tsx | Token images in hover cards and end matter |
src/components/journal-book.tsx | Arc details dialog |
qino-scribe Reference Files
| Path | Purpose |
|---|---|
tools/qino-scribe/references/qino-scribe/craft.md | Format specs |
tools/qino-scribe/references/qino-scribe/process.md | Writing workflow |