refactor(teleprompter): drop the two options that cannot justify themselves yet

Options are permanent surface. They live in URLs and in saved presets, so one
can be added later without breaking anything while removing one breaks every
preset that named it. That asymmetry says to keep only what earns its place
before the view ships, not after.

Autoplay goes. Its own code said as much: it was the single option needing a
lint suppression because it is a starting condition rather than a live setting,
which is not how any other option here behaves. It also has no coherent use.
Starting a scroll on load runs the script at a fixed rate whether or not
anybody is speaking, and following the loaded event, which is on by default,
immediately pulls the position somewhere else. Anyone wanting a prompter to
roll unattended needs the transport control we have not built.

Alignment goes. Nothing in the research pointed at it as a control operators
reach for, and centring multi line paragraphs leaves a ragged left edge which
is harder to track back to on each new line. Left is the safer default and the
option can come back cheaply if somebody asks for it.

Seventeen options remain, and each maps to something the research called for or
to the font and colour trio the other views already share.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Cb8RVPNQ2ETPJxdy4b8CHf
This commit is contained in:
Claude
2026-08-16 09:04:34 +00:00
parent 509305ee61
commit b42b10829a
6 changed files with 1 additions and 45 deletions
@@ -64,7 +64,6 @@
font-size: var(--tp-font-size);
line-height: var(--tp-line-height);
text-align: var(--tp-align);
/**
* The first line has to be able to reach the reading line, and the last line
@@ -87,7 +87,6 @@ function Teleprompter({ rundown, rundownMetadata, customFields }: TeleprompterDa
atEnd,
} = useTeleprompterScroll({
initialSpeed: options.speed,
autoplay: options.autoplay,
followLoaded: options.followLoaded,
selectedEventId,
readingLinePos: options.readingLinePos,
@@ -121,7 +120,6 @@ function Teleprompter({ rundown, rundownMetadata, customFields }: TeleprompterDa
// the overlays and by 1dvh for the content padding. Percentage padding would
// resolve against width and put the first line nowhere near the reading line
'--tp-reading-line': options.readingLinePos,
'--tp-align': options.align,
'--tp-background': options.keyColour,
'--tp-color': options.textColour,
...(options.font ? { '--font-family-override': options.font } : {}),
@@ -11,12 +11,10 @@ describe('getOptionsFromParams()', () => {
hideEmpty: true,
showGroups: true,
speed: DEFAULT_SPEED,
autoplay: false,
followLoaded: true,
fontSize: 64,
lineHeight: 1.5,
textWidth: 90,
align: 'left',
dimPast: true,
readingLine: 'line',
readingLinePos: 40,
@@ -44,10 +42,9 @@ describe('getOptionsFromParams()', () => {
});
test('booleans which default to false can be turned on', () => {
const options = getOptionsFromParams(new URLSearchParams('autoplay=true&flipH=true&flipV=true'));
const options = getOptionsFromParams(new URLSearchParams('flipH=true&flipV=true'));
expect(options).toMatchObject({
autoplay: true,
flipH: true,
flipV: true,
});
@@ -68,7 +65,6 @@ describe('getOptionsFromParams()', () => {
test('rejects an unknown value for an enumerated option', () => {
expect(getOptionsFromParams(new URLSearchParams('heading=banana')).heading).toBe('title');
expect(getOptionsFromParams(new URLSearchParams('readingLine=banana')).readingLine).toBe('line');
expect(getOptionsFromParams(new URLSearchParams('align=banana')).align).toBe('left');
});
test('adds the hash back to colour params', () => {
@@ -23,14 +23,8 @@ const readingLineOptions = [
{ value: 'none', label: 'None' },
];
const alignOptions = [
{ value: 'left', label: 'Left' },
{ value: 'center', label: 'Centre' },
];
const headingSources: readonly HeadingSource[] = ['none', 'title', 'cue', 'both'];
const readingLineVariants: readonly ReadingLineVariant[] = ['none', 'line', 'arrows'];
const alignments = ['left', 'center'] as const;
/**
* Defaults and bounds for every option, in one place.
@@ -45,12 +39,10 @@ const defaults = {
hideEmpty: true,
showGroups: true,
speed: DEFAULT_SPEED,
autoplay: false,
followLoaded: true,
fontSize: 64,
lineHeight: 1.5,
textWidth: 90,
align: 'left',
readingLine: 'line',
readingLinePos: 40,
dimPast: true,
@@ -110,13 +102,6 @@ export const getTeleprompterOptions = (customFields: CustomFields): ViewOption[]
type: 'number',
defaultValue: defaults.speed,
},
{
id: 'autoplay',
title: 'Start scrolling on load',
description: 'Whether the script starts scrolling as soon as the view opens',
type: 'boolean',
defaultValue: defaults.autoplay,
},
{
id: 'followLoaded',
title: 'Follow loaded event',
@@ -171,14 +156,6 @@ export const getTeleprompterOptions = (customFields: CustomFields): ViewOption[]
type: 'number',
defaultValue: defaults.textWidth,
},
{
id: 'align',
title: 'Text alignment',
description: 'Alignment of the script text',
type: 'option',
values: alignOptions,
defaultValue: defaults.align,
},
{
id: 'readingLine',
title: 'Reading line',
@@ -288,13 +265,11 @@ export function getOptionsFromParams(
showGroups: toBoolean(getValue('showGroups'), defaults.showGroups),
speed: clampSpeed(toNumber(getValue('speed'), bounds.speed, defaults.speed)),
autoplay: toBoolean(getValue('autoplay'), defaults.autoplay),
followLoaded: toBoolean(getValue('followLoaded'), defaults.followLoaded),
fontSize: toNumber(getValue('fontSize'), bounds.fontSize, defaults.fontSize),
lineHeight: toNumber(getValue('lineHeight'), bounds.lineHeight, defaults.lineHeight),
textWidth: toNumber(getValue('textWidth'), bounds.textWidth, defaults.textWidth),
align: toEnum(getValue('align'), alignments, defaults.align),
dimPast: toBoolean(getValue('dimPast'), defaults.dimPast),
readingLine: toEnum(getValue('readingLine'), readingLineVariants, defaults.readingLine),
readingLinePos: toNumber(getValue('readingLinePos'), bounds.readingLinePos, defaults.readingLinePos),
@@ -32,12 +32,10 @@ export type TeleprompterOptions = {
showGroups: boolean;
/** lines per minute */
speed: number;
autoplay: boolean;
followLoaded: boolean;
fontSize: number;
lineHeight: number;
textWidth: number;
align: 'left' | 'center';
font?: string;
keyColour: string;
textColour: string;
@@ -27,7 +27,6 @@ const EXTERNAL_SCROLL_EPSILON = 2;
interface UseTeleprompterScrollArgs {
initialSpeed: number;
autoplay: boolean;
followLoaded: boolean;
selectedEventId: string | null;
/** percentage from the top of the screen */
@@ -52,7 +51,6 @@ interface UseTeleprompterScrollArgs {
*/
export function useTeleprompterScroll({
initialSpeed,
autoplay,
followLoaded,
selectedEventId,
readingLinePos,
@@ -234,14 +232,6 @@ export function useTeleprompterScroll({
return () => document.removeEventListener('visibilitychange', onVisibilityChange);
}, []);
// autoplay once, on mount
useEffect(() => {
if (!autoplay) return;
runningRef.current = true;
setIsRunning(true);
// eslint-disable-next-line react-hooks/exhaustive-deps -- autoplay is a starting condition, not a live one
}, []);
/**
* Whether the loaded event currently has a block to scroll to.
*