<h1 align="center">
<a href="https://prompts.chat">
> The agent is the editor. Each project gets a personalized reading experience. Zero config from the user.
Loading actions...
<a href="https://prompts.chat">
TypeScript and ESLint rules that MUST be followed when creating, modifying, or reviewing any file under apps/frontend/, including .ts, .tsx, .js, and .jsx files. Also apply when discussing frontend linting, type safety, or ESLint configuration.
risks
The agent is the editor. Each project gets a personalized reading experience. Zero config from the user.
Every project is different. An investigation into IMF lending cycles feels nothing like a comic about a clockmaker. The viewer must adapt — image sizing, text treatment, transitions, narrator visibility — all tuned to the content. Users are busy. They create content, the system makes it look good.
Step 9 (Assembly) — after investigation.json is built, before writing it to disk. The agent analyzes the content and writes a presentation block into investigation.json. The viewer reads this block and adjusts rendering automatically.
Added to investigation.json at the top level:
{
"presentation": {
"layout": "narrator",
"show_speaker_badge": false,
"image_sizing": "cover",
"text_position": "side-alternating",
"transition": "page-flip",
"typewriter_speed": 20,
"image_max_width": null,
"image_aspect": "auto"
}
}
| Field | Options | Default | What it controls |
|---|---|---|---|
layout | narrator, immersive, cinematic | auto-detect | Overall presentation mode |
show_speaker_badge | true, false | true | Whether to show speaker avatar + name label |
image_sizing | cover, contain, native | contain | How images fill their container |
text_position | side-alternating, overlay-bottom, below | varies by layout | Where text appears relative to image |
transition | page-flip, crossfade, cut | varies by layout | How panels transition |
typewriter_speed | 10-40 (ms per char) | 20 | Reading pace — lower = faster |
image_max_width | null, "60%", "80%", "100%" | null (layout default) | Constrain image width |
image_aspect | "auto", "16:9", "4:3", "1:1" | "auto" | Expected image aspect ratio |
The agent makes these decisions by analyzing content signals. No user input needed.
What is this project?
├─ Investigation (systems, power, history)
│ └─ > 60% NARRATOR speaker → layout: "narrator"
│ Text-position: "side-alternating"
│ Transition: "page-flip"
│ Show-speaker-badge: false (no narrator bubble — just text)
│
├─ Story / Comic (characters, dialogue, action)
│ └─ Multiple speakers, < 60% NARRATOR → layout: "immersive"
│ Text-position: "overlay-bottom"
│ Transition: "crossfade"
│ Show-speaker-badge: true (show who's speaking)
│
└─ Cinematic / Documentary (visual-heavy, minimal text)
└─ Average text length < 80 chars AND image quality is high
→ layout: "cinematic"
Text-position: "overlay-bottom"
Transition: "crossfade"
Show-speaker-badge: false
Image-sizing: "cover" (full bleed)
Analyze the generated images:
| Signal | Decision |
|---|---|
| Images are 512x512 (local model) | image_sizing: "contain", image_max_width: "60%" — don't stretch low-res |
| Images are 1024+ (Gemini/cloud) | image_sizing: "cover" or "native" — let them breathe |
| Mixed aspect ratios across panels | image_aspect: "auto" — respect each image's natural shape |
| Consistent 16:9 throughout | image_aspect: "16:9" — viewer can optimize layout |
| Investigation with data/text panels | image_sizing: "contain" — readable detail matters more than atmosphere |
Analyze the script:
| Signal | Decision |
|---|---|
| Long narration (avg > 150 chars/panel) | text_position: "side-alternating" — give text room |
| Short punchy text (avg < 80 chars) | text_position: "overlay-bottom" — image dominates |
| Mixed speakers with dialogue | show_speaker_badge: true — reader needs to know who |
| Pure narrator, no characters | show_speaker_badge: false — the N badge is noise |
| Poetic / contemplative tone | typewriter_speed: 25 — slower, let words land |
| Dense informational content | typewriter_speed: 15 — faster, don't bottleneck |
| Content type | Transition | Why |
|---|---|---|
| Investigation (measured, analytical) | page-flip | Feels like turning pages in a report |
| Story (flowing, emotional) | crossfade | Smooth, cinematic continuity |
| Quick explainer | cut | Snappy, no delay |
| Documentary with dramatic reveals | crossfade with slower timing | Build anticipation |
Each layout mode has a dedicated skill file with full implementation details, CSS patterns, and design rules. Always read the relevant template skill before building a viewer.
| Template | Skill File | Best For |
|---|---|---|
| Magazine | viewer-magazine.md | System investigations, documentary-style, heavy narration. Text bleeds over image, alternating left/right. Creator's default for investigations. |
| Cinematic | viewer-cinematic.md | Visual-heavy stories, photojournalism, art-first. Image dominates, subtitles only, letterbox bars. |
| Dossier | viewer-dossier.md | Evidence-heavy analysis, data investigations, structured intel. Framed evidence, stamps, structured data. |
| Immersive | viewer-immersive.md | Multi-character stories, educational comics, dialogue-driven. Visual novel style, speaker avatars, glass-morphism. |
| Scroll | viewer-scroll.md | Accessible reading, blog-style, social media sharing. Vertical scroll, parallax, centered column. |
| Comic | viewer-comic.md | True comics, manga-style, sequential art. Panel grid, speech bubbles, gutters. |
| First-Person | viewer-first-person.md | Horror, personal narratives, confessional. Center text, vignette, breathing, no UI chrome. |
Selection rule: For system investigations, default to magazine. Only deviate if content specifically calls for dossier (evidence-heavy with structured data) or cinematic (visual-heavy, minimal text). The creator has explicitly rejected slideshow-style viewers for investigations.
Learned (2026-03-21, insurance-architecture-global-trade): The magazine template was rebuilt mid-session from a slideshow layout. Text must bleed over the image, not sit below it. This is non-negotiable for investigation-type content.
narrator — The Magazine SpreadBest for: investigations, explainers, narrator-heavy content.
Full template: viewer-magazine.md
Reference: All current GatorSquare investigations use this. insurance-architecture-global-trade confirmed this as the creator's preferred format.
immersive — The Visual NovelBest for: stories, comics, multi-character dialogue.
Full template: viewer-immersive.md
Reference: 001-welcome (Bureau comic), clockmakers-commission.
cinematic — The DocumentaryBest for: visual-heavy pieces with minimal narration, atmospheric content.
Full template: viewer-cinematic.md
cover (fills viewport)Reference: Not yet implemented as default. Future mode for photo-essay style content.
During Step 9 (Assembly), the agent runs this analysis:
1. Count speakers:
- narratorRatio = NARRATOR moments / total moments
- speakerCount = unique speakers (excluding NARRATOR)
2. Measure text density:
- avgTextLength = average character count across all moment texts
- maxTextLength = longest single moment text
3. Check image quality signals:
- imageSource = "local" (512px) | "cloud" (1024px) | "gemini" (high-res)
- Check first 3 image file sizes / dimensions if accessible
4. Apply decision tree:
- narratorRatio > 0.6 → narrator layout
- speakerCount >= 2 AND narratorRatio < 0.4 → immersive layout
- avgTextLength < 80 AND imageSource = "gemini" → cinematic layout
- else → narrator layout (safe default)
5. Write presentation block to investigation.json
"presentation": {
"layout": "narrator",
"show_speaker_badge": false,
"image_sizing": "native",
"text_position": "side-alternating",
"transition": "page-flip",
"typewriter_speed": 20
}
"presentation": {
"layout": "immersive",
"show_speaker_badge": true,
"image_sizing": "contain",
"text_position": "overlay-bottom",
"transition": "crossfade",
"typewriter_speed": 22
}
"presentation": {
"layout": "immersive",
"show_speaker_badge": true,
"image_sizing": "contain",
"text_position": "overlay-bottom",
"transition": "crossfade",
"typewriter_speed": 20
}
"presentation": {
"layout": "cinematic",
"show_speaker_badge": false,
"image_sizing": "cover",
"text_position": "overlay-bottom",
"transition": "crossfade",
"typewriter_speed": 25,
"image_max_width": "100%"
}
The viewer automatically handles the image-vs-text balance:
When an image is too wide to leave 35% of viewport for text, the viewer switches to overlay mode:
object-fit: coverWhen images leave enough space beside them:
When a moment has no image (research-only, text pieces):
During assembly, assess each panel's image dimensions and text length. The viewer handles the mechanics, but the agent should:
image_sizing: "cover" for high-res atmospheric imagesimage_sizing: "contain" for images with important edge detailshow_speaker_badge: false to remove narrator clutterWhen the content has multi-character dialogue, the viewer switches to dialogue bubble mode — chat-style bubbles overlaying the image, one at a time, click-to-advance.
1. Count non-NARRATOR characters in characters dict
2. Scan moment text for dialogue pattern: `Name: "line"`
3. Calculate dialogue ratio: moments with 2+ dialogue lines / total moments
IF dialogueRatio > 0.5 AND nonNarratorCharacters >= 2:
→ Set presentation.dialogue_mode = "bubbles"
→ Set presentation.layout = "immersive" (image dominates)
→ Set presentation.text_position = "overlay-bubbles"
IF dialogueRatio > 0.3 AND nonNarratorCharacters >= 2:
→ Set presentation.dialogue_mode = "chat"
→ Dialogue renders as styled chat thread in side panel (colored per character)
→ Narration renders as plain text above the chat
IF dialogueRatio < 0.3 OR nonNarratorCharacters < 2:
→ No dialogue mode (standard text rendering)
dialogue_mode: "bubbles"The viewer parses each moment's text field for the pattern:
{Narration text}
{Speaker}: "{dialogue}" *{action}*
And renders:
characters dict drive bubble border/accent color"presentation": {
"layout": "immersive",
"dialogue_mode": "bubbles",
"show_speaker_badge": true,
"text_position": "overlay-bubbles",
"transition": "crossfade",
"typewriter_speed": 20,
"viewer_style": "picture-book"
}
The viewer renders bubbles differently based on the project's viewer_style:
| viewer_style | Bubble Background | Narration Strip | Font | Feel |
|---|---|---|---|---|
picture-book | White/cream rgba(255,255,255,0.95) | Warm cream rgba(255,250,240,0.92) | Crimson Pro serif | Soft, warm, children's book |
default / immersive | Dark glass rgba(30,30,50,0.85) | Dark blur rgba(0,0,0,0.55) | Inter sans-serif | Cinematic, moody |
editorial | White with border | White with thin border | Inter | Clean, magazine |
Character positioning rules:
When building investigation.json with merged dialogue text:
Speaker: "line" (with quotes)*asterisks*\n\ncharacters dict (case-insensitive lookup)color defined for bubble stylingThe review panel is the creator's window into every production decision. It must surface all data that went into creating each scene — not just the final text.
For illustrated projects:
| Data | Source | Review Column |
|---|---|---|
| Scene prompt (the image generation prompt) | prompts.json → merged at viewer load | Current Panel → "Prompt" |
| Scene direction / variant | prompts.json → variant field | Current Panel → "Scene Direction" |
| Color accent | prompts.json → color_accent field | Current Panel (tag) |
| Script text | investigation.json → moment.text | Previous Panel → "Text" |
| Objects / characters used | investigation.json → moment.metadata.characters/environments | Current Panel → "Objects & Chain" |
| Object reference sheets | objects/ directory | Current Panel → thumbnails |
| Act / narrative position | knowledge.json → act_structure | Current Panel → "Act" |
| Emotion beat | knowledge.json → emotion_arc | Current Panel → "Emotion Beat" |
For text-only projects:
| Data | Source | Review Column |
|---|---|---|
| Scene direction | scene-plan.md → embedded in moment.metadata.scene_direction | Current Panel → "Scene Direction" |
| Act position | moment.metadata.act | Current Panel → "Act" |
| Emotion beat | moment.metadata.emotion | Current Panel → "Emotion Beat" |
| Entities referenced | moment.metadata.entities | Current Panel → "Entities Referenced" |
| Causal links active | moment.metadata.causal_links | Current Panel → "Causal Links" |
Prompts live in prompts.json (Director's output) — separate from investigation.json (Assembler's output). The viewer merges them at load time:
Viewer loads investigation.json
→ Also fetches prompts.json (same directory)
→ Matches prompts to moments by panel number:
prompts.json panel: 1 → investigation.json panel_id: "panel-01"
→ Merges into moment.metadata:
prompt_text ← prompt field
scene_direction ← variant field
color_accent ← color_accent field
→ Review panel reads via getMeta(moment, 'prompt_text')
If prompts.json is missing: Review panel silently hides the Prompt section. No error. This is expected for text-only projects.
If a pipeline stage produces data about a scene, that data MUST be visible in review mode. The creator should never have to open raw JSON files to understand what went into a scene. Review is the single pane of glass.
presentation block — no project ships without editorial decisionsproject_id and moments (with panel_id, text, speaker, image). Using legacy panels array with narration causes a blank viewer. QA Patchup validates this.The viewer reads presentation from investigation.json and applies it. If presentation is missing, it falls back to auto-detection (current behavior: >60% NARRATOR → narrator, else immersive).
This means:
presentation still work (backward compatible)