Newsroom Reference

This document is the complete working reference for the Newsroom (NRCS): every data model field, the timing math, the readiness rules, the AI generation contract, feed source configuration, and the CG and rundown template schemas.

It is the technical companion to Newsroom, which explains the concepts and workflows. Read that first if you are new to the module; come here when you need the exact behavior of a field, a formula, or a schema.


Table of Contents


1. Overview

The Newsroom is composed of six modules sharing one project workspace:

ModuleSurfaceRole
Feed/newsroom/feedInbound wire, RSS, and Google News items
Editor/newsroom/editorStory list and the three-angle story editor
Rundown/newsroom/rundownShow building, timing, live mode (the primary surface)
Assignments/newsroom/assignmentsKanban assignment desk, contacts, resources
Publishing/newsroom/publishingSEO metadata and CMS/social targets
Settings/newsroom/settingsAI prompts, feed sources, rundown templates, CG templates

Every newsroom object — story, rundown, feed item, assignment, contact, publish target, and every settings entry — is a JSON document in the project's key/value store, under a newsroom: key namespace. The workspace is project-scoped: each project has its own independent feed, stories, rundowns, and settings. Object ids are server-assigned on creation, and an object's id always matches the suffix of its storage key. The full key map is in §14.


2. The Story Model

A story is the central object: every module either produces stories (feed, assignments), edits them (editor), sequences them (rundown), or distributes them (publishing).

FieldTypeNotes
idstringServer-assigned
headlinestringDirection (LTR/RTL) auto-detected from content
statusenumEditorial workflow state — see below
blocksScriptBlock[]The TV script (§3)
anglesPlatformAngle[]Web article and social post (§4)
assignedTostring[]Assigned team member ids
durationnumber (seconds)Computed on save — sum of block durations, excluding non-timed blocks (CG, production cue)
blockDurationsmap: block type → secondsComputed on save; per-type totals shown in the editor list and rundown
mediaItemsMediaItem[]Attached media (video / audio / image, with url, thumbnail, duration, title)
metadataobjectpriority, tags[], sources[]
sourceIngestIdsstring[]Linked feed items (§10)
notesstringProducer/editorial notes
onHoldbooleanRundown hold flag (§6)
createdAt / updatedAtISO timestamp

Statuses

StatusMeaning
draftCreated, not yet in active writing
in-progressBeing written
reviewAwaiting editorial review
approvedEditorially approved — rundown-ready
scheduledSlotted for air
on-airCurrently on air
publishedPushed to publishing targets
archivedRetired, kept for the record
killedRemoved — killed stories drop out of all lists

The set approved / scheduled / on-air / published is treated as "approved or beyond" — it drives the rundown's approval flagging (§7) and gates publishing (§13).

Metadata

  • priority — breaking | urgent | normal | feature.
  • tags[] — free-form labels; publishing appends published:{targetId} tags.
  • sources[] — agency names, accumulated automatically as feed items are linked.

The editor also derives live read metrics — word count, estimated read time at the broadcast rate of 2.5 words per second (150 wpm), and block count — from the script as it is written.


3. Script Blocks

The TV script is an ordered list of typed blocks. Blocks are reordered by drag and drop, collapsed individually or all at once, and each carries its own timing.

FieldTypeNotes
idstringUnique within the story
typeenumOne of the seven types below
contentstringScript text (or the instruction, for production cues)
durationnumber (seconds)See timing rules below
notesstringInternal note for the gallery/director
mediaRefstring or media referenceA media reference object { mode: online | offline, value, title?, thumbnail?, duration? }. Offline refs on VO/SOT/package blocks are picked from the station's local media catalog (the mapped station-visible path is stored); live-shot uses plain text (location/feed line)
autoTimingbooleanBlock duration derived from the text
cgTemplateKey / cgTemplateName / cgFieldValuesCG blocks onlySelected template and its filled field values (§11)
guidancePackage blocks onlyFree-text editing instructions for the video editor
referenceVideosPackage blocks onlyList of { id, url, status: validated | unvalidated, label?, service?, filename?, addedAt } — external video links for the editor, checked as they're added (see Reference-video validation)

Per-type behavior

TypeScript textMediaAuto-timingNew-block defaultCounts toward story duration
anchor-readrequired—yes30 syes
vorequiredclipyes (until clip-locked)30 syes
sotoptionalclip—30 syes
packageoptional introclip (final cut)—30 syes
live-shotrequiredlocation / feed referenceyes30 syes
production-cueinstruction——5 sno
cg— (template fields instead)——5 sno

Offline media references

The station-side agent publishes a catalog of the media files on the station's disk to KVS, and the editor reads its own project's catalog. In offline mode, VO, SOT and package blocks pick a file from that searchable catalog instead of typing a path, and the block stores the mapped station-visible path. While no catalog has been published (or it is being refreshed), the picker explains that inline; live-shot blocks keep a free-text location/feed field.

A stored path that is missing from the current catalog — the file was renamed or removed, or the catalog is momentarily stale — is shown as not available on this station and is never cleared automatically: the writer decides whether to reselect.

Timing rules

Three timing modes, in order of precedence:

  1. Media-linked. When a VO, SOT, or package block references an online clip with a known length, the block's duration locks to the clip's real length (rounded to the second). The duration field becomes read-only while the link exists. An offline reference never locks the duration.
  2. Auto-timing. Readable blocks (anchor read, VO, live shot) can time themselves from the text: duration = ceil(words / 2.5) — the standard 150 wpm broadcast read rate.
  3. Manual. Any block's duration can be typed directly when neither of the above applies.

CG and production-cue blocks carry a duration for the gallery but are excluded from the story's total run time and from all rundown timing. The single source of truth for the timing exclusion is NON_TIMED_BLOCK_TYPES / countsTowardTiming in lib/newsroom/storyReadiness.ts.

Reference-video validation

Package blocks carry guidance (free-text editing instructions) plus a list of referenceVideos — external links the writer/producer hands to the video editor, who later attaches the final cut to the same block. When a link is added it is probed against a self-hosted cobalt instance to confirm it's downloadable/supported. Nothing is downloaded at this stage (a separate worker fetches the files later); the probe only reads cobalt's POST / status.

Three outcomes:

  • supported → the link is added with status: validated (origin service and a suggested filename are captured when cobalt returns them).
  • unsupported (cobalt returned status: error) → the link is not added; the writer sees an inline reason.
  • unavailable (no instance configured, unreachable, timed out, rate-limited, or a bad response) → the link is still added with status: unvalidated so the writer is never blocked; a re-check can happen later.

Configuration (server/consumer runtime):

Env varRequiredPurpose
NRCS_COBALT_API_URLfor validationBase URL of the self-hosted cobalt instance. Unset ⇒ every link is added as unvalidated.
NEWSROOM_COBALT_API_KEYoptionalSent as Authorization: Api-Key <key> when the instance requires it.

Hosted cobalt instances (e.g. api.cobalt.tools) block third-party use — you must self-host. Endpoint: POST /dsb/v1/newsroom/validateReferenceVideo → services/newsroom.ts:validateReferenceVideo → services/cobalt.ts:probeMedia.


4. Platform Angles

Each story carries up to three angles of the same editorial content:

AngleContentNotes
tv-scriptThe block list of §3The source of truth for AI regeneration
web-articleRich text (HTML) + its own headlineEdited in a rich-text editor
social-postPlain textHard cap of 280 characters, with a live counter

The web article keeps a separate headline from the broadcast headline — broadcast slugs and SEO headlines serve different masters. Both derivative angles can be written by hand or generated/regenerated from the TV script (§9).


5. Rundowns & Segments

FieldTypeNotes
idstringServer-assigned
namestringe.g. "News Bulletin 20h00"
airDatedate
airTimeHH:MM:SS
plannedDurationnumber (seconds)The show's planned length
segmentsSegment[]Ordered show structure
isLivebooleanLive mode flag (§8)
currentStoryIdstringThe on-air story while live

Each segment:

FieldTypeNotes
idstring
namestringe.g. "Opening", "Sports"
storiesordered story listStored as story ids; rows always render the live story objects
plannedDurationnumber (seconds)Segment budget for over/under
hitTimenumber (seconds, optional)Target start offset from the top of the show
colorenumnone | blue | green | amber | red | purple | teal | pink | orange
isFloatbooleanMarks the float section

Behavior notes:

  • Page numbers are auto-assigned: segment letter (A, B, C, … by position) plus a two-digit row index — A01, B03. The float section always pages as Z.
  • Ordering: timed segments sort first, the float section last. A rundown has at most one float section.
  • Planned duration input accepts MM:SS, H:MM:SS, or a bare number (interpreted as minutes), with presets from 15 minutes to 2 hours.
  • Because segments store story references, a story edited in the split editor updates its row — duration, status, readiness — everywhere at once, including in any other rundown that carries it.

6. The Timing Engine

All rundown timing derives from story durations and segment budgets:

text
front time     = sum of durations of all timed stories before this one
cume           = front time + story duration
back time      = planned show duration − cume
segment O/U    = Σ story durations in segment (holds excluded) − segment planned duration
show O/U       = Σ durations across timed segments − planned show duration
hit-time delta = segment's computed start − segment hitTime   (when a hit time is set)

Exclusions:

  • Float segments are not part of the show clock — their stories carry zeroed timing and contribute nothing to over/under.
  • Held stories (onHold) keep their position in the running order, but the clock does not advance through them: they are excluded from segment and show totals, and their timing cells render as em-dashes.
  • Non-timed blocks (CG, production cue) never contribute to a story's duration in the first place (§3).

Display tones on over/under values: within ±30 seconds renders amber (close enough to ignore), under runs green, over runs red.


7. Readiness & Approval Flags

The rundown grid doubles as a readiness board. Every story row renders its blocks as green/red chips, with a tooltip listing exactly what is missing.

Per-block readiness

Block typeReady when
anchor-readscript text present, duration > 0
voscript text + media attached, duration > 0
sotmedia attached, duration > 0
packagemedia attached, duration > 0
live-shotscript text + location/feed reference, duration > 0
production-cueinstruction text present
cgtemplate selected + field values filled

Story-level readiness

Four aggregate signals derive from the blocks:

  • script — every spoken block has text;
  • media — every VO/SOT/package/live-shot has a media reference;
  • cg — every CG block has a template and filled fields;
  • timing — the story has a total duration and every spoken block is timed.

Row flags

  • Amber left border — story has not yet reached approved (the approved-or-beyond set of §2). Stories are never blocked from a rundown; the flag keeps the approval state visible while producers build ahead of sign-off.
  • Muted row + HOLD badge — story is held.
  • Red ring + LIVE badge — the current on-air story while live.

8. Live Mode & Keyboard Shortcuts

Going live sets the rundown's isLive flag and points currentStoryId at the first timed story. Next/Back walk the timed running order, the elapsed clock ticks against the planned duration, and the current story is highlighted in the grid and named in the "On Air" pill. Ending live mode clears both fields.

Shortcuts are scoped to the rundown grid (they never fire while typing in an input):

KeyAction
SpaceGo live / end live
N / PNext / previous story (live mode)
EscExit live mode
↓ / J, ↑ / KMove row focus
EnterOpen the focused story in the split editor
AAdd a story to the focused segment
FToggle float on the focused story
HToggle hold on the focused story
SJump to the next segment
?Show the shortcuts help dialog

9. AI Story Generation

Generation drafts the full story — TV script blocks, web article, and social post — in one pass, from the material already attached to the story.

Inputs

InputSource
Editorial direction promptSettings → AI Prompts — the newsroom's voice and standards
Anchor-read style promptSettings → AI Prompts
Voice-over style promptSettings → AI Prompts
Story contextHeadline, priority, tags, sources
Source materialThe full bodies of all linked feed items, concatenated as numbered source sections
CG templatesNames and field summaries of every AI-enabled CG template (§11)
Mode + instructionsChosen per generation in the dialog

Modes

ModeTV scriptWeb articleSocial post
full5–8 blocks: anchor lead, 1–2 VO, at least one SOT (placeholder attribution), optional package, CG blocks where useful, production cues500–800 words, headline 60–100 chars≤ 280 chars
medium3–5 blocks300–500 words≤ 280 chars
simple1–2 blocks150–300 words≤ 280 chars

Output contract

Generation returns a single JSON object, validated before anything is written to the story:

json
{
  "blocks": [
    {
      "type": "anchor-read",
      "content": "…",
      "duration": 18,
      "notes": "…",
      "cgTemplateKey": "…",
      "cgTemplateName": "…",
      "cgFieldValues": []
    }
  ],
  "webArticleHeadline": "60–100 character SEO headline",
  "webArticle": "<p>HTML article body…</p>",
  "socialPost": "≤ 280 characters"
}

Duration rules the model must follow (and the validator enforces a minimum of 1 s): spoken blocks at ceil(words / 2.5); defaults of 15 s for a SOT, 90 s for a package, 45 s for a live shot, 3 s for a production cue, 5 s for a CG. Blocks with unknown types are dropped; the social post is truncated to 280 characters.

Behavior

Generation runs as a background task attached to the story — progress is visible in the editor, and reopening the story (from any machine) re-attaches to a generation already in flight. On completion the blocks, both derivative angles, and the recomputed total duration are written to the story.

Angle regeneration

The web article and social post can be regenerated independently at any time, treating the current TV script as the source of truth:

  • Input: the serialized script blocks, the editorial direction prompt, optional user instructions, and — when "include current copy" is chosen — the existing angle text to rework rather than replace.
  • Web article output: { headline, body } — a 60–100 character SEO headline and a 300–800 word semantic-HTML body.
  • Social post output: plain text ≤ 280 characters, hashtags included.

Angle regeneration runs immediately and returns into the editor (no background task).


10. The Feed & Ingestion

Feed items

FieldTypeNotes
idstring
sourceenumwire | nml2 | rss | gnews | social | press-release
sourceIdstringWhich configured feed source produced the item
headline / bodystringBody is Markdown
receivedAtISO timestamp
agencystringe.g. "AFP", "Reuters", or the feed's reference name
priorityenumflash | urgent | normal
categorystringEditorial category
typeenumarticle | photo | video
langstringItem language
tags[] / locations[]string[]Topics and geography
attachmentsFeedAttachment[]See below
revisionsFeedRevision[]Update trail for versioned wire items
versionnumberWire version (Reuters)

Attachments: { type: url | picture | video | other, displayTitle, sourceUrl, externalUrl, dusFileId? }. When media has been copied into the project's media library, the library file is used for display and the original URL is preserved alongside.

Source kinds

KindDeliveryBehavior
AFPReal-time pushItems arrive as the agency publishes; priorities map from agency flags (flash/alert/urgency level); video previews and thumbnails copied into the library
Reuters ConnectPolled, versionedItems keyed by their NewsML id — a version bump updates the existing item in place and appends a revisions[] entry (shown as an "Updated" badge); best video rendition and thumbnail selected and copied
RSSPolledSelf-service in Settings; article link and enclosures become attachments
Google NewsPolledSelf-service in Settings; search-driven

Polling checkpoints

Each polled source tracks which item field marks recency and only imports items strictly newer than its checkpoint:

FieldSet byMeaning
referencenewsroomDisplay/agency label for imported items
typenewsroomrss or gnews — fixed at creation
sourcenewsroomFeed URL
lastItemFieldnewsroomItem field used for recency (e.g. pubDate, guid, publishedAt)
lastItemTypenewsroomHow to compare it — date or number
_lastChecksystemTimestamp of the last poll
_lastItemParsedsystemHighest item value seen — the import checkpoint

Wire sources (AFP, Reuters) are provisioned per project rather than created in Settings; Reuters source records additionally hold credentials, an auto-refreshed access token, and an item-details URL template with a ${{id}} placeholder.

AI pre-import analysis

Every RSS and Google News item passes through an AI classification before it is written to the feed (wire copy skips this — agencies pre-classify their own output). The analysis returns three things:

  • priority — flash strictly reserved for programming-interrupting events, urgent for significant developing news, normal otherwise.
  • category — exactly one of nine: Politics · Economy & Business · Sports · Technology · Entertainment & Culture · Health & Science · Military & Security · Environment & Weather · Society & Human Interest.
  • formattedBody — the body re-formatted into clean Markdown word for word: paragraph breaks, bold first mentions of key names, bullet lists for enumerations, markup stripped — never paraphrased, never given invented headings.

11. CG Template Schema

CG templates define the graphics vocabulary available to CG blocks and to AI generation. They are JSON documents, edited in Settings with live validation. Two structural models are accepted: flat (a single fields array) and nested (fields organized into layout_groups).

Top level

FieldRequiredNotes
cg_template_versionyesSchema version string
template_nameyesShown in the CG block picker and to the AI
ai_enablednoDefaults to available. false hides the template from both AI generation and the CG block picker
fieldsflat modelArray of field objects, at least one
layout_groupsnested modelArray of groups, at least one
cg_template_idno (nested)Optional external template identifier

Each layout group: { group_id, group_title, elements: [field, …] } with at least one element.

Field object

FieldRequiredNotes
idyesStable field identifier
typeyestext | number | select | color_palette | boolean
display_nameyesLabel shown in the block editor
valueyesDefault value (string, number, or boolean)
validationno{ required?, character_limit (≥ 1)?, min_value?, max_value?, regex? }
optionsno[{ label, value }] — for select and color_palette
ui_hintnoFree-form rendering hint

In the block editor, field types render as: text → text input (with character limit), number → numeric input, select → dropdown of options, color_palette → color-dot picker, boolean → switch.

Example (flat model)

json
{
  "cg_template_version": "1.0",
  "template_name": "Lower Third — Name & Title",
  "ai_enabled": true,
  "fields": [
    {
      "id": "name",
      "type": "text",
      "display_name": "Name",
      "value": "",
      "validation": { "required": true, "character_limit": 40 }
    },
    {
      "id": "title",
      "type": "text",
      "display_name": "Title",
      "value": "",
      "validation": { "character_limit": 60 }
    },
    {
      "id": "accent",
      "type": "color_palette",
      "display_name": "Accent",
      "value": "#C8102E",
      "options": [
        { "label": "Red", "value": "#C8102E" },
        { "label": "Blue", "value": "#003087" }
      ]
    }
  ]
}

The nested model wraps the same field objects in groups:

json
{
  "cg_template_version": "1.0",
  "template_name": "Election Board",
  "layout_groups": [
    { "group_id": "header", "group_title": "Header", "elements": [ … ] },
    { "group_id": "results", "group_title": "Results", "elements": [ … ] }
  ]
}

When a CG block is filled in, the chosen template's key and name plus the entered field values (including group information for nested templates) are stored on the block itself — so a script survives later template edits intact.


12. Rundown Template Schema

Rundown templates capture a standing show structure. They are built in a structured form editor in Settings (no JSON required) and instantiated from the rundown page's "New" dialog.

FieldTypeNotes
templateNamestring
airTimeHH:MM:SS (optional)Default 20:00:00
plannedDurationseconds (optional)Default 1800 (30 minutes)
segmentsarrayOrdered show structure

Each segment: { name, plannedDuration (seconds), color?, isFloat?, rows[] }.

Each row: { slug, title?, type?, defaultDuration? } — type is a free-text production label (e.g. LVO, Read, Break, Clip/vt), and defaultDuration is in seconds.

Instantiation. Creating a rundown from a template builds the segments in order and creates one draft story per row: the headline comes from the row's title (falling back to its slug), the duration from defaultDuration, and the production label is kept in the story's notes. The result is a complete show skeleton — every slot is a real story, ready to be written, timed, and approved.


13. Assignments, Contacts & Publish Targets

Assignments

FieldTypeNotes
idstring
storyIdstringThe linked story — every assignment created on the desk starts with a draft story attached
title / descriptionstringThe pitch
stageenumpitch → assign → in-progress → review → approved → scheduled → on-air → published (+ archived, hidden from the board)
assigneesstring[]Team member ids
resourcesarray{ type: personnel | camera | vehicle | edit-suite | transmission-unit, name, available }
dueDateISO timestampDrives derived urgency
createdAtISO timestamp

Status sync. When a linked story's status changes, the assignment's stage follows automatically:

Story status→ Assignment stage
draftassign
in-progressin-progress
reviewreview
approvedapproved
scheduledscheduled
on-airon-air
publishedpublished
archivedarchived

Card priority is derived rather than set: the linked story's priority wins; pitches default to feature; otherwise due-date proximity escalates it (within ~3 hours → breaking, within ~8 hours → urgent).

Contacts

{ id, name, role, organization, phone?, email?, notes? } — a shared per-project contacts book, searchable from the assignment desk.

Publish targets & SEO

Publish targets: { id, type, name, enabled } with type one of wordpress | arc-xp | custom-cms | youtube | facebook | instagram | x-twitter.

Publishing requires the story to be approved or beyond (§2). Publishing to a target marks the story published and appends a published:{targetId} tag. The SEO editor validates: title ≤ 60 characters, description ≤ 160, up to 12 keywords, and well-formed Open Graph image / canonical URLs — with live search-snippet and social-card previews.


14. Storage & Key Map

Every newsroom object is a JSON value in the project's key/value store. Keys are namespaced and ids server-assigned; an object's id always equals its key suffix.

KeyValue
newsroom:story:{id}Story (§2) — rundowns reference these by id
newsroom:rundown:{id}Rundown with segments holding ordered story ids (§5)
newsroom:ingest:{id}Feed item (§10)
newsroom:ingest:nml2:{newsmlId}Reuters feed item — stable key per NewsML id, so versions revise in place
newsroom:assignment:{id}Assignment (§13)
newsroom:contact:{id}Contact
newsroom:target:{id}Publish target
newsroom:settings:aiEditorialDirectionPrompt{ prompt } — editorial direction (§9)
newsroom:settings:aiAnchorPrompt{ prompt } — anchor-read style
newsroom:settings:aiVoiceOverPrompt{ prompt } — voice-over style
newsroom:settings:cgtemplate:{id}CG template (§11)
newsroom:settings:rundownTemplate:{id}Rundown template (§12)
newsroom:settings:sourceRss:{id}RSS feed source (§10)
newsroom:settings:sourceGNews:{id}Google News feed source
newsroom:settings:source:{id}Provisioned wire source (AFP, Reuters)

Deletion semantics differ by object: deleting a story sets its status to killed — it leaves every list but its record survives; deleting a settings entry (feed source, CG template, rundown template) archives the entry.

Per-user interface preferences — the rundown split size, the Blocks column display mode (icons vs. text), feed filters, editor panel state, and collapsed blocks per story — are stored per user and follow the user across devices.