Commit 06bdbe0
Transcript-driven editing (Descript model) + build/test/DB/rebrand fixes (#3)
* style: hover of copy markdown button
* fix effects button in light mode
* fix inconsistency in blur intensity
* changelog: improve phrasing for timeline clip gap description
* changelog open graph image
* fix changelog opengraph image
* changelog: fix date
* fix content-collections implicit content deprecation
* close effects after last effect removal + spacing
* fix: invert logos in /sponsors when dark mode
* button color
* feat: add changelog link to footer
* fix: outline button styling
* replace opencut assets
* chore: upgrade tailwind-related deps
* feat: branding page
* rename branding page to brand
* checker pattern for brand asset cards (branding page)
* fix: auto scroll on link
* add context menu (w/ brand options) to header logo
allows downloading brand assets directly from the logo in the header
* refactor: remove foreground variant from button, make it default
* dialog: decrease gap
* fix: fallback to opus codec
* fix blog build when marble cms isn't available
* Fix transcription sample-rate handling (#740)
Fix for #703.
Adds an optional sampleRate parameter to the decodeAudioToFloat32() function. The caption transcriber passes in a value of 16000 to prepare audio in the format Whisper expects.
* Update OpenCut logo in README (#741)
* changelog: new version with already-made changes
* fix: assets having unreasonable widths and heights
* changelog: add already-completed item
* untrack .cursor/
* chore: gitignore
* feat: masks, properties refactor, shaders, storage migrations, and more
* fix: resolve typescript/lint errors
* chore: init cloudflare
* chore: update botid
* chore: next-env
* chore: normalize line endings
* fix: add missing file
* feat: init desktop app
* chore: rename proxy to middleware
* chore: update dep
* chore: add missing open-next config
* chore: remove dead songs tab
* refactor: clean up number formatting and step handling in property tabs
* docs: rewrite agents file
* feat: add published status to changelog entries
* refactor: move packages sub-folders into web app
* feat: add shared Rust crates for cross-platform time utilities
Introduce `bridge` proc-macro crate with `#[export]` attribute that conditionally applies `wasm_bindgen` for WASM builds, and `time` crate porting all timecode/frame utilities from TypeScript to Rust.
* chore: remove accidentally committed Zone.Identifier stream
* fix(security): replace new Function() with safe math expression parser (#753)
* fix(security): replace new Function() with safe math expression parser
Replace the dangerous new Function() constructor in evaluateMathExpression()
with a recursive descent parser that safely evaluates basic arithmetic
expressions.
The Function constructor is a security anti-pattern that could enable
arbitrary code execution if the validation regex is ever bypassed or
relaxed. The new parser:
- Only supports numbers, +, -, *, /, parentheses, and whitespace
- Has no eval-like functionality that could execute arbitrary code
- Maintains backward compatibility with existing expressions
- Handles operator precedence and parentheses correctly
Fixes #725
* Apply suggestion from @coderabbitai[bot]
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
---------
Co-authored-by: Maze <167211895+mazeincoding@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
* chore: remove unused middleware file
* fix: remove OpenNext Cloudflare initialization
* chore: untrack files
* chore: update .gitignore
* chore: update deps
* fix: improve DATABASE_URL validation in web environment schema
* fix: eliminate first-paint flicker in landing handlebars
* refactor: name magic numbers and rename el in landing handlebars
* style: remove unnecessary margin from keyframe
* feat: add keyframe graphs button
* refactor: standardize icon component props
* style: swap ripple icon
* feat: migrate time utilities from typescript to rust WASM
* refactor: centralize track placement into its own module
* fix: allow timeline paste and duplicate commands to reassign updated tracks
* fix: build wasm before bun install in CI
* fix: normalize vertical drag direction in preferIndex placement
* feat: centralize selection management
* feat: separate audio from elements
* refactor: move track pruning into a post-command reactor
* feat: migrate GPU renderer from WebGL to wgpu/WASM
* fix: align background blur with effect blur intensity scale
* fix: don't crash when GPU isn't available
* fix: update tooltip text for graph editor button
* refactor: split constants by domain and isolate timeline panel from core
* chore: sort root package.json scripts
* fix: use published opencut-wasm package
* refactor: update command execute methods to return CommandResult | undefined
* chore: update opencut-wasm to 0.1.3
* chore: remove transcription envs
* refactor: swap out manual tabs withthe shared tabs component in stickers
* style: tabs
* refactor: rewrite animation system to use bindings and scalar channels
* fix: subscribe to playback time so keyframe button updates on frame change
Made-with: Cursor
* chore: remove comment
* refactor: unify element updates into pipeline and centralize ripple
* docs: document rust/apps architecture in AGENTS.md
* fix: preserve negative start times during timeline drag
* feat: graphs popover
* chore: addition to last merge
* refactor: use useShiftKey hook in bezier graph
* refactor: split presets into data definitions and storage store
* fix: whitespace
* fix: clean up
* fix: clean up graph editor
* refactor: split effects and masks into dedicated rust crates, introduce MediaTime and FrameRate
* fix: place new overlay tracks at the top of the timeline
* fix: respect scale lock when editing keyframed scale
* feat: select element on insert
* fix: use line-height-based bounds for text elements
* fix: fix timeline width calculation for tick-based time
* style: compact dropdown and context menu styling
* refactor: structured track layout per scene
* fix: clicking a selected element collapses multi-selection
* changelog: add entry
* fix: convert media asset duration to ticks on insert
* fix: one more
* refactor: gpu webgl fallback and batched command encoding
Made-with: Cursor
* feat: wasm compositor
Made-with: Cursor
* feat: position animated independently per axis
Made-with: Cursor
* feat: cinematic bars, diamond, heart, and star masks
Made-with: Cursor
* feat: clipboard with keyframe paste
Made-with: Cursor
* feat: expandable keyframe lanes in timeline
Made-with: Cursor
* refactor: rendering pipeline
Made-with: Cursor
* fix: text measurement, rotation handle, video cache races, playback sync
Made-with: Cursor
* changelog: 0.3.0
Made-with: Cursor
* chore: bump opencut-wasm to 0.2.5
* fix: use published opencut-wasm package
* fix: stable background blur previews in settings
* chore: remove filters tab from assets panel
* feat: graph editor for flat keyframe segments
* fix: linter
* changelog
* changelog: publish 0.3.0
* Add support for basic SRT subtitle import to Captions panel (#756)
* Add SRT subtitle import to the captions panel
[Modified from 0bd641b4102c827ef8d218ada099229f73396077, 2026-03-28 08:46:23 -0400]
Parse .srt files into chunks and insert them as text elements on a new text track using the existing caption styling defaults. Displays a warning when malformed cues are skipped during import.
* Batch caption text insertion
[Modified from eece3bbc5f0578a24a4f90f0ab87fe5ca37e94e9, 2026-03-28 09:13:18 -0400]
Batch caption insertion in a BatchCommand so that undo will batch undo all subtitle insertion from either srt file or ai transcriptions.
* Move subtitle parsing into a shared interface
[Modified from e84404b7857f8c611eda7a65b5d61ee6c9823d75, 2026-03-28 09:52:31 -0400]
Extracted the srt specific extraction behind a generic shared interface to allow easier adding of other subtitle formats in the future.
* Apply better default styling for subtitles.
[Modified from b5deec4285f99873ba5bf57971f7bcab26e8c743, 2026-03-31 11:49:34 -0400]
Shrink font size, aim to layout 5% above bottom, keep subtitle width 80% of video, wrap text on new lines if too long.
* Update Captions Panel UI
Split captions panel into Generate and Import tabs as they are two different approaches for adding captions.
* Add biome formatting fixes
* Add .ass subtitle import support
Extend subtitle import to accept .ass files alongside .srt, map supported .ass styles onto imported text elements, and display warnings when unsupported .ass features are skipped.
* Change “Import subtitles” button style
* refactor: review fixes
* fix: convert subtitle times from seconds to ticks
* refactor: replace captions tab switcher with import action button
---------
Co-authored-by: Maze Winther <mazewinther@gmail.com>
* changelog
* chore: hide preview grid guide until v0.4.0
* chore: hide logos sticker category
* chore: fix comment
* refactor: move constants to domain modules
* fix: timeline element resizing
* chore: remove unused import
* fix: keyframe removal state
* test: remove keyframe query
* style: export button icon size
* feat: draft persistence on Form via persistKey
* docs: remove features section
* docs: update file structure to be accurate
* docs: formatting
* feat: editor feedback
* chore: add .tokeignore
* feat: transcription diagnostics
* feat: add support for technical details in changelog entries
* docs: fill in v0.3.0 technical changelog entries
* changelog: fix date
* fix: copy to markdown formatting
* feat: changelog now supports markdown within entries
* changelog: update to be more accurate
* fix: turbo
* feat: graph editor supports multiple properties at once
* chore: remove debugging throughout codebase
* changelog: another accuracy fix
* fix: remove redundant "opencut" prefix from storage key
* feat: new-release notice and colocate changelog under domain
* fix: show changelog notice when stored version is missing or older
* tests: remove
* chore: force deployment
* fix: skip unknown actions in keyboard shortcuts help
* fix: remap split-element keybinding to split
* chore: force deployment
* changelog: rephrasing
* fix: repair scene tracks broken by v23-to-v24 migration
* feat: move/resize multiple elements
* review: fixes
* tests: delete
* fix: load dotenv before env validation in drizzle config
* fix: correct schema path in drizzle config
* fix: audio waveform height in timeline
* chore: formatting
* feat: history view in feedback popover
* feat: text / custom masks, fix preview pan, refactor selection
* feat: clip volume line and timeline waveform improvements
* fix: align LayerUniformBuffer to 16 bytes for WebGL compat
* chore: update opencut-wasm version to 0.2.6 in Cargo.lock
* fix: default audio waveform color from timeline theme
* delete legacy folder
* fix: migrate commands to CommandResult selection contract
* docs: add prerequisites for rust
* fix: WebGL frame rendering and opencut-wasm 0.2.8
* refactor: restructure files to their domains, new preview overlay system, and dep graph
* docs: ts conventions to agents.md
* chore: delete ffmpeg public assets
* fix: project loading thing
* chore: formatting
* refactor: migration run() and pure v1-to-v2 transform
* fix: wasm WebGL only until WebGPU is fixed
* chore: bump opencut-wasm version
* chore: remove stale files
* perf: improve rendering performance by ~20x
* fix: surface unsupported video codec on import, fix Input leaks
* refactor: centralize editor interaction state
Move timeline and preview gestures into dedicated controllers so hooks stay thin around editor state and DOM events.
This also routes drag and playback synchronization through manager APIs and reuses persistent wasm surfaces so scrubbing, seeking, and preview rendering stay in sync.
Made-with: Cursor
* fix: model editor time as wasm MediaTime ticks end-to-end
* fix: follow-up to prev commit
* fix: linter
* refactor: split animation helpers by domain
* refactor: thinner hooks
* fix: restore README header, badges, links, and star chart
Made-with: Cursor
* chore: remove accidental file
Made-with: Cursor
* chore: untrack cargo-tools files
* chore: switch from biome to eslint + prettier; fix ton of lint issues
* refactor: unify element params
* chore: cleanup
* refactor: animate params over time
* refactor: split mask snap dispatch
Made-with: Cursor
* refactor: make mask renderer dispatch explicit
Made-with: Cursor
* refactor: type mask handle identifiers
Made-with: Cursor
* refactor: split builtin and freeform masks
Made-with: Cursor
* docs: capture mask rust migration notes
Made-with: Cursor
* fix: address review findings in masks substrate
- Remove dead ternary in getPreferredEdges (always returned undefined; the
all-false object was never reached by Object.values().some(Boolean))
- Document why drawWithFeather descriptors zero the feather field in the
compositor (analytical renderer handles softness itself; zeroing prevents
a second JFA feather pass)
- Remove legacyType from V30→V31 migration (not consumed anywhere; a
down-migration can map type:"freeform" back to "custom" directly)
- Add V30→V31 test coverage: skip-guard branches, freeform passthrough,
elements without a masks array
- Add snap tests for vertical-edge (bottom) and uniform-scale handle paths
Co-authored-by: Cursor <cursoragent@cursor.com>
* refactor: remove dead tangent handle variant
The { kind: "tangent" } MaskHandleId variant was never constructed — neither
getFreeformDisplayHandles nor any other code ever produced a tangent handle
object. The in/out control-arm branches in computeFreeformParamUpdate, the
tangent case in maskHandleIdKey, and the CUSTOM_MASK_TANGENT_SIZE rendering
path in mask-handles.tsx were all unreachable. Removed all four dead paths
and removed "tangent" from MaskHandleKind.
Co-authored-by: Cursor <cursoragent@cursor.com>
* refactor: collapse mask definition registry to a single keyed registry
The freeform mask was assembled on every getMaskDefinition("freeform") call,
re-spreading params and injecting a wrong SquareIcon placeholder, with a type
cast to suppress the structural mismatch. Root cause: MasksRegistry was keyed
on BuiltinMaskType, so freeform had no entry.
- Re-key MasksRegistry from BuiltinMaskType to MaskType
- Register freeform alongside builtins in registerDefaultMasks, with
PenToolAddIcon as its menu icon
- getMaskDefinition and getMaskDefinitionsForMenu become plain registry
lookups with no branching, no manual spreading, no cast
- Rename RegisteredBuiltinMaskDefinition to RegisteredMaskDefinition and
tighten computeParamUpdate return type to Partial<BaseMaskParams>
Co-authored-by: Cursor <cursoragent@cursor.com>
* chore: remove file
* chore: ignore migrations file
* docs: move NOTES.md file
* docs: fix readme
* chore: update copyright year
* feat: transcript-driven timeline editing
Adds a Descript-style "edit by editing the transcript" panel that drives
real cuts on opencut's timeline.
- Switch transcription worker to word-level timestamps (return_timestamps:
"word") and group words back into sentence-like segments for display.
Subtitles consumer keeps working since segments still expose start/end.
- New apps/web/src/transcript-editor/ module:
- types.ts: TranscriptDocument with per-word IDs and selection state.
- apply-deletion.ts: maps word ranges to splitElements + deleteElements
calls, classifying each covering element as fully-inside, partial-
left, partial-right, or straddle-both. Latest-first iteration so
earlier ranges keep stable timeline coordinates.
- transcript-panel.tsx: panel UI. Click word = seek; shift/cmd-click
or double-click = toggle selection; delete button cuts the timeline.
- Wire panel into the editor route as a toggleable "Transcript" tab in
the right column (alongside Properties).
Also removes ~90 stale Electron files from apps/desktop that the
opencut merge auto-kept; HEAD now matches opencut's apps/desktop tree.
Known limitation: each transcript edit emits 3-5 separate timeline
commands (one per split / delete) so undo steps through them
individually. BatchCommand wrapping deferred to a follow-up.
* docs: rewrite README without upstream-fork framing
* feat: descript-style transcript panel
Major UX overhaul of the transcript editor:
- Document-style layout: timestamps in a left margin, generous line-height,
reading-width container, prose typography. Each segment is its own
paragraph with a 0:00 timestamp.
- Active-word highlighting: as the video plays, the current word gets an
accent background and auto-scrolls into view. Re-renders driven by
editor.playback subscription via useEditor.
- Range selection: shift-click extends selection from the last clicked
word; cmd/ctrl-click toggles a single word; plain click seeks and
anchors the range.
- Filler-word detection: scans words against a stop-list (um, uh, like,
you know, etc.). Flagged words get a dotted amber underline. Header
shows "Remove N fillers" button for one-shot bulk removal.
- Cmd+F find: opens a search bar, highlights matches in yellow with the
active match in solid yellow, Enter cycles forward, Shift+Enter back,
Esc closes.
- Single undo per edit: applyTranscriptDeletions now constructs commands
directly, executes them in sequence to thread split IDs, then pushes a
BatchCommand to the command manager. One Cmd+Z reverses the whole edit.
Supporting changes:
- src/env/web.ts: defaulted DB/Redis/auth/CMS/FreeSound vars so the
editor boots without backing services. Routes that actually use them
will fail at use; transcript editor doesn't touch any of them.
- src/transcription/models.ts: swap whisper-tiny/small/medium from
onnx-community to Xenova exports. The onnx-community ASR exports
don't carry cross-attentions, breaking return_timestamps:"word".
* chore: rebrand to OpenScript and remove marketing surfaces
Renames @opencut/web -> @openscript/web (root scripts and apps/web/package.json).
Updates SITE_INFO and visible UI strings (header, footer, onboarding,
mobile gate, storage dialog, legal pages, browser-compat banner, better-auth
appName).
Removes marketing routes that don't belong in an editor product:
- /blog, /roadmap, /sponsors, /contributors, /brand, /changelog
- src/changelog/* (release notes data + ChangelogNotification component)
- src/components/landing/* (hero etc.)
- src/components/gitHub-contribute-section.tsx
- src/site/sponsors.ts and src/site/external-tools.ts (now-unused)
Replaces / with a redirect to /projects so users land on their project list.
Strips the OpenCut analytics tracker (databuddy) and the OpenCut nav links
from Header. Footer reduced to copyright + Privacy/Terms.
Kept (not user-visible):
- LICENSE (MIT requires preserving the OpenCut copyright notice)
- localStorage/zustand persist keys ("opencut-keybindings",
"opencut-storage-persist-dismissed") to avoid orphaning user data
- eslint plugin name "opencut/prefer-object-params" in disable comments
- The opencut-wasm npm dep (it's the upstream-published artifact)
- public/logos/opencut/svg/logo.svg as DEFAULT_LOGO_URL with a TODO to
replace with an OpenScript wordmark
* chore: remove placeholder logo and discord references
- editor-header.tsx: replace OpenCut wordmark <Image> in the project
dropdown trigger with a text "OS" mark. Drop the Discord menu item
and its now-orphan separator. Cleans up unused imports (Image,
Link, FaDiscord, DEFAULT_LOGO_URL, SOCIAL_LINKS, DropdownMenuSeparator).
- onboarding.tsx: drop the third onboarding step (the Discord pitch)
and the inert roadmap link from step 2. Replace the dynamic step
title with a static "OpenScript Onboarding" label for the
screen-reader DialogTitle.
DEFAULT_LOGO_URL and SOCIAL_LINKS still exist in src/site for the
privacy/terms pages that link out to GitHub/X (currently inert "#"
hrefs); replace those when real social accounts exist.
* fix: repair production build — missing guards + object-param call sites
next build was failing on the TypeScript gate (12 errors); three of the
root causes were also live runtime bugs. Fix all and add the action plan.
- actions/keybindings: implement isShortcutKey + isActionWithOptionalArgs;
persistence.ts imported guards that never existed. The 9 pre-existing
persistence tests now compile and pass.
- stickers: register providers with object params (was registering every
built-in provider under key `undefined`, breaking the sticker library).
- storage migrations: construct IndexedDBAdapter / call set() with object
params in runner + v1-to-v2 (was indexedDB.open(undefined), silently
breaking legacy-project migration).
- timeline tests: wrap raw numbers with MediaTime brand helpers.
- bun.lock: reconcile with package.json (was stale; broke --frozen-lockfile).
- add PLAN.md.
Verified: tsc --noEmit clean (was 12 errors), next build exits 0,
keybindings suite 9/9.
* test: run editor-core tests under bun by instantiating real opencut-wasm
opencut-wasm is a wasm-pack "bundler"-target package: its entry calls
wasm.__wbindgen_start() on an import that bun's test runner doesn't
instantiate, so every test reaching @/wasm crashed at module load — the
entire editor core (timeline, masks, transcript, storage) was untested.
Add a bun test preload that performs the bundler step itself against the
real wasm (read bytes, instantiate with the glue as the import object,
wire it in, run the start hook), then redirect the bare "opencut-wasm"
specifier to the initialized glue. Tests now exercise the actual Rust
implementation, not a hand-written stub that could drift from source.
Result: 193 tests across 30 files now run (was ~0 in the wasm graph) —
192 pass, 1 skip, 0 fail. Surfaced failures fixed:
- resolve.test: time spans used fractional ticks (2.5/5.5); MediaTime is
integer ticks. Use integer positions that preserve the overlap scenario.
- snap.test (box scale): a centered uniform scale snaps both edges to
±100; expect both vertical guides, not just the right one.
- snap.test (point split): De Casteljau subdivision of the straight a→b
segment yields collinear handles (-0.1 in / +0.1 out), not zero.
- snap.test (text mask): needs a DOM/canvas font-metrics context; skip
under bun, runs in a browser/jsdom env.
Verified: tsc --noEmit clean, bun test 192 pass / 1 skip / 0 fail.
* feat: make transcript edits correct across undo, ripple, and reload
The transcript panel stored a per-word `deleted` flag and mutated it on
delete, so it drifted from the actual timeline: undo restored the media
but left words struck-through, and reload showed the full text over an
already-cut timeline. Deletions also bypassed reactors, orphaning tracks.
- Derive `deleted` from timeline coverage instead of storing it. A word is
deleted when its midpoint is no longer covered by media (new coverage.ts,
7 unit tests). The panel subscribes to the active scene's tracks and
recomputes, so undo/redo and reload stay correct automatically — the cut
media returning makes the word reappear. `deleted` is dropped from
TranscriptWord; filler-words/search take a deletedWordIds set.
- Run reactors after a transcript delete (CommandManager.reactToExternalChange).
Deletions execute sub-commands incrementally to read live element IDs, so
they use push() rather than execute(); push() skips reactors, which left
empty audio/overlay tracks behind. (Ripple-on-delete stays in Phase 2 — it
needs word-coordinate re-sync.)
- Persist the transcript (text + word timings only) per project and rehydrate
on mount, so it survives tab-toggle and reload. Deleted-state is NOT stored;
it is re-derived from the persisted timeline, so the two cannot diverge.
Transcript is removed when its project is deleted.
Verified: tsc clean, bun test 199 pass / 1 skip / 0 fail, next build exits 0.
* feat: transcript model/language pickers + inline word editing
Toward the "edit by transcript" promise (Phase 2, parts a+b):
- Model + language selectors in the transcript empty state, threaded into
transcribe(). Previously locked to whisper-small/auto despite 4 models and
9 languages being defined but unused. Each model shows an approximate
first-use download size (new approxDownloadMb) so users on slow/metered
connections can pick Tiny; a note clarifies it runs locally in-browser.
- Inline word correction: double-click a word to edit its text (Enter/blur
commits, Escape cancels), persisted with the transcript. This is a
transcript-only edit — it changes the displayed/exported text, not the
timeline or word timing — so it is low-risk and independent of the
coverage-derived deleted state.
Ripple-close on delete (Phase 2c) is intentionally not included: closing
timeline gaps conflicts with deriving deleted-state from gaps and needs an
architecture decision (tracked separately).
Verified: tsc clean, bun test 199 pass / 1 skip / 0 fail, next build exits 0.
* feat(wip): Descript model foundation — data model, mapping, ripple-open, transcript store
Phase 2c foundation (steps 1-4 of 10). Inert scaffolding for the destructive
Descript transcript model; not yet wired into the delete flow.
- types.ts: v2 model — TranscriptWord.deleted, DeletedRange + RemovedPiece
(lossless element capture for restore), TranscriptDocument.deletedRanges.
- mapping.ts: pure source<->timeline mapping (frozen source seconds minus removed
durations of earlier cuts; in-gap clamps to gap edge); integer-tick math.
- ripple/open.ts: rippleOpenElements, the inverse of rippleShiftElements (restore).
- core/managers/transcript-store.ts: EditorCore-owned holder for the doc, wired
into useEditor — lets a Command mutate the transcript from global undo/redo.
- storage SerializedTranscript.version loosened to number (v1->v2 migration lands
with the wiring). DESIGN.md records the full plan.
Tests: 14 new (types/mapping/ripple-open/store). tsc clean, full suite 213 pass /
1 skip / 0 fail.
* docs: record Descript-model test-infra findings (EditorCore under bun; save side-effect)
EditorCore constructs under bun, but updateTracks triggers SaveManager -> IndexedDB
(absent in bun) and would reject — so the fidelity test should drive a pure
split/trim+ripple transform on fixture tracks rather than a live editor. Captures
the delete input contract (contiguous runs) for the panel rewire.
* feat(wip): pure transcript delete transform + split helper
Phase 2c steps 5-6 (delete side). Both pure and unit-tested on fixture tracks —
no editor/save coupling, so they are the testable core of the destructive model.
- timeline/split-element.ts: splitElementAtTime — a pure, generic, retime-aware
element split (verbatim mirror of SplitElementsCommand's per-element math,
snap-source-once). Net-new split test coverage. (Kept separate from the command
for now to avoid touching untested split behavior; noted to keep in sync.)
- transcript-editor/delete-transform.ts: deleteRangeFromTracks — removes a
[start,end) range across all tracks (split straddlers, drop contained), captures
the removed slices as RemovedPiece (with offsetTicks for restore), and
ripple-closes the gap uniformly. Returns {afterTracks, removed, durationTicks}.
Tests cover straddle-both, fully-contained, no-overlap, ripple-close, capture, and
trim math. tsc clean; full suite 219 pass / 1 skip / 0 fail.
* feat(wip): pure restore transform + delete<->restore round-trip fidelity gate
Phase 2c step 6c/7. restoreRangeToTracks is the pure inverse of
deleteRangeFromTracks: ripple-open the gap and re-insert the captured slices
(lossless but for a fresh id). The round-trip test is the primary correctness
gate for the destructive model and now runs purely (no editor):
- straddle-both reconstructs exact contiguous source coverage (startTimes,
durations, trimStarts).
- a multi-element timeline stays contiguous and the same total length after
delete->restore (catches tick drift).
- a retimed element round-trips without drift and keeps its retime config.
tsc clean; full suite 222 pass / 1 skip / 0 fail.
* feat(wip): transcript edit plan functions + TranscriptEditCommand
Phase 2c step 6b/6c. Composes the pure transforms into the editable model:
- plan.ts: planTranscriptDelete / planTranscriptRestore — pure (modulo a fresh
range id). Delete processes runs latest-source-first (so each cut's timeline
position, derived from pre-existing cuts, is unaffected by the batch's earlier
cuts), marks words deleted, and records DeletedRanges; restore re-opens the gap
at its current position, reconstructs the media, un-marks the words, drops the
range. Tested: delete records+ripple-closes, restore reverses, unknown id no-op.
- transcript-edit-command.ts: TranscriptEditCommand snapshots tracks + doc and
applies the plan; undo/redo replay snapshots so one undo reverts the timeline
AND the transcript document atomically (the desync fix). dispatchTranscriptEdit
records via push() (the plan already rippled — avoid the global ripple toggle
double-applying) + runs reactors. Known v1 edge noted: restoring a cut that
emptied+pruned a whole track can't rebuild it.
tsc clean; full suite 225 pass / 1 skip / 0 fail.
* feat(wip): v1->v2 transcript migrator (pure)
Phase 2c step 9. migrateTranscript normalizes a persisted transcript to the v2
document shape. v1 (pre-Descript) stored no deleted/deletedRanges and relied on
coverage-derived deletion against an already-cut timeline; migrate it once by
deriving each word's `deleted` from current timeline coverage and starting an
empty deletedRanges log — so pre-v2 cuts show struck but are not restorable
(documented). Never mutates the timeline. v2+ docs pass through. Tested:
v1 derivation, v2 pass-through, no-tracks fallback. tsc clean.
* docs: specify the remaining transcript panel wiring + in-app checklist
The Descript-model backend is complete and tested (transforms, plan, command,
migrator). The panel rewire is the last integration and is the part that needs
in-app verification, so it is specified precisely here (exact deltas + a live
test checklist) for a turnkey hand-off rather than shipped unverified.
* feat: wire transcript panel to the Descript edit model
Phase 2c final step. The panel now drives the destructive, undoable,
restorable model end to end:
- reads the document from the editor-owned TranscriptStore (reactive), so a
command can mutate it from global undo/redo; generate/load/edit write the store.
- deleted state comes from the stored `deleted` flag (not coverage).
- on mount, loads + migrates (v1->v2) the persisted transcript into the store.
- delete builds contiguous runs from the selection and dispatches a
TranscriptEditCommand (split + delete + ripple-close + tombstone, one undo step).
- seek and the active-word highlight go through the source<->timeline mapping,
so they stay correct after cuts.
- clicking a struck-through word restores its whole cut (ripple-open +
reconstruct); pre-v2 cuts stay non-restorable.
- retires coverage from the render hot path (kept only for the v1->v2 migrator)
and removes the superseded apply-deletion.ts.
tsc clean; full suite 228 pass / 1 skip / 0 fail; next build exits 0.
Needs in-app verification (ripple/restore/seek/undo/reload) — checklist in DESIGN.md.
* feat: transcription robustness (Phase 3)
- Default to the Tiny model (~40 MB): fast even on the single-threaded wasm
fallback (no WebGPU / no cross-origin isolation), where larger models are
painfully slow. The picker still offers Small/Medium/Large.
- Gate Generate on timelineHasAudio: a silent/empty timeline now shows
"This timeline has no audio to transcribe" instead of downloading the model
and running Whisper on silence (verified in-app).
- Cancel button during transcription (the service already supported cancel);
cancelling returns to the start rather than surfacing an error.
- Preserve recognized text when a model returns no word-level timing, so the
user still sees what was transcribed instead of a bare error.
Verified: tsc clean, suite 228 pass / 1 skip / 0 fail, next build exits 0;
in-app confirmed default=Tiny and the audio gate.
* fix: repair DB tooling + correct README setup (Phase 4)
- drizzle.config.ts schema path pointed at ./src/lib/db/schema.ts (no such
dir), so db:generate/migrate/push all failed. Fix to ./src/db/schema.ts.
- The committed migration created a dead `waitlist` table and omitted the
`feedback` table the app uses (feedback popover -> db.insert(feedback)),
so feedback 500s on a freshly-migrated DB. Regenerated migrations from the
corrected schema (pre-launch; no deployed DB depends on the old one): the
new 0000 creates accounts/feedback/sessions/users/verifications, no waitlist.
- README "Getting started" claimed the editor needs Postgres + Redis. It does
not — the editor runs fully local (IndexedDB/OPFS + on-device Whisper).
Reworded: those services are only for the optional auth/feedback/sound-search
routes, with the .env.local copy + docker + db:migrate steps spelled out.
* chore: rebrand cleanup + desktop scaffold honesty (Phase 5)
- mobile gate: the "Roadmap" button linked to a removed /roadmap route (404 on
mobile/iPad); point it at the GitHub repo instead.
- sitemap: drop the removed routes (/contributors, /roadmap, /why-not-capcut,
/blog + per-post) that all 404'd; emit only /, /privacy, /terms.
- manifest.json: name/description were still "OpenCut" + the old tagline.
- LICENSE: copyright OpenCut -> OpenScript.
- package.json: remove the dead *:tools scripts (no @openscript/tools package).
- social links: set the real GitHub repo (x/discord left as TODO placeholders).
- desktop: window title OpenCut -> OpenScript, and README now states plainly
that it's an early empty-window scaffold (not wired to the editor) and that
commands run from the repo root.
Verified: tsc clean, next build exits 0.
* chore: remove dead placeholders + OpenCut leftovers (no replacements available)
Rather than leave stubs for things we have no real values for:
- Drop the placeholder contact email and the "#" X/Twitter + Discord social
links from /privacy and /terms; route contact to the real GitHub issues link.
SOCIAL_LINKS reduced to the github repo only.
- Remove the dead /blog RSS feed (apps/web/src/app/rss.xml) and its now-orphaned
blog data layer (apps/web/src/blog) — there is no blog route.
- Remove the unused DEFAULT_LOGO_URL and the OpenCut wordmark assets it pointed
at (public/logos/opencut, public/landing-page-dark.png); the header uses a
text wordmark.
- Remove the empty packages/{env,ui,desktop-bridge} scaffolding dirs.
- .github docs: rebrand OpenCut -> OpenScript (SUPPORT/CONTRIBUTING/SECURITY/
CODE_OF_CONDUCT/feature_request), remove the dead Discord invite, and point
security reports at GitHub security advisories instead of the dead
security@opencut.app. Left the legit `opencut-wasm` package name and the
opencut/opencut local Postgres creds (consistent with docker-compose).
Verified: tsc clean, next build exits 0.
* chore: rebrand remaining OpenCut references in rust/ crates
Fix the dead github.com/opencut/opencut repository URL (rust/wasm/Cargo.toml,
rust/wasm/README.md) to the real OpenScript repo, and OpenScript the rust/
README. Left the published `opencut-wasm` package/crate name and its
opencut_wasm_* file names intact (the JS side imports them by that name).
* fix: align root next/typescript with apps/web to fix CI build
CI `next build` failed type-checking next.config.ts: the root pinned
`next: ^16.1.3` (resolving to 16.2.x, which adds `adapterPath` to
NextConfig) while apps/web pinned `next: 16.1.3`, so two incompatible
`next` copies were visible and `NextConfig` types didn't match
("Property 'adapterPath' is missing"). Pin the root to `next: 16.1.3`
(and `typescript: ^5.8.3`) to match apps/web so the workspace resolves a
single version. (apps/website keeps its own next 16.1.1 — isolated.)
Verified: bun install + `cd apps/web && bun run build` exits 0.
* fix(ci): pin wasm-pack to v0.15.0 so wasm-opt builds on macOS
`version: latest` mis-resolves to wasm-pack v0.9.1, whose bundled
wasm-opt fails ("exited with exit code 1") on the wasm that current
rustc/wasm-bindgen emit. Pinning to the current stable fixes the
Build WASM step across all three runner OSes.
---------
Co-authored-by: Maze Winther <mazewinther@gmail.com>
Co-authored-by: kcfancher <keli@keisi.co>
Co-authored-by: Aspen <bobfeltner123@gmail.com>
Co-authored-by: Rayan Salhab <r.salhab@aiyexpertsolutions.com>
Co-authored-by: Maze <167211895+mazeincoding@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: Cursor <cursoragent@cursor.com>1 parent 4dd762b commit 06bdbe0
1,217 files changed
Lines changed: 131981 additions & 241560 deletions
File tree
- .github
- ISSUE_TEMPLATE
- workflows
- .vscode
- apps
- desktop
- build
- coverage
- electron
- types
- ui
- components
- hooks
- resources/models/whisper-tiny
- scripts
- script
- src
- electron
- test
- types
- ui
- __tests__
- assets
- components
- hooks
- styles
- utils
- web
- migrations
- meta
- public
- effects
- flags
- fonts
- icons
- logos/others
- open-graph
- platform-guides
- shapes
- scripts
- src
- actions
- components
- keybindings
- __tests__
- migrations
- animation
- __tests__
- app
- api
- auth/[...all]
- feedback
- health
- sounds/search
- editor/[project_id]
- privacy
- projects
- terms
- auth
- background
- canvas
- clipboard
- handlers
- commands
- media
- project
- scene
- timeline
- clipboard
- element
- effects
- keyframes
- masks
- track
- components
- editor
- panels
- assets
- views
- settings
- properties
- components
- hooks
- stores
- icons
- providers
- ui
- core
- managers
- __tests__
- data/colors
- db
- diagnostics
- editor
- effects
- components
- definitions
- env
- export
- feedback
- components
- fonts
- fps
- __tests__
- gradients
- graphics
- components
- definitions
- guides
- definitions
- hooks
- masks
- __tests__
- builtin
- definitions
- components
- freeform
- media
- panels
- params
- __tests__
- preview
- components
- controllers
- hooks
- project
- components
- rendering
- retime
- __tests__
- ripple
- __tests__
- selection
- hooks
- services
- renderer
- compositor
- nodes
- storage
- components
- migrations
- __tests__
- fixtures
- transformers
- transcription
- video-cache
- waveform-cache
- site
- sounds
- components
- speed/components
- stickers
- __tests__
- components
- providers
- subtitles
- components
- text
- components
- timeline
- __tests__
- audio-separation
- bookmarks
- components
- hooks
- components
- graph-editor
- controllers
- group-move
- group-resize
- hooks
- element
- placement
- __tests__
- snapping
- transcript-editor
- __tests__
- transcription
- types
- utils
- __tests__
- wasm
- docs
- eslint/rules
- __tests__
- notes
- rust
- crates
- bridge
- src
- compositor
- src
- shaders
- effects
- src
- shaders
- gpu
- src
- shaders
- masks
- src
- shaders
- time
- src
- wasm
- src
- script
Some content is hidden
Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
0 commit comments