Screenplay Editor

Professional Web-Based Screenplay Authoring as a Rails Mountable Engine

Project Overview

Objective: Build a drop-in screenplay authoring experience that any Rails 8 application can mount under a URL prefix, with the editing experience shipped as a first-class React + TypeScript + TipTap bundle and the server-side lifecycle managed by the host app's existing authentication, authorization, and background job stack.

Distribution Model: Packaged as a Ruby gem and published to a self-hosted Gitea Package Registry. Host applications consume it the same way they would consume any gem — add to the Gemfile, bundle install, mount the engine, run the migrations.

Industry Format: Follows the April Screenplay Format convention — Courier 12, fixed margins, ~55 lines per page, proper indentation rules per line type. Plain-text import/export uses the same convention so documents round-trip cleanly between this editor and any April-compatible tool. PDF export produces vector output with correct pagination.

v0.1.1
Current Release
Rails 8
+ React
Host Integration
186
RSpec Examples
4
npm Packages

Engine Architecture

Host Rails 8 Application User auth, authorization, session, Solid Queue / Solid Cable / Solid Cache / PostgreSQL mount ScreenplayEditor::Engine => "/screenplays" ScreenplayEditor Engine (Ruby gem) Controllers scripts, versions, comments, imports REST + JSON Models Script, ScriptVersion, ScriptComment PG JSONB / SQLite TEXT Jobs PDF export, text export/import Solid Queue Policies Pundit-based host overrideable can_access?(role) Framework-Agnostic TypeScript Packages screenplay-model types, schema, validation screenplay-parse text + PDF import screenplay-export text + HTML-for-PDF screenplay-cli command-line tool Editor Frontend React + TypeScript + TipTap (ProseMirror) Scene headings • action • character • dialogue • parentheticals • transitions • shots • notes • dual dialogue

👤 Authorization: Pundit Policies with Host Override

The engine ships a Pundit policy set keyed to industry-recognizable screenplay roles. The host app's User model implements a single method — can_access?(role_name) — and the engine wires the rest automatically. Hosts can override individual policies in app/policies/screenplay_editor/ to layer project-based or team-based logic on top of the defaults.

Guest

View public scripts only

Registered

Create scripts, view own

Premium

Edit + PDF/text export

Industry

Full editing, priority support

Collaborator

Project-based collaboration

Admin

Full access to all scripts

✍️ Authoring Experience

  • Line types: scene headings, action, character, dialogue, parentheticals, transitions, shots, notes, dual dialogue
  • Formatting enforced by the editor: Courier 12, fixed margins, ~55 lines per page, proper indent rules per line type
  • Scene numbering: automatic, renumbers as scenes are inserted / reordered
  • Basic revisions: version tracking, audit trail, activity log
  • Title page generator: produces a correctly-formatted cover sheet

📤 Import / Export

  • Plain-text import: April-style parser — round-trip clean with external tools
  • PDF import: text-based PDF parser with confidence scoring
  • Plain-text export: April-style output
  • Vector PDF export: correct pagination; rendered via a Playwright-based Node.js sidecar
  • Async by design: exports and imports run as Solid Queue background jobs so the editor never blocks

💬 Review Workflow

  • Inline comments: anchored to document ranges (ProseMirror positions persisted as JSONB)
  • Suggestions + resolutions: comments can be resolved / unresolved, preserving history
  • Watermarks: on exported PDFs, configurable per script
  • Draft → Review → Final → Archived status progression
  • Admin oversight: host app's existing admin role sees all scripts and comments

📦 Distribution

  • Gitea Package Registry: published as a standard Ruby gem
  • Automated publish: push to main triggers the publish workflow on a self-hosted Gitea Actions runner
  • Local development: alternative path: source for iterating against a host app without publishing
  • Test coverage: 186 RSpec examples — model specs, policy specs (all roles), job specs, request specs, integration specs

🐍 Rails Engine Side

  • Rails 8 mountable engine: integer PKs, migrations, policies, jobs, controllers, views
  • PostgreSQL: JSONB for ProseMirror document storage and comment range anchors
  • SQLite parity: TEXT-with-serialization for JSONB columns, enabling lightweight test configs
  • ActiveStorage: PDF attachments for exported documents
  • Solid Queue: background jobs — no Redis dependency
  • Solid Cable: real-time comment updates without Redis
  • Solid Cache: parse/export caches without Redis
  • Pundit: role-based policies with host-overrideable defaults

💻 Editor Frontend

  • React + TypeScript: type-safe component model
  • TipTap (ProseMirror): extensible rich-text editor with a clean extension API
  • Custom node types: one per screenplay line category with formatting rules
  • Vite build: engine-bundled asset pipeline, mounted through Rails static serving

💿 Framework-Agnostic Packages

  • @studio/screenplay-model: types, schema, validation
  • @studio/screenplay-parse: text and PDF import
  • @studio/screenplay-export: text and HTML-for-PDF
  • @studio/screenplay-cli: command-line conversions
  • Isolation: core logic usable outside Rails — CLI batch conversions, external tooling, headless tests

🚀 Operations

  • Gitea Actions CI: test + lint + security audit + gem build
  • Automated publishing: push to main → build → publish to Gitea Package Registry
  • Playwright PDF sidecar: chromium-based, vector output, locked fonts
  • Font bundling: Courier Prime under SIL OFL for license-clean distribution

🔧 Engineering Highlights

📦 Engine + Packages Separation

  • Core screenplay logic lives in framework-agnostic TypeScript packages with their own tests
  • Rails engine is a thin integration layer: controllers, models, jobs, policies
  • CLI tool proves the core packages work outside Rails

📄 Industry Format Fidelity

  • April Screenplay Format: Courier 12, fixed margins, proper indents
  • Plain-text round-trips with external April-compatible tools
  • PDF export is vector with correct pagination — not a screenshot

🛠 Host App Friendliness

  • One-line mount: mount ScreenplayEditor::Engine => "/screenplays"
  • Single method contract on User model: can_access?(role_name)
  • Policies overridable per-host without forking the engine
  • No Redis dependency: Solid Queue / Cable / Cache throughout

📊 Test Discipline

  • 186 RSpec examples across models, policies, jobs, requests, integration
  • All six user roles tested against every policy action
  • Import/export jobs exercised end-to-end in specs
  • CI runs on every push to main or develop and on PRs

🎯 Project Impact

Ships a professional screenplay editor as reusable infrastructure. Any Rails 8 + Solid Stack application can mount this engine and get industry-format screenplay authoring, review, and export — without rebuilding the editor, parser, or PDF pipeline from scratch.

Demonstrates end-to-end gem productization. From source, to CI, to versioned releases published automatically to a self-hosted package registry, the project shows the full distribution lifecycle that a production internal tool needs — not just the code.

Integration target: the inaugural consumer is the SpinJockey Network platform, where registered and premium users can draft, collaborate on, and export screenplays as part of the broader film-production workflow.