Accept codegen requests queued while no machine was connected: the poll
loop applies them like any other edit and logs "queued by <name>"
attribution. Update the editor docs to describe pending edits and code
sync.
A slug used at two distinct call sites silently merged both actions into
one runtime identity (the second became a phantom loop-repeat, its edits
unreachable) and was only caught reactively as an ambiguous-edit-id.
Static analysis now finds every duplicate up front, keeps the first
occurrence, and re-stamps the rest with fresh, never-reused slugs. Wired
into record (before the record run), the screenci edit startup handshake,
and the interactive codegen apply (retry-after-resolve, still-ambiguous
still throws). Genuine loops (one call site) stay untouched.
- codemod: collectEditIdOccurrences, renameEditIdAtCall
- editIdStamp: planDuplicateEditIdFixes (skips in-use slugs)
- record path reuses one --list discovery pass for both title and
duplicate-editId checks
blockRemoveEdit now unwraps autoZoom blocks (not only hide/speed/time), and
codeSync orders the unwrap before the two re-wraps in one pass, so splitting a
code-authored autoZoom yields two sibling brackets carrying the original zoom
options. Adds codeSync.spec coverage and documents the flow in editor.md.
Overlays no longer vary by language. video.overlays(...) accepts only a
names-only array or a shared content-major object; the language-major
spelling is rejected at the type level and throws a clear ScreenciError at
declare time. Narration (and the hidden values/audio) keep the per-language
form.
Removes the runtime per-language overlay resolution (buildOverlays is now
shared-only), the overlay language contribution/warning paths, the
AssetTranslation type and asset translations fields, and the assetStart branch
of the translation fold. Docs updated.
Make recorded waitForTimeout calls web-editable and keep the source in sync
with timeline drags:
- Unlock recorded numeric waitForTimeout delays (instrument) so their
durationMs can be rewritten in code instead of being locked.
- Add a durationMs codegen path (codeSync): locate the wait via a stamped
neighbor action (findRecordedWait) and rewrite the numeric arg in place, or
remove the call at 0. Refuse safely when the source has drifted.
- Coalesce back-to-back waitForTimeout statements into one summed call
(mergeAdjacentWaitEdits), run as a post-pass on every file a sync changed.
Adjacency is judged per source block, so it never crosses control flow;
comment-separated sleeps are preserved.
- Carry schemaKind on editable snapshot entries so codegen can tell a recorded
delay apart from other unstamped entries.
Docs and unit tests included.
'screenci edit [patterns...]' filters managed videos by title, like
'playwright test <pattern>' (multiple positionals OR-combined). --grep
takes precedence when both are given.
BREAKING: the name-first overloads of hide/speed/time are removed. A block's
stable identity now lives in the trailing options object, like an action's:
hide(fn, { editId: 'setup' }). The dev-startup handshake that stamps action
editIds now stamps blocks too (the options object lands right after the
callback argument), so every block gains a stable identity when an edit
session starts. blockRemoveEdit resolves blocks by the editId option (legacy
string-name args still match), making every stamped block web-removable.
A gap span could only anchor forward (a sleep after an interaction's end or
a delay into it), so a cut over the footage leading into the first
interaction was unrepresentable. fromLeadMs pulls the span start to before
its anchor (codegen emits it as a leading waitForTimeout inside the wrap);
untilLeadMs is its end-boundary twin for editor-only bare splits. Bare-split
detection treats matching leads like matching sleeps.
Editor edits no longer fetch backend-stored overrides at record time.
Instead every edit is codegenned into the user's .screenci.ts sources
over the dev channel (or fails when no machine is connected).
- Remove the CLI override fetchers and their env plumbing
(SCREENCI_VALUES_OVERRIDES / _RECORD_OPTIONS / _WEB_LANGUAGES), the
parsers/merge in runtimeMode, resolveEffectiveRecordOptions, and the
SCREENCI_DEBUG_OVERRIDES debug dump.
- Reconcile the languages model: drop the 'studio' sentinel, studioSeed,
studioOwned, pending, and web-language unioning. A video with no
.languages() is the implicit 'en' default; an explicit .languages([...])
set is unioned with per-feature keys and filtered by --languages.
- Add codegen edit kinds and codemods: languagesEdit (setVideoLanguages),
valuesEdit (setValuesValue), and editorMediaEdit (setEditorMedia).
- Add the { editor: '<name>' } media declaration for backend-hosted,
editor-uploaded overlays / narration audio / audio tracks: the bytes
stay in the backend, but the item is explicit in code (no local path).
- Rename internal resolveStudio* helpers to resolve*Base; keep wire/DB
keys that deployed CLIs and stored recordings depend on.
- Update docs (languages, editor, overlays, narration, video-script-basics).
Editor codegen edits and dev startup editId stamping now format each
changed file with the user project's own prettier install before
writing. Formatting is gated on prettier resolving and a Prettier
config file being found for the edited file, so it is opt-in by
presence and never fails the edit: errors pass the content through
unchanged with a warning.
screenci init enables it by scaffolding a minimal user-editable
.prettierrc (2-space indent, single quotes, no semicolons, matching
the generated example style) and installing prettier in the island,
plus a format script and README note. Docs updated (editor.md,
manual-setup.mdx).
Remove the stale screenci status and sync sections from the CLI reference
(both commands are gone on this branch, codegen writes edits into sources
directly) and reword the timeline editing intros that still claimed edits
apply without code changes.
Rewrite editor.md around the sync-gated codegen model (connected machine,
live preview, timeline, Export-first), fix the record vs export flow in
overview, agent-integration, manual-setup, cli, ci-setup, version-history,
public-urls-and-embeds, update-screenci, and video-script-basics, fix the
broken captureAudio examples (boolean | { gain }), document accent and the
web narration/overlay editing, add enableCaptureAudio to configuration, and
rename the CLI's Studio strings to Editor.
- planCodeSync unappliable items now carry a typed UnappliableReason
plus the human message; applyCodegenRequest errors name the reason so
the editor toast is actionable.
- Block/point/effect calls resolve through the file's import table, so
aliased imports (autoZoom as az) are found, wrapped, unwrapped, and
reused on insert; unresolvable functions refuse with
unresolved-import.
- plannedRemoval unwraps stale editor-owned zoom/hide/speed/time wraps
(anonymous blocks over the anchors) instead of leaving them in code.
- timelineEdits gains paramEditIdFor/renameEditIdFor and parseEditId,
exported via the new screenci/timeline-edits subpath so the web app
compiles against the same wire types and id encoding.
- Tests: refusal reasons, alias resolution, stale wrap unwrapping,
TS-absent guard, plain-JS parsing, locator alias chains, editsOverlap.
- Docs: refusal reasons and alias support in cli.mdx and editor.md.
Adapts the scissors additions to the codegen architecture: blockRemoveEdit
is routed as a codify record through splitTimelineEditsByVideo and applied
by the dev codegen channel; bare-split hides stay editor-only (skipped by
the planner); the sync-era parse/apply/report subsystem removed on the
editor branch stays removed.
- optionsEdit and narrationEdit records (timeline edits v4): render/record
option snapshots merge into .renderOptions/.recordOptions builder calls
(section added when missing), narration cue values merge into the
video.narration declaration via the new setNarrationValue codemod
(content-major and language-major forms, conversion to language-major on
a non-default language edit, names-only declarations stay app-managed)
- applyCodegenRequest wires the new records into planCodeSync
- screenci dev watches the managed videos' test sources and
screenci.config.ts (src/devWatch.ts): a real content change re-records
the affected videos as previews through a new machine-local record job
in the listen loop; codegen self-writes are baseline-suppressed;
--no-watch disables it
- docs: cli.mdx dev watching, editor.md option/narration codegen coverage
- Record a hiddenAction marker event for every instrumented action inside
hide(), so the web editor can show what a hide suppresses.
- Skip zero-width hide gapSpanEdits at sync: bare split markers stay
editor-only state and are never codified as empty hide() blocks.
- New blockRemoveEdit record + unwrapBlockCall codemod: unwrap a NAMED
hide/speed/time block from source, keeping the wrapped calls and their
waitForTimeout pacing.
Editor edits now arrive over the dev channel as codegen requests and are
written straight into the .screenci.ts sources; recordings always run purely
from code values.
- Keep data.json across uploads (media-only cleanup, last-data.json rename)
and stamp a sourceHash of the test file into its metadata.
- screenci dev startup handshake: skip recording when the source hash matches
and every editable action has an editId; otherwise stamp missing editIds
and re-record as a preview. New --grep, --force-record and
--record-kill-window flags.
- Dev loop: killable background record slot, serial codegen applies acked
over /cli/dev/report-codegen, fast-poll during active editing, kill-or-queue
policy for superseding triggers, sync-state timeline locking.
- Removed: editable-actions.json and action-params.json snapshots,
SCREENCI_TIMELINE_EDITS injection, runtime override application,
screenci sync / status / reset-web-edits and dev --sync.
Comment out (not delete) the screenshot(), narration positioning
(moveNarration/resizeNarration), setBackground, background audio, and
selected() dependency exports; remove values()/audio() from the public
builder type while keeping them attached at runtime. Move the values,
audio, screenshots, and dependencies guides out of the docs manifest
(sources now live in docs/removed/ at the mono repo root), trim the
overlay-updates guide to the still-exported APIs, and scrub mentions
from the remaining docs and the agent skill.
Editor edits now arrive over the dev channel as codegen requests and are
written straight into the .screenci.ts sources; recordings always run purely
from code values.
- Keep data.json across uploads (media-only cleanup, last-data.json rename)
and stamp a sourceHash of the test file into its metadata.
- screenci dev startup handshake: skip recording when the source hash matches
and every editable action has an editId; otherwise stamp missing editIds
and re-record as a preview. New --grep, --force-record and
--record-kill-window flags.
- Dev loop: records run in a killable background slot while the loop keeps
polling; codegen requests apply serially via the codemod pipeline and are
acked over /cli/dev/report-codegen; fast-poll during active editing;
kill-or-queue policy for superseding record triggers; sync-state reporting
locks editor timelines during the startup handshake.
- Remove the server-override machinery: editable-actions.json and
action-params.json snapshots, SCREENCI_TIMELINE_EDITS injection, runtime
override application, screenci sync / status / reset-web-edits and
dev --sync.
Adds an optional delay (ms) to instant overlay updates (setBackground,
moveNarration, resizeNarration, resizeRecording, hideRecording,
showRecording, hideNarration, showNarration), media start() calls
(narration cues, overlays, audio), and the hide/speed/time wrappers
(start stamp only). The recorder stamps the event at call time + delay,
so a call written before an interaction can take effect during it.
Same-type events must stay in time order: recording fails with a clear
error when a delayed event would land behind one recorded after it, or
when a delay pushes an event past the end of the recording.
screenci sync learns delayMs on gapPointEdit/mediaEdit/gapSpanEdit:
the call is codified BEFORE the anchor interaction with { delay },
idempotently, including sleep<->delay placement switches and removal.
- cursorCurve: controlPointsToTuple (inverse mapping) and parseCursorCurve
- instrument: stamp effective moveCurve/moveCurviness into editable defaults
at every pointer wrapper, and apply saved curve overrides at record time
- codeSync: codify cursor-move param edits (move.duration/speed/easing/
curve/curviness/delayAfter, duration, easing, dragSteps) onto the stamped
call, with duration-vs-speed exclusivity and an overlap guard
- easing: browser-safe EASING_NAMES re-export for the web editor
- docs: visual curve editing and cursor-move codesync
Breaking: an overlay config object must now pick exactly one placement
variant, enforced by a discriminated union (OverlayFillPlacement |
OverlayOverPlacement | OverlayBoxPlacement). The implicit "no placement
fields = fill the recording" default is removed for config objects; the
bare path-string and React-element shorthands still mean fill: 'recording'.
selected(...) render dependencies now require a placement too.
A placement resolved from an over locator additionally records
overLocked, marginPx, and the raw elementRect on the assetStart event,
so editors know the box is pinned to a live element and only its margin
is adjustable.
Animated (code/react) overlays are encoded as a dual-stream mp4 (color +
alpha matte) that only the renderer can composite; a browser <video> plays
the color stream alone, so the web editor's live preview showed the overlay
on an opaque black card. Rasterization now also encodes a VP9 .webm with a
real alpha channel (best-effort: skipped when ffmpeg lacks libvpx-vp9),
stamps it on the assetStart event as previewPath/previewFileHash, and the
CLI uploads it alongside the render clip so the editor can play it with
transparency.
Recording is now fast by default and independent of narration length; the
render's frame holds remain the source of truth for cue timing. Opt into
record-time cue-audio pacing with recordOptions.actualNarrationPace, replacing
the removed --fast-narration flag and SCREENCI_FAST_NARRATION env.
Cursor moves can now follow a cubic-bezier path instead of a straight line.
New move.curve ('none' | 'natural' | 'arc' | normalized [x1,y1,x2,y2] tuple)
and signed move.curviness options on every locator action and page.mouse
move/click, plus project-wide recordOptions.cursorCurve / cursorCurviness.
Curves resolve to absolute-px control points baked onto the recorded event
(FocusChangeEvent.mouse.control); the real dispatched cursor follows the
bezier so hover/pointer events fire along the arc. Easing still drives the
speed along the path.
screenci sync now also writes web-editor render/record option edits into
video.renderOptions({...}) / video.recordOptions({...}) builder calls,
closing the code round-trip. New codemod setBuilderOptions locates the
video by its name literal and inserts or deep-merges the options into the
builder chain (video('N',fn), video.narration([...])('N',fn), etc.),
idempotent and conservative (null rather than mangle an unknown chain).
Re-adds the injectable studio-sync fetcher wired through runSync and
dev --sync.
applyZoomWindowOffsets (the only consumer) was deleted with the linear
timeline redesign, and the renderer never read these fields, so
startOffset/endOffset on autoZoom did nothing. Drop them from
AutoZoomOptions, DEFAULT_ZOOM_OPTIONS, the autoZoomStart event, the
event serialization, and the sync codemod path. Zoom lead-in/hold is now
expressed by where the autoZoom block sits plus interior waitForTimeout
sleeps.
Re-syncing an unchanged edit set is now a no-op: media/point placements
find their prior codemod-authored effect call + adjacent sleep and update
in place instead of stacking; changed sleep values rewrite the existing
waitForTimeout; zoom/span wraps are detected and not re-wrapped. Removing
an effect in the editor (a disabled codify record) deletes the codemod-
authored call and re-coalesces its split gap sleep, but only when the
sleep ms matches the record so hand-authored sleeps are never touched.
Zoom/gap-span unwrap-on-removal is left conservative (documented).
Drop timestamp()/waitSince()/place* and the sync-prompt agent path from
the guides; describe effects as call-position block wrappers (autoZoom/
hide/speed/time) plus waitForTimeout gaps, keyed to editId. Move the last
config-level use.recordOptions/renderOptions examples to the per-video
video.recordOptions()/renderOptions() builder form.
Replaces the anchor+offset placed-event wire model with editId-keyed
call-position records (MediaEdit/ZoomEdit/GapSpanEdit/GapPointEdit;
ParamEdit/RenameEdit kept). Deletes all record-time materialization of
anchored events (applyPlacedEvents/applyZoomWindowOffsets/resolveTimeline
Anchor/insertSorted) and the entire agent-prompt path (buildSyncPrompt,
buildEditablePlacementPrompt, buildStudioSyncPrompt, the sync-prompt
command, printSyncPrompt, codeSync fallback). Rewrites the sync codemod
to place effects into the linear code timeline: media/points inserted
between interactions with split waitForTimeout gaps, zoom/hide/speed/time
wrapped as blocks over interaction runs, with a loop/branch lock guard.
Ghost-sleep reconcile left as a follow-up (todo).
TIMELINE_EDITS_VERSION -> 3.
Record and render options are now settable only per video via
video.recordOptions(...)/video.renderOptions(...) (the web editor is the
source of truth). Drops the config use.* fields and the remapConfigOptions
plumbing; the internal default-options fixtures stay as the base layer.
Docs (configuration, ci-setup, screen-audio) migrated to the per-video
form.
Option edits resolve their call sites via the editId recorded on
action-param records; sleepBefore, autoZoom offsets, and placed events
anchor by slug too. The selector/occurrence lexical matcher, line
anchors, and loop detection are gone: unstamped actions are never
guessed at and route to the agent prompt until a record plus sync
stamps them.
New renameEdit timeline record { target: { editId }, newEditId }.
screenci sync and dev --sync apply it by replacing the slug string
literal (renames run last so earlier edits key on the old slug);
invalid or unlocatable renames fall through to the agent prompt.
Slugs validate as [A-Za-z0-9_-]+.
Actions and autoZoom blocks accept an optional editId slug (click1,
zoom2). screenci sync and dev --sync stamp missing slugs automatically
using the call sites captured at record time, allocating from the
committed .screenci/edit-ids.json (numbers never reused, ids never
removed). With an editId the stable key IS the slug, so editor edits
and placed-event anchors survive refactors, moved lines, and locator
changes; sync locates call sites by exact slug instead of heuristics.
Loop executions key as slug#N and stay web-runtime-only.
Also: record --no-render uploads the recording and editable data
without dispatching a render, for a fast first editor sync.
pressSequentially now tracks a typing duration like fill and exposes it as an
editable field, but because it types key by key its default total scales with
the text length (about 60ms per character) instead of a fixed spread. An
explicit duration wins; a native per-key delay is read as the per-character
cadence. The effective total drives the per-key delay handed to Playwright.
Adds a DEFAULT_PRESS_SEQUENTIALLY_MS_PER_CHAR constant, the duration option and
docs, and a test asserting the char-count scaling.
A background loop polls the editor's edits, applies what static
analysis can (screenci sync semantics), and clears the web edits of
videos whose pending edits were all applied. Pauses during triggered
records. sleepBefore edits now update an existing waitForTimeout
directly before the action instead of stacking a new one, keeping
repeated syncs idempotent; inserted placed events are remembered by
signature for the session.
New command applies action-option changes, sleepBefore waits, autoZoom
offsets, and placeHide/Speed/Time/Zoom calls directly to .screenci.ts
files using the TypeScript parser with raw text splices (formatting and
comments preserved). Anything ambiguous (loops, spreads, stale edits,
narration content) falls back to the existing agent sync prompt.
Dry-run diff by default; --write saves, --write --reset clears web
edits for fully applied videos.
Add a /cli/studio-sync fetch and a pure studioSync builder so screenci
sync-prompt emits SET directives for the render and record options the web
editor holds, plus NOTE lines for narration/text/audio/overlay edits, letting
an agent codify render options into the scripts too.