~14 min · on-air, checklist, process-check, fail-safe
Level 0Muted
0 XP0/57 lessons0/14 achievements
0/100 XP to next level100 XP to go0% complete
"Unmissable over subtle." — Dad's rule for the warning, as the pre-flight code records it
A Check That Became a Habit
The broadcast that went unrecorded (the last lesson) did not go unchecked. Since 2026-09-30, the first On Air turn of every broadcast carries one more block, Dad's recording checklist: ScreenFlow recording every display he needs, Logic Pro recording his voice, the camera on, and the camera recorder's record button pressed. She asks it in her own words before anything else, and waits for his answer. "First" is read from the path, not from a flag: a turn opens a broadcast when his turn before it was not on air. So the rule reads the turns he sent, not the switch: a turn sent with On Air off ends the broadcast, and the next on-air turn opens a new one with a new checklist, but turning On Air off and on again without a word in between leaves it the same broadcast, and she doesn't ask again. The card below follows the switch instead, so it does come back.
That morning she asked. Dad answered "다 됐어", all set. ScreenFlow was not running. Nobody did anything wrong: a spoken check that is asked every day becomes a habit, and a habit answers before it looks. And On Air is full screen, so ScreenFlow's icon in the menu bar, the one place that would have said so, was out of sight. Dad's ask was three things: the checklist on screen from the moment On Air comes on, ScreenFlow checked by the machine, and a warning nobody can miss until it records. It is fine for that warning to be on air; he cuts it in editing.
The Card Is the Checklist She Asks From
The card's items are the numbered lines of the very prompt she asks from, onair_checklist_instruction, which Dad edits in Admin. There is no second list to keep in step: change the prompt and the card changes with it. The card stands first on the stage from the moment On Air comes on until his second turn (the first opens the broadcast and hears her ask, the second answers it), and it comes back whenever ScreenFlow is not recording. It is an ordinary stage card, so the lightbox opens it too. Beside the ScreenFlow item it shows what the machine sees: recording, open but not recording, not open, or cannot be checked from here.
The Machine Check
ScreenFlow records exactly while a process called screenflowrecorder exists. That was measured, not assumed: on office it appears when Record starts (at about 43% CPU) and goes when recording stops, while the app itself and its helper stay. One detail nearly made the check lie. A process's short name is cut to 16 characters, so the recorder shows up as screenflowrecord; the check reads full executable paths from ps -axo comm= and takes the last part of each.
The check runs only while the checklist names ScreenFlow, so a show recorded some other way gets no alarm. And the processes it reads are this Mac's. A screen asking from the same Mac (a loopback address, or the server's own) gets an answer; a screen on another Mac is told it cannot be checked from here, never that ScreenFlow is or is not recording. The screen asks GET /api/onair/preflight every 2 seconds while On Air is on, and never after.
A Warning You Cannot Miss, and Cannot Hear
The warning plays through the On Air effects layer, the same renderer her effects use, but it is the screen's own effect, carried whole, never one from her library: a red flash over the whole screen, ⚠ ScreenFlow is NOT recording across it, sized to fit the width on one line, and warning signs scattered about. It plays again every 3 seconds for as long as ScreenFlow is known not to be recording, so the screen never settles into looking fine. When the recorder appears, the last pass fades and no new one comes.
Two refusals are as deliberate as the alarm. Not knowing never rings it: a server restarting or a screen on another Mac is not evidence that anything is wrong, and an alarm that cries on doubt is an alarm people learn to ignore. And it makes no sound: a beep would land in the open microphone, and the loop decides the end of Dad's turn by loudness. A replay rings the same warning, without the card, because a replay is captured too. The On Air screen without the show (the next lesson) records nothing for the channel, so it gets none of this.
It was verified in the browser pane on the dev build, with the server's answer stood in: the card and the warning came up at the press, the warning stopped within one pass of recording starting and came back when it stopped, and turning On Air off stopped the asking.
Code
A minimal sketch of the pre-flight rules: the card, the process check and the alarm·python
import re
from pathlib import PurePosixPath
# The rules of backend/services/onair_preflight.py and frontend/src/lib/onairPreflight.ts.
SCREENFLOW_RECORDER = "screenflowrecorder" # exists exactly while ScreenFlow records
SCREENFLOW_APP = "screenflow"
CARD_TURNS = 2 # his first turn hears the checklist, the second answers it
ITEM = re.compile(r"^\s*\d+[.)]\s+(?P<text>\S.*?)\s*$")
NAMES_SCREENFLOW = re.compile(r"screen\s*flow", re.IGNORECASE)
CHECKLIST = """[On Air: the opening checklist]
Before anything else, run Dad's recording checklist with him:
1. ScreenFlow is running and recording every display he needs.
2. Logic Pro is recording his voice.
3. The camera is on.
Ask, then wait for his answer."""
def checklist_items(instruction: str) -> list[str]:
"""The card is the very text she asks from: its numbered lines."""
return [m.group("text") for line in instruction.splitlines() if (m := ITEM.match(line))]
def executable(command: str) -> str:
"""The full path's last part, a bundle-id prefix left off. The short process
name is cut to 16 characters ("screenflowrecord"), so read the path."""
name = PurePosixPath(command.strip()).name.lower()
return name.rsplit(".", 1)[-1] if "." in name else name
def screenflow(items: list[str], commands: list[str] | None) -> dict | None:
if not any(NAMES_SCREENFLOW.search(item) for item in items):
return None # a show recorded some other way: no check, no alarm
if commands is None: # a screen on another Mac: never a guess
return {"checked": False, "reason": "cannot be checked from here"}
names = {executable(c) for c in commands if c.strip()}
return {"checked": True, "recording": SCREENFLOW_RECORDER in names, "app_running": SCREENFLOW_APP in names}
def alarm(state: dict | None) -> bool:
"""Only a KNOWN 'not recording' rings it. Not knowing never does."""
return bool(state) and state["checked"] and state["recording"] is False
def card_shows(items: list[str], state: dict | None, turns: int) -> bool:
return bool(items) and (alarm(state) or turns < CARD_TURNS)
items = checklist_items(CHECKLIST)
# Illustrative process paths, the shape `ps -axo comm=` prints.
app = "/Applications/ScreenFlow.app/Contents/MacOS/ScreenFlow"
recorder = "/Applications/ScreenFlow.app/Contents/MacOS/screenflowrecorder"
helper = "/Library/PrivilegedHelperTools/net.telestream.screenflowhelper"
for label, commands, turns in (
("not open, his first turn", ["/usr/sbin/cfprefsd", helper], 0),
("open, not recording, third turn", [app, helper], 2),
("recording, first turn", [app, recorder, helper], 0),
("recording, third turn", [app, recorder, helper], 2),
("screen on another Mac", None, 0),
):
state = screenflow(items, commands)
print(f"{label:32s} card={card_shows(items, state, turns)!s:5s} warning={alarm(state)!s:5s} {state}")
print("no ScreenFlow on the list:", screenflow(items[1:], [app, helper]))
How often the screen asks, how often it warns, how long the card stays: frontend/src/lib/onairPreflight.ts:16-25·typescript
export const PREFLIGHT = {
/** How often the screen asks: the recorder appears within a second of Record. */
pollMs: 2000,
/** While ScreenFlow is not recording the warning plays again this often,
* so the screen never settles into looking fine. */
warningEveryMs: 3000,
/** His turns the card stays for: the first opens the broadcast and hears
* her checklist, the second answers it. */
cardTurns: 2,
} as const;
The alarm and the card's rule: frontend/src/lib/onairPreflight.ts:65-83·typescript
/** The alarm rings only when ScreenFlow is on his checklist and known not to
* be recording. Not knowing (a screen on another Mac, a server restarting)
* never rings it. */
export function screenflowAlarm(state: PreflightState | null): boolean {
return !!state && state.screenflowItem !== null && !!state.screenflow?.checked && state.screenflow.recording === false;
}
/** His turns since On Air came on: the messages there then are the baseline. */
export function turnsSince(messages: ReadonlyArray<{ id: string; role: string }>, baseline: ReadonlySet<string>): number {
return messages.filter(message => message.role === 'user' && !baseline.has(message.id)).length;
}
/** The card stands from the moment On Air comes on until his second turn
* (the first hears her checklist, the second answers it), and again for as
* long as ScreenFlow is not recording. A checklist with no numbered items
* has no card. */
export function showPreflightCard(state: PreflightState | null, turns: number): boolean {
return !!state && state.items.length > 0 && (screenflowAlarm(state) || turns < PREFLIGHT.cardTurns);
}
Run the sketch and read the six lines: which ones show the card, which ring the warning, and why the other-Mac line does neither of the alarming things. Then build a pre-flight for something you record or publish yourself: pick the one item that ruins the result if it's wrong, find a fact the system can answer about it (a process, an open file, a port, a device), and write the check with three outcomes, yes, no and unknown. Run ps -axo comm= on your own Mac while the app is idle and while it records, and diff the two lists to find your marker.
Hint
The other-Mac line shows the card (it's still his first turn) but never the warning, because 'cannot be checked from here' is unknown, not no. For your own marker, diff the process list before and after pressing Record; a process that appears only while recording is the cleanest signal there is. If none appears, look for a file growing in the output folder instead, and keep the same three outcomes.
Progress
Progress is local-only — sign in to sync across devices.