You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Part of #184 (Fountain import). Spec findings posted in the umbrella's research comment — read that first; this issue assumes those decisions.
What
Implement a Fountain parser in Rust that consumes a .fountain file string and produces a complete ScreenplayDocument (Film shape): meta, settings, content (ProseMirror JSON), and scene_cards.
Boneyard pre-pass — strip every /* ... */ block (may cross double-line-breaks; this is the only Fountain syntax that does). Drop content per Fountain import (round-trip-safe) #184 scope. Increment a counter for the import summary toast.
Note extraction — find every [[ ... ]] (may span lines, but a fully blank line terminates). Record (text, anchor-position) and replace with a placeholder so line-based tokenisation doesn't see them.
Title page — top of file only. Recognised keys (case-insensitive, whitespace before : allowed): Title, Credit, Author, Authors, Source, Notes, Draft date, Date, Contact, Copyright. Multi-line values: continuation lines indented 3+ spaces or a tab. Title page ends at the first blank line followed by a non-key: line.
Map Title → meta.title, Author/Authors → meta.author, Credit → meta.author if blank else append, Draft date/Date → meta.draft_date, Contact → meta.contact, Copyright → meta.registration_number if empty else meta.extra, Notes → meta.footnote if empty else meta.extra.
Everything else (including unknown writer-coined keys) → meta.extra verbatim with original key spelling.
Body — paragraph-by-paragraph with the precedence below. Forced prefixes win over auto-detection.
Element precedence (per paragraph)
Forced prefix wins: ., !, @, >, ~, =, #. Note: .. is not a forced scene heading (must be a single . not followed by another .).
Page break: line is = × 3 or more and nothing else. (== is action; === 5 === is action.)
Centered: line starts > and ends <. (Beats forced-transition >.)
Scene heading auto: line begins with INT, EXT, EST, INT./EXT, INT/EXT, or I/E (case-insensitive) followed by . or space, with blank line before and after.
Transition auto: all-caps line ending in TO:, with blank line before and after. Also: FADE IN: at top of file (reference-impl convention; Fountain.js disagrees — we follow the reference).
Character auto: all-uppercase Latin line, ≥1 alphabetic char, non-empty next line, blank previous line. Lowercase inside parens is allowed (MOM (V.O.), HANS (on the radio)).
Parenthetical: context-dependent — line (...) immediately after Character or Dialogue.
Dialogue: context-dependent — line(s) after Character or Parenthetical, terminated by a fully blank line. A line with only trailing whitespace (e.g. two spaces) keeps the dialogue block alive (Fountain's two-space convention).
Action: fallback. Tabs and leading whitespace are preserved.
append to scene_cards[matching_scene].description (\\n-joined if multiple)
Section (# ... to ######)
append to scene_cards[next_scene].notes prefixed with [[#section depth=N]] so #188 can re-emit
Note ([[ ... ]])
append to scene_cards[containing_scene].notes
Boneyard
dropped silently, counted
Dual dialogue (^)
sequential Character/Dialogue pairs (caret stripped, no marking — documented loss)
Page break (===)
dropped (Scriptty has continuous-page editor; no page-break node)
Lyrics (~)
action node (rare; promote to its own node later if writers need it)
Emphasis */**/***/_
strip markers, keep text. Honor \\* etc. as literal. Documented v1 data loss.
Malayalam handling (critical — see umbrella research comment)
A Malayalam line cannot satisfy the all-caps character rule (caseless script).
Auto-detect will treat unforced Malayalam character cues as Action — this is unrecoverable on import.
The @ prefix is the only trustworthy signal: @രമേശ് → character node with text രമേശ്. Trust it regardless of caps.
Document this in the import-summary toast: if zero @-prefixed cues are found in a file with substantial Malayalam content, surface a warning ("Fountain file appears to contain Malayalam but no @-prefixed character cues — characters may have imported as action. The sender should re-export with forced character prefixes.").
Implementation sketch
New module src-tauri/src/screenplay/fountain_import.rs. Symmetric to the existing fountain.rs (export).
Public surface: parse_fountain(input: &str) -> Result<(ScreenplayDocument, ImportSummary), String> where ImportSummary carries boneyards_dropped, sections_count, synopses_count, notes_count, dual_dialogue_count, scene_numbers_dropped, emphasis_stripped, plus warnings list.
Hand-rolled parser — Fountain is simple enough that a nom/pest dependency would cost more than it saves. Plain string scanning + line iteration.
Pipeline stages as separate functions: strip_boneyard, extract_notes, parse_title_page, tokenize_body, fold_tokens.
Part of #184 (Fountain import). Spec findings posted in the umbrella's research comment — read that first; this issue assumes those decisions.
What
Implement a Fountain parser in Rust that consumes a
.fountainfile string and produces a completeScreenplayDocument(Film shape):meta,settings,content(ProseMirror JSON), andscene_cards.Parsing pipeline (locked, mirrors reference implementation)
/* ... */block (may cross double-line-breaks; this is the only Fountain syntax that does). Drop content per Fountain import (round-trip-safe) #184 scope. Increment a counter for the import summary toast.[[ ... ]](may span lines, but a fully blank line terminates). Record (text, anchor-position) and replace with a placeholder so line-based tokenisation doesn't see them.:allowed):Title,Credit,Author,Authors,Source,Notes,Draft date,Date,Contact,Copyright. Multi-line values: continuation lines indented 3+ spaces or a tab. Title page ends at the first blank line followed by a non-key:line.Title→meta.title,Author/Authors→meta.author,Credit→meta.authorif blank else append,Draft date/Date→meta.draft_date,Contact→meta.contact,Copyright→meta.registration_numberif empty elsemeta.extra,Notes→meta.footnoteif empty elsemeta.extra.meta.extraverbatim with original key spelling.Element precedence (per paragraph)
.,!,@,>,~,=,#. Note:..is not a forced scene heading (must be a single.not followed by another.).=× 3 or more and nothing else. (==is action;=== 5 ===is action.)>and ends<. (Beats forced-transition>.)INT,EXT,EST,INT./EXT,INT/EXT, orI/E(case-insensitive) followed by.or space, with blank line before and after.TO:, with blank line before and after. Also:FADE IN:at top of file (reference-impl convention; Fountain.js disagrees — we follow the reference).MOM (V.O.),HANS (on the radio)).(...)immediately after Character or Dialogue.Mapping to Scriptty model
scene_headingnode (drop optional#1A#scene-number suffix; Scriptty auto-numbers)actionnode (preserve internal soft line breaks)@)characternode — strip the@prefix from text contentparentheticalnode — parens stored in content (project convention; commit 27a126f)dialoguenodetransitionnode — strip the>prefix> ... <)actionnode (no centered node in Scriptty schema)= ...)scene_cards[matching_scene].description(\\n-joined if multiple)# ...to######)scene_cards[next_scene].notesprefixed with[[#section depth=N]]so #188 can re-emit[[ ... ]])scene_cards[containing_scene].notes^)===)~)actionnode (rare; promote to its own node later if writers need it)*/**/***/_\\*etc. as literal. Documented v1 data loss.Malayalam handling (critical — see umbrella research comment)
@prefix is the only trustworthy signal:@രമേശ്→characternode with textരമേശ്. Trust it regardless of caps.@-prefixed cues are found in a file with substantial Malayalam content, surface a warning ("Fountain file appears to contain Malayalam but no @-prefixed character cues — characters may have imported as action. The sender should re-export with forced character prefixes.").Implementation sketch
src-tauri/src/screenplay/fountain_import.rs. Symmetric to the existingfountain.rs(export).parse_fountain(input: &str) -> Result<(ScreenplayDocument, ImportSummary), String>whereImportSummarycarriesboneyards_dropped,sections_count,synopses_count,notes_count,dual_dialogue_count,scene_numbers_dropped,emphasis_stripped, plus warnings list.nom/pestdependency would cost more than it saves. Plain string scanning + line iteration.strip_boneyard,extract_notes,parse_title_page,tokenize_body,fold_tokens.Test plan
.screenplayfiles, export to Fountain via existingfountain.rs(after Fountain export: round-trip synopses, sections, inline notes, meta.extra #188 lands), re-import, diff. Must reach a fixed point after one full round.src-tauri/tests/fixtures/fountain/:.,!,@,>,~,=,#)@-forced characters@(must warn in summary)\\*,\\_)===,====)FADE IN:at topBANG!followed by blank line (must stay action)TO:(auto-detected as transition — verify behavior matches spec, even if "wrong" by author intent)nyousefi/Fountainandwildwinter/screenplay-toolsoutputs (don't copy code).Out of scope
#1A#) round-trip{{HEADER: ...}})