Vibecode ScreenFlow
track this build4 phases, 12 steps, beginner friendly0%ScreenFlow's solo core is compact: build a local Mac screen recording + editing workflow that imports user-owned media, applies the core edit or transformation, previews it, and exports a standard file. A competent builder can reach a useful personal version in one sitting, while the paid product mainly wins on media pipeline, hosting, distribution.
You are building a lean indie version of ScreenFlow. Create the following project files first, then implement the application by following them. Keep the files updated as decisions change. Do not collapse this into a single README or prompt. ===== README.md ===== # ScreenFlow · indie build A local screen recorder whose hard problems are capturing through the supported framework with permission handled properly, writing the take to disk continuously so a crash never destroys it, and keeping variable rate screen frames in step with a microphone over a long recording. Estimated effort: **weekend**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Capture | ScreenCaptureKit through a small native helper, with the system sharing picker | the supported path, and the one that does not put your app on a warnings list | | Processing | FFmpeg as a subprocess for assembly, trimming and export | it already understands the containers and the timestamps you are about to meet | | Runtime | Python 3.12 with FastAPI serving a local interface | only the capture has to be native, and everything else is easier if it is not | | Storage | SQLite for recordings and edits, media inside a project folder | the media is the data and it belongs somewhere a person can move | | Editing | An edit list applied at export, sources never modified | a trim you cannot undo destroys the take a second time | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **A Mac you can grant screen recording permission on** · free if you have one - Why: The permission flow, the picker and the capture framework are the first phase, and none of it can be tested anywhere else. - Get it: Any supported Mac, with the ability to change privacy settings. - Verify: A trivial capture helper records five seconds of the screen - [ ] **The Swift toolchain and command line tools** · free - Why: The capture helper is native, and the toolchain download is large enough to be worth starting before you need it. - Get it: Install Xcode or the command line tools from Apple. - Verify: swiftc --version prints a version - [ ] **FFmpeg with ffprobe** · free - Why: Assembly, trimming and export all shell out to it, and ffprobe is how you verify timestamps and drift. - Get it: Your package manager, or a static build from the official downloads page. - Verify: ffmpeg -version and ffprobe -version both print a version - [ ] **A recording of at least thirty minutes** · free - Why: Drift, disk growth and crash recovery all behave perfectly on a two minute clip and reveal themselves only on a long one. - Get it: Record yourself working for half an hour with a clap at the start and the end. - Verify: The clap appears at the same moment in both tracks at both ends - [ ] **Your disk budget and segment retention** · free - Why: This tool writes gigabytes without being asked, and deciding the limits after filling a disk means deciding them under pressure. - Get it: Write down the minimum free space to start recording and how long segments survive a successful export. - Verify: Both are in the environment and enforced in code ## Quick start Follow Phase 1 in `BUILD_PLAN.md`; it creates the project. Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - Multitrack compositing, transitions and titles. - Stock media libraries. - Hosted upload and sharing. - Camera compositing and keying. - production codecs, rendering speed, and media templates - hosted recording reliability - large-scale rendering - distribution and podcast hosting - professional codecs, effects, and collaboration If one of those is essential to you, that is the reason to keep paying for ScreenFlow, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · ScreenFlow The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a Mac screen recorder and trimmer that cannot lose a take to replace ScreenFlow. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Stack (fixed, do not substitute) - Capture: ScreenCaptureKit through a small native helper, with the system sharing picker · the supported path, and the one that does not put your app on a warnings list - Processing: FFmpeg as a subprocess for assembly, trimming and export · it already understands the containers and the timestamps you are about to meet - Runtime: Python 3.12 with FastAPI serving a local interface · only the capture has to be native, and everything else is easier if it is not - Storage: SQLite for recordings and edits, media inside a project folder · the media is the data and it belongs somewhere a person can move - Editing: An edit list applied at export, sources never modified · a trim you cannot undo destroys the take a second time ### Data model (create this before Phase 1) - recordings(id, started_at, ended_at, source_kind, state, segments_dir, bytes, recovered_from_crash) - segments(recording_id, ordinal, path, start_pts_ms, end_pts_ms, bytes, sealed_at) · written as they arrive and sealed as they close - tracks(recording_id, kind, device_name, sample_rate, channels, first_pts_ms) · screen, microphone and system audio each carry their own clock - projects(id, name, folder, created_at) · media referenced by relative path, so the folder can be moved or copied - edits(project_id, ordinal, recording_id, in_ms, out_ms, track_kind) · an edit list, never a rewritten source - exports(id, project_id, preset, path, settings_json, bytes, started_at, finished_at, exit_code) · the settings that produced the file, kept with it - No operation in this model modifies a source recording, which is the only reason a mistaken trim at midnight is survivable. ### Phase 1 · Capture through the supported path Build: Capture with ScreenCaptureKit from a small native helper. Request screen recording permission before capturing and declare the usage description in the bundle. Use the system content sharing picker to choose the display or window rather than enumerating sources yourself. Trap: Building your own window list and capturing through the older CoreGraphics calls still works today, prompts the person with warnings about software that can collect detailed information about them, and is the path Apple is closing; it also breaks quietly after an update nobody chose. Use ScreenCaptureKit with the system picker, and handle a denied permission as a first class state. Done when: Launching without permission shows the system prompt and an explanatory state, never a black recording. The usage description string is present in the bundle and visible in the system privacy settings entry. Source selection goes through the system picker and the application never enumerates windows itself, verified by review. Denying permission leaves the rest of the application usable for editing existing files. Do not build yet: No crash safety, audio alignment, trimming or export. ### Phase 2 · A take that survives the process dying Build: Write the capture to disk continuously as sealed segments rather than holding it and writing one file at stop. Check free space before starting and stop cleanly with a message when it runs low. On launch, find orphaned segments and offer to assemble them into a recording. Trap: Buffering the take and muxing one file when the person presses stop means a crash, a forced restart or a full disk at minute fifty destroys the entire recording, which is the single thing this kind of tool must never do. Seal a segment every few seconds and make a crashed session recoverable from what is already on disk. Done when: Killing the process mid recording leaves playable segments and the next launch offers to assemble them, tested by killing it. Free space is checked before recording starts and the estimate for the chosen settings is shown. Filling the disk stops the recording with a message and keeps everything captured so far. No code path holds more than a bounded amount of capture in memory, verified by review and by watching memory over a long take. Do not build yet: No audio alignment, trimming or export yet. ### Phase 3 · Picture and sound that stay together Build: Carry the presentation timestamps the capture pipeline gives you rather than assuming a frame rate. Record the microphone as its own track with its own clock and align on timestamps at assembly. Measure drift across a long take and record the measurement. Trap: Assuming a constant frame rate is safe because screen capture reports one is how a forty minute tutorial ends up with the mouse click arriving a second before the sound: the capture delivers frames when the screen changes, so the frame count and the wall clock disagree more the longer you record. Keep the timestamps from capture and assemble against them. Done when: A thirty minute recording with a sharp sound at the start and the end shows drift under a stated threshold at both, measured rather than assumed. Variable frame rate output is either preserved or converted with the choice recorded on the export row. Microphone and system audio are separate tracks that can be muted independently. A take with no audio device available still records video and says which track is missing. Do not build yet: No trimming, presets or project moves yet. ### Phase 4 · Trimming without re-encoding everything Build: Keep an edit list of in and out points per recording. At export, copy the stream where the cut falls on a keyframe and re-encode only the boundary pieces. Preview from the edit list rather than from a rendered file. The source is never modified. Trap: Re-encoding a twenty minute recording to remove two seconds at the front costs a long wait and a generation of quality every time somebody changes their mind, which they will, four times. Store an edit list, copy the stream where keyframes allow, and re-encode only the pieces at the boundaries. Done when: Trimming a twenty minute take completes in seconds rather than minutes, timed in the suite. The source file's checksum is unchanged after any number of edits, asserted. The exported file plays in at least two players other than the one you developed against. An edit can be undone because the edit list is data and the source is intact. Do not build yet: No presets, cleanup or project portability yet. ### Phase 5 · Disk, cleanup and a project you can carry Build: Keep media inside a project folder referenced by relative path, so moving or copying it still opens. Delete segments after a successful export according to a stated retention, and report what was removed. Record the exact settings behind each export preset with the exported file. Trap: Segments accumulating in a temporary directory forever is how a screen recorder quietly consumes two hundred gigabytes, and the person discovers it when the disk is full during the recording they actually needed. Apply a retention after a successful export, report the bytes removed, and show current usage in the interface. Done when: A project folder copied to another path opens with every recording and edit intact, tested. Segments are removed after a successful export according to the retention and the byte count is reported. Current disk usage by project is shown in the interface. Each export row stores the settings used, so the same preset produces the same encoder arguments a month later. Do not build yet: Nothing after this. ### Out of scope (and why) - Multitrack compositing with transitions, titles and animation, which is a full editor and several months rather than a weekend. - Stock media libraries, which are a licensing arrangement rather than a feature and are a standing annual cost in the paid product. - Hosted upload and sharing, which turns a local recorder into storage, bandwidth and an access control model you now operate. - Camera compositing and green screen keying, which need real time processing decisions that conflict with never dropping a frame. ### README must contain - That capture goes through ScreenCaptureKit and the system sharing picker, and which permission and usage description are required - That the take is written as sealed segments continuously, and how a crashed session is recovered on the next launch - That timestamps come from the capture pipeline and are never recomputed from a frame index - That sources are never modified and every edit lives in an edit list applied at export - The retention applied to segments after a successful export, and where disk usage is shown ===== AGENTS.md ===== # Agent instructions · ScreenFlow indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: ScreenCaptureKit through a small native helper, with the system sharing picker, FFmpeg as a subprocess for assembly, trimming and export, Python 3.12 with FastAPI serving a local interface, SQLite for recordings and edits, media inside a project folder, An edit list applied at export, sources never modified. Do not substitute. - Work one phase at a time, in order. Do not start a phase until every "Done when" item of the previous one passes. - Prefer the fewest moving parts that satisfy the step. No frameworks, services or dependencies the plan does not name. - Secrets live in `.env`, never in source or logs. Keep `.env.example` current when a variable is introduced. - Do not invent cryptography, security guarantees, APIs or compliance claims. - Add a focused test for every destructive, security-sensitive or data-loss path the plan names. - Run the project checks before declaring a phase complete, and record any deliberate shortcut in the README under "Tradeoffs". ## Known traps - Building your own window list and capturing through the older CoreGraphics calls still works today, prompts the person with warnings about software that can collect detailed information about them, and is the path Apple is closing; it also breaks quietly after an update nobody chose. Use ScreenCaptureKit with the system picker, and handle a denied permission as a first class state. - Buffering the take and muxing one file when the person presses stop means a crash, a forced restart or a full disk at minute fifty destroys the entire recording, which is the single thing this kind of tool must never do. Seal a segment every few seconds and make a crashed session recoverable from what is already on disk. - Assuming a constant frame rate is safe because screen capture reports one is how a forty minute tutorial ends up with the mouse click arriving a second before the sound: the capture delivers frames when the screen changes, so the frame count and the wall clock disagree more the longer you record. Keep the timestamps from capture and assemble against them. - Re-encoding a twenty minute recording to remove two seconds at the front costs a long wait and a generation of quality every time somebody changes their mind, which they will, four times. Store an edit list, copy the stream where keyframes allow, and re-encode only the pieces at the boundaries. ===== BUILD_PLAN.md ===== # Build plan · ScreenFlow A local screen recorder whose hard problems are capturing through the supported framework with permission handled properly, writing the take to disk continuously so a crash never destroys it, and keeping variable rate screen frames in step with a microphone over a long recording. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · Capture through the supported path Recording starts only after permission is granted, through the system's own picker. ### Steps 1. Put capture in a native helper One small process with one job, talking to the rest over a local socket. Files: `helper/Capture.swift`, `src/capture/client.py` 2. Declare the usage description and ask first A missing description string is a silent denial at the worst moment. Files: `helper/Info.plist` 3. Use the system sharing picker Less code, fewer prompts, and the sources the person actually expects. Files: `helper/Picker.swift` ### Done when - [ ] No permission produces a prompt and an explained state - [ ] The usage description appears in system settings - [ ] Sources come from the system picker only - [ ] Editing still works when permission is denied ### Watch out - Building your own window list and capturing through the older CoreGraphics calls still works today, prompts the person with warnings about software that can collect detailed information about them, and is the path Apple is closing; it also breaks quietly after an update nobody chose. Use ScreenCaptureKit with the system picker, and handle a denied permission as a first class state. ## Phase 2 · A take that survives the process dying Whatever was recorded before a crash is still on disk and playable. ### Steps 1. Seal segments as they close A few seconds each; the cost is negligible and the insurance total. Files: `src/capture/segments.py` 2. Recover orphans at launch The recovery path is the feature, so it runs automatically. Files: `src/recover.py`, `tests/test_recover.py` 3. Check free space before and during Refusing to start beats stopping at minute fifty. Files: `src/disk.py` ### Done when - [ ] A killed recording leaves playable segments - [ ] Launch offers to assemble orphaned segments - [ ] Free space is checked and estimated before starting - [ ] Memory stays bounded over a long take ### Watch out - Buffering the take and muxing one file when the person presses stop means a crash, a forced restart or a full disk at minute fifty destroys the entire recording, which is the single thing this kind of tool must never do. Seal a segment every few seconds and make a crashed session recoverable from what is already on disk. ## Phase 3 · Picture and sound that stay together A long recording is still in sync at the end. ### Steps 1. Keep capture timestamps end to end Never recompute a time from a frame index. Files: `src/capture/segments.py`, `src/assemble.py` 2. Keep audio as its own track Separate clocks, aligned at assembly, not merged at capture. Files: `src/audio.py` 3. Measure drift on a long take A number in the test output, not a feeling while watching. Files: `tests/test_drift.py` ### Done when - [ ] Drift over thirty minutes is measured and under threshold - [ ] The variable frame rate decision is recorded per export - [ ] Audio tracks are separate and independently mutable - [ ] A missing audio device still yields a video with a note ### Watch out - Assuming a constant frame rate is safe because screen capture reports one is how a forty minute tutorial ends up with the mouse click arriving a second before the sound: the capture delivers frames when the screen changes, so the frame count and the wall clock disagree more the longer you record. Keep the timestamps from capture and assemble against them. ## Phase 4 · Trimming without re-encoding everything Cutting the dead air off the front takes seconds and costs no quality. ### Steps 1. Store edits as a list, apply at export The timeline is data and the file is evidence. Files: `src/edits.py` 2. Copy the stream where keyframes allow Re-encode the boundary pieces only, and say when you had to. Files: `src/export/trim.py` 3. Assert the source checksum never changes One test that makes the whole model true. Files: `tests/test_source_untouched.py` ### Done when - [ ] A twenty minute trim completes in seconds - [ ] Source checksums are unchanged after editing - [ ] Exports play in two other players - [ ] Any edit can be undone from the edit list ### Watch out - Re-encoding a twenty minute recording to remove two seconds at the front costs a long wait and a generation of quality every time somebody changes their mind, which they will, four times. Store an edit list, copy the stream where keyframes allow, and re-encode only the pieces at the boundaries. ## Not in this build - Multitrack compositing, transitions and titles. - Stock media libraries. - Hosted upload and sharing. - Camera compositing and keying. ## After v1, if you want it - Cursor emphasis and click highlighting, which is a compositing pass over an existing timeline - Automatic silence detection to suggest cuts, once the edit list has proven itself - A second export preset tuned for a specific destination, with its settings recorded like the first ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. Recordings, segments, edits and exports. DATABASE_PATH=./data/recorder.db # Required. Project folders holding media. Referenced relatively so they can move. PROJECTS_DIR=~/Movies/Recordings # Required. The native helper. Without it there is no capture at all. CAPTURE_HELPER_PATH=./helper/build/CaptureHelper # Required. Assembly, trimming and export. FFMPEG_PATH=/opt/homebrew/bin/ffmpeg # Required. How much of a take a crash can cost. Smaller is safer and slightly busier. SEGMENT_SECONDS=5 # Required. Recording refuses to start below this and stops cleanly if it is reached. MIN_FREE_BYTES=10000000000 # Required. After a successful export only. The cleanup reports what it removed. SEGMENT_RETENTION_DAYS=14
You are building a lean indie version of ScreenFlow. Create the following project files first, then implement the application by following them. Keep the files updated as decisions change. Do not collapse this into a single README or prompt. ===== README.md ===== # ScreenFlow · indie build A local screen recorder whose hard problems are capturing through the supported framework with permission handled properly, writing the take to disk continuously so a crash never destroys it, and keeping variable rate screen frames in step with a microphone over a long recording. Estimated effort: **weekend**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Capture | ScreenCaptureKit through a small native helper, with the system sharing picker | the supported path, and the one that does not put your app on a warnings list | | Processing | FFmpeg as a subprocess for assembly, trimming and export | it already understands the containers and the timestamps you are about to meet | | Runtime | Python 3.12 with FastAPI serving a local interface | only the capture has to be native, and everything else is easier if it is not | | Storage | SQLite for recordings and edits, media inside a project folder | the media is the data and it belongs somewhere a person can move | | Editing | An edit list applied at export, sources never modified | a trim you cannot undo destroys the take a second time | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **A Mac you can grant screen recording permission on** · free if you have one - Why: The permission flow, the picker and the capture framework are the first phase, and none of it can be tested anywhere else. - Get it: Any supported Mac, with the ability to change privacy settings. - Verify: A trivial capture helper records five seconds of the screen - [ ] **The Swift toolchain and command line tools** · free - Why: The capture helper is native, and the toolchain download is large enough to be worth starting before you need it. - Get it: Install Xcode or the command line tools from Apple. - Verify: swiftc --version prints a version - [ ] **FFmpeg with ffprobe** · free - Why: Assembly, trimming and export all shell out to it, and ffprobe is how you verify timestamps and drift. - Get it: Your package manager, or a static build from the official downloads page. - Verify: ffmpeg -version and ffprobe -version both print a version - [ ] **A recording of at least thirty minutes** · free - Why: Drift, disk growth and crash recovery all behave perfectly on a two minute clip and reveal themselves only on a long one. - Get it: Record yourself working for half an hour with a clap at the start and the end. - Verify: The clap appears at the same moment in both tracks at both ends - [ ] **Your disk budget and segment retention** · free - Why: This tool writes gigabytes without being asked, and deciding the limits after filling a disk means deciding them under pressure. - Get it: Write down the minimum free space to start recording and how long segments survive a successful export. - Verify: Both are in the environment and enforced in code ## Quick start Follow Phase 1 in `BUILD_PLAN.md`; it creates the project. Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - Multitrack compositing, transitions and titles. - Stock media libraries. - Hosted upload and sharing. - Camera compositing and keying. - production codecs, rendering speed, and media templates - hosted recording reliability - large-scale rendering - distribution and podcast hosting - professional codecs, effects, and collaboration If one of those is essential to you, that is the reason to keep paying for ScreenFlow, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · ScreenFlow The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a Mac screen recorder and trimmer that cannot lose a take to replace ScreenFlow. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Stack (fixed, do not substitute) - Capture: ScreenCaptureKit through a small native helper, with the system sharing picker · the supported path, and the one that does not put your app on a warnings list - Processing: FFmpeg as a subprocess for assembly, trimming and export · it already understands the containers and the timestamps you are about to meet - Runtime: Python 3.12 with FastAPI serving a local interface · only the capture has to be native, and everything else is easier if it is not - Storage: SQLite for recordings and edits, media inside a project folder · the media is the data and it belongs somewhere a person can move - Editing: An edit list applied at export, sources never modified · a trim you cannot undo destroys the take a second time ### Data model (create this before Phase 1) - recordings(id, started_at, ended_at, source_kind, state, segments_dir, bytes, recovered_from_crash) - segments(recording_id, ordinal, path, start_pts_ms, end_pts_ms, bytes, sealed_at) · written as they arrive and sealed as they close - tracks(recording_id, kind, device_name, sample_rate, channels, first_pts_ms) · screen, microphone and system audio each carry their own clock - projects(id, name, folder, created_at) · media referenced by relative path, so the folder can be moved or copied - edits(project_id, ordinal, recording_id, in_ms, out_ms, track_kind) · an edit list, never a rewritten source - exports(id, project_id, preset, path, settings_json, bytes, started_at, finished_at, exit_code) · the settings that produced the file, kept with it - No operation in this model modifies a source recording, which is the only reason a mistaken trim at midnight is survivable. ### Phase 1 · Capture through the supported path Build: Capture with ScreenCaptureKit from a small native helper. Request screen recording permission before capturing and declare the usage description in the bundle. Use the system content sharing picker to choose the display or window rather than enumerating sources yourself. Trap: Building your own window list and capturing through the older CoreGraphics calls still works today, prompts the person with warnings about software that can collect detailed information about them, and is the path Apple is closing; it also breaks quietly after an update nobody chose. Use ScreenCaptureKit with the system picker, and handle a denied permission as a first class state. Done when: Launching without permission shows the system prompt and an explanatory state, never a black recording. The usage description string is present in the bundle and visible in the system privacy settings entry. Source selection goes through the system picker and the application never enumerates windows itself, verified by review. Denying permission leaves the rest of the application usable for editing existing files. Do not build yet: No crash safety, audio alignment, trimming or export. ### Phase 2 · A take that survives the process dying Build: Write the capture to disk continuously as sealed segments rather than holding it and writing one file at stop. Check free space before starting and stop cleanly with a message when it runs low. On launch, find orphaned segments and offer to assemble them into a recording. Trap: Buffering the take and muxing one file when the person presses stop means a crash, a forced restart or a full disk at minute fifty destroys the entire recording, which is the single thing this kind of tool must never do. Seal a segment every few seconds and make a crashed session recoverable from what is already on disk. Done when: Killing the process mid recording leaves playable segments and the next launch offers to assemble them, tested by killing it. Free space is checked before recording starts and the estimate for the chosen settings is shown. Filling the disk stops the recording with a message and keeps everything captured so far. No code path holds more than a bounded amount of capture in memory, verified by review and by watching memory over a long take. Do not build yet: No audio alignment, trimming or export yet. ### Phase 3 · Picture and sound that stay together Build: Carry the presentation timestamps the capture pipeline gives you rather than assuming a frame rate. Record the microphone as its own track with its own clock and align on timestamps at assembly. Measure drift across a long take and record the measurement. Trap: Assuming a constant frame rate is safe because screen capture reports one is how a forty minute tutorial ends up with the mouse click arriving a second before the sound: the capture delivers frames when the screen changes, so the frame count and the wall clock disagree more the longer you record. Keep the timestamps from capture and assemble against them. Done when: A thirty minute recording with a sharp sound at the start and the end shows drift under a stated threshold at both, measured rather than assumed. Variable frame rate output is either preserved or converted with the choice recorded on the export row. Microphone and system audio are separate tracks that can be muted independently. A take with no audio device available still records video and says which track is missing. Do not build yet: No trimming, presets or project moves yet. ### Phase 4 · Trimming without re-encoding everything Build: Keep an edit list of in and out points per recording. At export, copy the stream where the cut falls on a keyframe and re-encode only the boundary pieces. Preview from the edit list rather than from a rendered file. The source is never modified. Trap: Re-encoding a twenty minute recording to remove two seconds at the front costs a long wait and a generation of quality every time somebody changes their mind, which they will, four times. Store an edit list, copy the stream where keyframes allow, and re-encode only the pieces at the boundaries. Done when: Trimming a twenty minute take completes in seconds rather than minutes, timed in the suite. The source file's checksum is unchanged after any number of edits, asserted. The exported file plays in at least two players other than the one you developed against. An edit can be undone because the edit list is data and the source is intact. Do not build yet: No presets, cleanup or project portability yet. ### Phase 5 · Disk, cleanup and a project you can carry Build: Keep media inside a project folder referenced by relative path, so moving or copying it still opens. Delete segments after a successful export according to a stated retention, and report what was removed. Record the exact settings behind each export preset with the exported file. Trap: Segments accumulating in a temporary directory forever is how a screen recorder quietly consumes two hundred gigabytes, and the person discovers it when the disk is full during the recording they actually needed. Apply a retention after a successful export, report the bytes removed, and show current usage in the interface. Done when: A project folder copied to another path opens with every recording and edit intact, tested. Segments are removed after a successful export according to the retention and the byte count is reported. Current disk usage by project is shown in the interface. Each export row stores the settings used, so the same preset produces the same encoder arguments a month later. Do not build yet: Nothing after this. ### Out of scope (and why) - Multitrack compositing with transitions, titles and animation, which is a full editor and several months rather than a weekend. - Stock media libraries, which are a licensing arrangement rather than a feature and are a standing annual cost in the paid product. - Hosted upload and sharing, which turns a local recorder into storage, bandwidth and an access control model you now operate. - Camera compositing and green screen keying, which need real time processing decisions that conflict with never dropping a frame. ### README must contain - That capture goes through ScreenCaptureKit and the system sharing picker, and which permission and usage description are required - That the take is written as sealed segments continuously, and how a crashed session is recovered on the next launch - That timestamps come from the capture pipeline and are never recomputed from a frame index - That sources are never modified and every edit lives in an edit list applied at export - The retention applied to segments after a successful export, and where disk usage is shown ===== AGENTS.md ===== # Agent instructions · ScreenFlow indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: ScreenCaptureKit through a small native helper, with the system sharing picker, FFmpeg as a subprocess for assembly, trimming and export, Python 3.12 with FastAPI serving a local interface, SQLite for recordings and edits, media inside a project folder, An edit list applied at export, sources never modified. Do not substitute. - Work one phase at a time, in order. Do not start a phase until every "Done when" item of the previous one passes. - Prefer the fewest moving parts that satisfy the step. No frameworks, services or dependencies the plan does not name. - Secrets live in `.env`, never in source or logs. Keep `.env.example` current when a variable is introduced. - Do not invent cryptography, security guarantees, APIs or compliance claims. - Add a focused test for every destructive, security-sensitive or data-loss path the plan names. - Run the project checks before declaring a phase complete, and record any deliberate shortcut in the README under "Tradeoffs". ## Known traps - Building your own window list and capturing through the older CoreGraphics calls still works today, prompts the person with warnings about software that can collect detailed information about them, and is the path Apple is closing; it also breaks quietly after an update nobody chose. Use ScreenCaptureKit with the system picker, and handle a denied permission as a first class state. - Buffering the take and muxing one file when the person presses stop means a crash, a forced restart or a full disk at minute fifty destroys the entire recording, which is the single thing this kind of tool must never do. Seal a segment every few seconds and make a crashed session recoverable from what is already on disk. - Assuming a constant frame rate is safe because screen capture reports one is how a forty minute tutorial ends up with the mouse click arriving a second before the sound: the capture delivers frames when the screen changes, so the frame count and the wall clock disagree more the longer you record. Keep the timestamps from capture and assemble against them. - Re-encoding a twenty minute recording to remove two seconds at the front costs a long wait and a generation of quality every time somebody changes their mind, which they will, four times. Store an edit list, copy the stream where keyframes allow, and re-encode only the pieces at the boundaries. ===== BUILD_PLAN.md ===== # Build plan · ScreenFlow A local screen recorder whose hard problems are capturing through the supported framework with permission handled properly, writing the take to disk continuously so a crash never destroys it, and keeping variable rate screen frames in step with a microphone over a long recording. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · Capture through the supported path Recording starts only after permission is granted, through the system's own picker. ### Steps 1. Put capture in a native helper One small process with one job, talking to the rest over a local socket. Files: `helper/Capture.swift`, `src/capture/client.py` 2. Declare the usage description and ask first A missing description string is a silent denial at the worst moment. Files: `helper/Info.plist` 3. Use the system sharing picker Less code, fewer prompts, and the sources the person actually expects. Files: `helper/Picker.swift` ### Done when - [ ] No permission produces a prompt and an explained state - [ ] The usage description appears in system settings - [ ] Sources come from the system picker only - [ ] Editing still works when permission is denied ### Watch out - Building your own window list and capturing through the older CoreGraphics calls still works today, prompts the person with warnings about software that can collect detailed information about them, and is the path Apple is closing; it also breaks quietly after an update nobody chose. Use ScreenCaptureKit with the system picker, and handle a denied permission as a first class state. ## Phase 2 · A take that survives the process dying Whatever was recorded before a crash is still on disk and playable. ### Steps 1. Seal segments as they close A few seconds each; the cost is negligible and the insurance total. Files: `src/capture/segments.py` 2. Recover orphans at launch The recovery path is the feature, so it runs automatically. Files: `src/recover.py`, `tests/test_recover.py` 3. Check free space before and during Refusing to start beats stopping at minute fifty. Files: `src/disk.py` ### Done when - [ ] A killed recording leaves playable segments - [ ] Launch offers to assemble orphaned segments - [ ] Free space is checked and estimated before starting - [ ] Memory stays bounded over a long take ### Watch out - Buffering the take and muxing one file when the person presses stop means a crash, a forced restart or a full disk at minute fifty destroys the entire recording, which is the single thing this kind of tool must never do. Seal a segment every few seconds and make a crashed session recoverable from what is already on disk. ## Phase 3 · Picture and sound that stay together A long recording is still in sync at the end. ### Steps 1. Keep capture timestamps end to end Never recompute a time from a frame index. Files: `src/capture/segments.py`, `src/assemble.py` 2. Keep audio as its own track Separate clocks, aligned at assembly, not merged at capture. Files: `src/audio.py` 3. Measure drift on a long take A number in the test output, not a feeling while watching. Files: `tests/test_drift.py` ### Done when - [ ] Drift over thirty minutes is measured and under threshold - [ ] The variable frame rate decision is recorded per export - [ ] Audio tracks are separate and independently mutable - [ ] A missing audio device still yields a video with a note ### Watch out - Assuming a constant frame rate is safe because screen capture reports one is how a forty minute tutorial ends up with the mouse click arriving a second before the sound: the capture delivers frames when the screen changes, so the frame count and the wall clock disagree more the longer you record. Keep the timestamps from capture and assemble against them. ## Phase 4 · Trimming without re-encoding everything Cutting the dead air off the front takes seconds and costs no quality. ### Steps 1. Store edits as a list, apply at export The timeline is data and the file is evidence. Files: `src/edits.py` 2. Copy the stream where keyframes allow Re-encode the boundary pieces only, and say when you had to. Files: `src/export/trim.py` 3. Assert the source checksum never changes One test that makes the whole model true. Files: `tests/test_source_untouched.py` ### Done when - [ ] A twenty minute trim completes in seconds - [ ] Source checksums are unchanged after editing - [ ] Exports play in two other players - [ ] Any edit can be undone from the edit list ### Watch out - Re-encoding a twenty minute recording to remove two seconds at the front costs a long wait and a generation of quality every time somebody changes their mind, which they will, four times. Store an edit list, copy the stream where keyframes allow, and re-encode only the pieces at the boundaries. ## Not in this build - Multitrack compositing, transitions and titles. - Stock media libraries. - Hosted upload and sharing. - Camera compositing and keying. ## After v1, if you want it - Cursor emphasis and click highlighting, which is a compositing pass over an existing timeline - Automatic silence detection to suggest cuts, once the edit list has proven itself - A second export preset tuned for a specific destination, with its settings recorded like the first ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. Recordings, segments, edits and exports. DATABASE_PATH=./data/recorder.db # Required. Project folders holding media. Referenced relatively so they can move. PROJECTS_DIR=~/Movies/Recordings # Required. The native helper. Without it there is no capture at all. CAPTURE_HELPER_PATH=./helper/build/CaptureHelper # Required. Assembly, trimming and export. FFMPEG_PATH=/opt/homebrew/bin/ffmpeg # Required. How much of a take a crash can cost. Smaller is safer and slightly busier. SEGMENT_SECONDS=5 # Required. Recording refuses to start below this and stops cleanly if it is reached. MIN_FREE_BYTES=10000000000 # Required. After a successful export only. The cleanup reports what it removed. SEGMENT_RETENTION_DAYS=14
You are building a production product version of ScreenFlow. Create the following project files first, then implement the application by following them. Keep the files updated as decisions change. Do not collapse this into a single README or prompt. ===== PRODUCT.md ===== # ScreenFlow · product brief ## Problem ScreenFlow's solo core is compact: build a local Mac screen recording + editing workflow that imports user-owned media, applies the core edit or transformation, previews it, and exports a standard file. A competent builder can reach a useful personal version in one sitting, while the paid product mainly wins on media pipeline, hosting, distribution. ## Product outcome A recorder that asks for permission the supported way, writes every second of a take to disk as it happens, stays in sync after half an hour, trims without re-encoding the whole file, and cleans up after itself instead of filling the disk. ## Target user A builder who needs a maintainable product foundation, not a one-off demo. ## Required capabilities - Python 3.12 - FFmpeg - desktop with sufficient storage ## Explicit non-goals for v1 - Multitrack compositing, transitions and titles. - Stock media libraries. - Hosted upload and sharing. - Camera compositing and keying. - production codecs, rendering speed, and media templates - hosted recording reliability - large-scale rendering - distribution and podcast hosting - professional codecs, effects, and collaboration ## Success criteria - Permission and the system picker are confirmed in use - A killed recording is confirmed recoverable from segments - Drift over thirty minutes is confirmed measured and within threshold - Source checksums are confirmed unchanged after editing - Retention is confirmed to run only after a clean export ===== BRIEF.md ===== # Build brief · ScreenFlow The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a Mac screen recorder and trimmer that cannot lose a take to replace ScreenFlow. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Stack (fixed, do not substitute) - Capture: ScreenCaptureKit through a small native helper, with the system sharing picker · the supported path, and the one that does not put your app on a warnings list - Processing: FFmpeg as a subprocess for assembly, trimming and export · it already understands the containers and the timestamps you are about to meet - Runtime: Python 3.12 with FastAPI serving a local interface · only the capture has to be native, and everything else is easier if it is not - Storage: SQLite for recordings and edits, media inside a project folder · the media is the data and it belongs somewhere a person can move - Editing: An edit list applied at export, sources never modified · a trim you cannot undo destroys the take a second time ### Data model (create this before Phase 1) - recordings(id, started_at, ended_at, source_kind, state, segments_dir, bytes, recovered_from_crash) - segments(recording_id, ordinal, path, start_pts_ms, end_pts_ms, bytes, sealed_at) · written as they arrive and sealed as they close - tracks(recording_id, kind, device_name, sample_rate, channels, first_pts_ms) · screen, microphone and system audio each carry their own clock - projects(id, name, folder, created_at) · media referenced by relative path, so the folder can be moved or copied - edits(project_id, ordinal, recording_id, in_ms, out_ms, track_kind) · an edit list, never a rewritten source - exports(id, project_id, preset, path, settings_json, bytes, started_at, finished_at, exit_code) · the settings that produced the file, kept with it - No operation in this model modifies a source recording, which is the only reason a mistaken trim at midnight is survivable. ### Phase 1 · Capture through the supported path Build: Capture with ScreenCaptureKit from a small native helper. Request screen recording permission before capturing and declare the usage description in the bundle. Use the system content sharing picker to choose the display or window rather than enumerating sources yourself. Trap: Building your own window list and capturing through the older CoreGraphics calls still works today, prompts the person with warnings about software that can collect detailed information about them, and is the path Apple is closing; it also breaks quietly after an update nobody chose. Use ScreenCaptureKit with the system picker, and handle a denied permission as a first class state. Done when: Launching without permission shows the system prompt and an explanatory state, never a black recording. The usage description string is present in the bundle and visible in the system privacy settings entry. Source selection goes through the system picker and the application never enumerates windows itself, verified by review. Denying permission leaves the rest of the application usable for editing existing files. Do not build yet: No crash safety, audio alignment, trimming or export. ### Phase 2 · A take that survives the process dying Build: Write the capture to disk continuously as sealed segments rather than holding it and writing one file at stop. Check free space before starting and stop cleanly with a message when it runs low. On launch, find orphaned segments and offer to assemble them into a recording. Trap: Buffering the take and muxing one file when the person presses stop means a crash, a forced restart or a full disk at minute fifty destroys the entire recording, which is the single thing this kind of tool must never do. Seal a segment every few seconds and make a crashed session recoverable from what is already on disk. Done when: Killing the process mid recording leaves playable segments and the next launch offers to assemble them, tested by killing it. Free space is checked before recording starts and the estimate for the chosen settings is shown. Filling the disk stops the recording with a message and keeps everything captured so far. No code path holds more than a bounded amount of capture in memory, verified by review and by watching memory over a long take. Do not build yet: No audio alignment, trimming or export yet. ### Phase 3 · Picture and sound that stay together Build: Carry the presentation timestamps the capture pipeline gives you rather than assuming a frame rate. Record the microphone as its own track with its own clock and align on timestamps at assembly. Measure drift across a long take and record the measurement. Trap: Assuming a constant frame rate is safe because screen capture reports one is how a forty minute tutorial ends up with the mouse click arriving a second before the sound: the capture delivers frames when the screen changes, so the frame count and the wall clock disagree more the longer you record. Keep the timestamps from capture and assemble against them. Done when: A thirty minute recording with a sharp sound at the start and the end shows drift under a stated threshold at both, measured rather than assumed. Variable frame rate output is either preserved or converted with the choice recorded on the export row. Microphone and system audio are separate tracks that can be muted independently. A take with no audio device available still records video and says which track is missing. Do not build yet: No trimming, presets or project moves yet. ### Phase 4 · Trimming without re-encoding everything Build: Keep an edit list of in and out points per recording. At export, copy the stream where the cut falls on a keyframe and re-encode only the boundary pieces. Preview from the edit list rather than from a rendered file. The source is never modified. Trap: Re-encoding a twenty minute recording to remove two seconds at the front costs a long wait and a generation of quality every time somebody changes their mind, which they will, four times. Store an edit list, copy the stream where keyframes allow, and re-encode only the pieces at the boundaries. Done when: Trimming a twenty minute take completes in seconds rather than minutes, timed in the suite. The source file's checksum is unchanged after any number of edits, asserted. The exported file plays in at least two players other than the one you developed against. An edit can be undone because the edit list is data and the source is intact. Do not build yet: No presets, cleanup or project portability yet. ### Phase 5 · Disk, cleanup and a project you can carry Build: Keep media inside a project folder referenced by relative path, so moving or copying it still opens. Delete segments after a successful export according to a stated retention, and report what was removed. Record the exact settings behind each export preset with the exported file. Trap: Segments accumulating in a temporary directory forever is how a screen recorder quietly consumes two hundred gigabytes, and the person discovers it when the disk is full during the recording they actually needed. Apply a retention after a successful export, report the bytes removed, and show current usage in the interface. Done when: A project folder copied to another path opens with every recording and edit intact, tested. Segments are removed after a successful export according to the retention and the byte count is reported. Current disk usage by project is shown in the interface. Each export row stores the settings used, so the same preset produces the same encoder arguments a month later. Do not build yet: Nothing after this. ### Out of scope (and why) - Multitrack compositing with transitions, titles and animation, which is a full editor and several months rather than a weekend. - Stock media libraries, which are a licensing arrangement rather than a feature and are a standing annual cost in the paid product. - Hosted upload and sharing, which turns a local recorder into storage, bandwidth and an access control model you now operate. - Camera compositing and green screen keying, which need real time processing decisions that conflict with never dropping a frame. ### README must contain - That capture goes through ScreenCaptureKit and the system sharing picker, and which permission and usage description are required - That the take is written as sealed segments continuously, and how a crashed session is recovered on the next launch - That timestamps come from the capture pipeline and are never recomputed from a frame index - That sources are never modified and every edit lives in an edit list applied at export - The retention applied to segments after a successful export, and where disk usage is shown ===== ARCHITECTURE.md ===== # Architecture · ScreenFlow ## Stack | Part | Choice | Why | | --- | --- | --- | | Capture | ScreenCaptureKit through a small native helper, with the system sharing picker | the supported path, and the one that does not put your app on a warnings list | | Processing | FFmpeg as a subprocess for assembly, trimming and export | it already understands the containers and the timestamps you are about to meet | | Runtime | Python 3.12 with FastAPI serving a local interface | only the capture has to be native, and everything else is easier if it is not | | Storage | SQLite for recordings and edits, media inside a project folder | the media is the data and it belongs somewhere a person can move | | Editing | An edit list applied at export, sources never modified | a trim you cannot undo destroys the take a second time | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Capture | The native helper, permission and the system picker. | Not swappable for the deprecated capture calls or a self built picker. | | Segments | Continuous sealed writes and crash recovery. | Any segmenting, provided nothing large is held in memory. | | Assembly | Timestamp based alignment across tracks. | Any muxer, provided timestamps come from capture. | | Edits | The edit list and stream copying at export. | Any editor, provided sources are never modified. | | Lifecycle | Disk checks, retention and project portability. | Any policy, provided retention only runs after a clean export. | ## Configuration Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists. - `DATABASE_PATH` · required · Recordings, segments, edits and exports. - `PROJECTS_DIR` · required · Project folders holding media. Referenced relatively so they can move. - `CAPTURE_HELPER_PATH` · required · The native helper. Without it there is no capture at all. - `FFMPEG_PATH` · required · Assembly, trimming and export. - `SEGMENT_SECONDS` · required · How much of a take a crash can cost. Smaller is safer and slightly busier. - `MIN_FREE_BYTES` · required · Recording refuses to start below this and stops cleanly if it is reached. - `SEGMENT_RETENTION_DAYS` · required · After a successful export only. The cleanup reports what it removed. ## Production baseline - Security: least privilege, input validation at every boundary, secret redaction in logs, rate limits on abuse-prone paths, no invented security primitives. - Data: explicit schema and migrations, transactional writes where integrity matters, backup and restore procedures that have been exercised. - Integrations: adapters around third-party providers, idempotent webhook or job processing, bounded retries, timeouts. - Observability: structured logs with request or operation ids, an error-tracking hook, and health and readiness checks where a server exists. - Quality: unit tests for domain rules, integration tests at module boundaries, one end-to-end test of the critical path. ## Decision records For each dependency in the stack table, keep a short note: why it was chosen, its failure mode, and how it is replaced. Do not add infrastructure until a requirement in `PRODUCT.md` justifies it. ===== AGENTS.md ===== # Agent instructions · ScreenFlow product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: ScreenCaptureKit through a small native helper, with the system sharing picker, FFmpeg as a subprocess for assembly, trimming and export, Python 3.12 with FastAPI serving a local interface, SQLite for recordings and edits, media inside a project folder, An edit list applied at export, sources never modified. - Implement milestone by milestone from `MILESTONES.md`; keep each change reviewable and leave the application runnable at every commit. - Treat authentication, payments, encryption, imports, webhooks and destructive actions as high-risk boundaries when present. - Never invent cryptography or silently weaken a requirement to make a check pass. - Put every external service behind an interface with a deterministic fake for tests. - Add migrations and rollback or recovery notes for every persistent data change. - Log useful operational context without credentials, tokens, passwords or personal data. - Update documentation and run every check before completing a milestone. ## Known traps - Building your own window list and capturing through the older CoreGraphics calls still works today, prompts the person with warnings about software that can collect detailed information about them, and is the path Apple is closing; it also breaks quietly after an update nobody chose. Use ScreenCaptureKit with the system picker, and handle a denied permission as a first class state. - Buffering the take and muxing one file when the person presses stop means a crash, a forced restart or a full disk at minute fifty destroys the entire recording, which is the single thing this kind of tool must never do. Seal a segment every few seconds and make a crashed session recoverable from what is already on disk. - Assuming a constant frame rate is safe because screen capture reports one is how a forty minute tutorial ends up with the mouse click arriving a second before the sound: the capture delivers frames when the screen changes, so the frame count and the wall clock disagree more the longer you record. Keep the timestamps from capture and assemble against them. - Re-encoding a twenty minute recording to remove two seconds at the front costs a long wait and a generation of quality every time somebody changes their mind, which they will, four times. Store an edit list, copy the stream where keyframes allow, and re-encode only the pieces at the boundaries. - Segments accumulating in a temporary directory forever is how a screen recorder quietly consumes two hundred gigabytes, and the person discovers it when the disk is full during the recording they actually needed. Apply a retention after a successful export, report the bytes removed, and show current usage in the interface. ===== MILESTONES.md ===== # Delivery milestones · ScreenFlow Estimated effort: **weekend** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · Capture through the supported path Recording starts only after permission is granted, through the system's own picker. ### Steps 1. Put capture in a native helper One small process with one job, talking to the rest over a local socket. Files: `helper/Capture.swift`, `src/capture/client.py` 2. Declare the usage description and ask first A missing description string is a silent denial at the worst moment. Files: `helper/Info.plist` 3. Use the system sharing picker Less code, fewer prompts, and the sources the person actually expects. Files: `helper/Picker.swift` ### Done when - [ ] No permission produces a prompt and an explained state - [ ] The usage description appears in system settings - [ ] Sources come from the system picker only - [ ] Editing still works when permission is denied ### Watch out - Building your own window list and capturing through the older CoreGraphics calls still works today, prompts the person with warnings about software that can collect detailed information about them, and is the path Apple is closing; it also breaks quietly after an update nobody chose. Use ScreenCaptureKit with the system picker, and handle a denied permission as a first class state. ## M2 · A take that survives the process dying Whatever was recorded before a crash is still on disk and playable. ### Steps 1. Seal segments as they close A few seconds each; the cost is negligible and the insurance total. Files: `src/capture/segments.py` 2. Recover orphans at launch The recovery path is the feature, so it runs automatically. Files: `src/recover.py`, `tests/test_recover.py` 3. Check free space before and during Refusing to start beats stopping at minute fifty. Files: `src/disk.py` ### Done when - [ ] A killed recording leaves playable segments - [ ] Launch offers to assemble orphaned segments - [ ] Free space is checked and estimated before starting - [ ] Memory stays bounded over a long take ### Watch out - Buffering the take and muxing one file when the person presses stop means a crash, a forced restart or a full disk at minute fifty destroys the entire recording, which is the single thing this kind of tool must never do. Seal a segment every few seconds and make a crashed session recoverable from what is already on disk. ## M3 · Picture and sound that stay together A long recording is still in sync at the end. ### Steps 1. Keep capture timestamps end to end Never recompute a time from a frame index. Files: `src/capture/segments.py`, `src/assemble.py` 2. Keep audio as its own track Separate clocks, aligned at assembly, not merged at capture. Files: `src/audio.py` 3. Measure drift on a long take A number in the test output, not a feeling while watching. Files: `tests/test_drift.py` ### Done when - [ ] Drift over thirty minutes is measured and under threshold - [ ] The variable frame rate decision is recorded per export - [ ] Audio tracks are separate and independently mutable - [ ] A missing audio device still yields a video with a note ### Watch out - Assuming a constant frame rate is safe because screen capture reports one is how a forty minute tutorial ends up with the mouse click arriving a second before the sound: the capture delivers frames when the screen changes, so the frame count and the wall clock disagree more the longer you record. Keep the timestamps from capture and assemble against them. ## M4 · Trimming without re-encoding everything Cutting the dead air off the front takes seconds and costs no quality. ### Steps 1. Store edits as a list, apply at export The timeline is data and the file is evidence. Files: `src/edits.py` 2. Copy the stream where keyframes allow Re-encode the boundary pieces only, and say when you had to. Files: `src/export/trim.py` 3. Assert the source checksum never changes One test that makes the whole model true. Files: `tests/test_source_untouched.py` ### Done when - [ ] A twenty minute trim completes in seconds - [ ] Source checksums are unchanged after editing - [ ] Exports play in two other players - [ ] Any edit can be undone from the edit list ### Watch out - Re-encoding a twenty minute recording to remove two seconds at the front costs a long wait and a generation of quality every time somebody changes their mind, which they will, four times. Store an edit list, copy the stream where keyframes allow, and re-encode only the pieces at the boundaries. ## M5 · Disk, cleanup and a project you can carry (production only) The tool does not fill the disk and a project folder still opens somewhere else. ### Steps 1. Reference media relatively Absolute paths are what stop a project from moving. Files: `src/projects.py` 2. Apply retention after a successful export After, and only after, the export exited cleanly. Files: `src/retention.py` 3. Store preset settings with the export So a file can be reproduced rather than remembered. Files: `src/export/presets.py` ### Done when - [ ] A copied project folder opens intact - [ ] Retention removes segments and reports bytes - [ ] Disk usage per project is visible - [ ] Export settings are stored with each file ### Watch out - Segments accumulating in a temporary directory forever is how a screen recorder quietly consumes two hundred gigabytes, and the person discovers it when the disk is full during the recording they actually needed. Apply a retention after a successful export, report the bytes removed, and show current usage in the interface. ===== OPERATIONS.md ===== # Operations · ScreenFlow ## Backup Back up project folders, which contain the media and the relative references; the database can be rebuilt from a folder but the footage cannot be re-recorded. A finished export belongs in the backup before the segments are cleaned up. ## Restore Copy a project folder back and open it; the relative references are what make this work from a different path. Confirm one export plays before trusting the restore. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring Free space against the minimum, orphaned segment directories from crashed sessions, and measured drift on the last long recording. Rising orphan counts mean the helper is crashing and nobody has noticed because recovery works. ## Incident checklist The incident is a lost take, and the recovery is the orphan assembly path, which is why it runs on launch rather than waiting to be found in a menu. 1. Contain the issue without destroying evidence or user data. 2. Record the timeline and affected scope. 3. Rotate exposed secrets and revoke compromised sessions or credentials. 4. Restore from a verified backup when needed. 5. Document the root cause, the remediation and the regression test. ## Release gate - [ ] Permission and the system picker are confirmed in use - [ ] A killed recording is confirmed recoverable from segments - [ ] Drift over thirty minutes is confirmed measured and within threshold - [ ] Source checksums are confirmed unchanged after editing - [ ] Retention is confirmed to run only after a clean export ## Launch constraint Do not market omitted ScreenFlow capabilities as implemented. The non-goals in `PRODUCT.md` remain user-visible limitations until they are deliberately delivered. ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. Recordings, segments, edits and exports. DATABASE_PATH=./data/recorder.db # Required. Project folders holding media. Referenced relatively so they can move. PROJECTS_DIR=~/Movies/Recordings # Required. The native helper. Without it there is no capture at all. CAPTURE_HELPER_PATH=./helper/build/CaptureHelper # Required. Assembly, trimming and export. FFMPEG_PATH=/opt/homebrew/bin/ffmpeg # Required. How much of a take a crash can cost. Smaller is safer and slightly busier. SEGMENT_SECONDS=5 # Required. Recording refuses to start below this and stops cleanly if it is reached. MIN_FREE_BYTES=10000000000 # Required. After a successful export only. The cleanup reports what it removed. SEGMENT_RETENTION_DAYS=14
# ScreenFlow · indie build A local screen recorder whose hard problems are capturing through the supported framework with permission handled properly, writing the take to disk continuously so a crash never destroys it, and keeping variable rate screen frames in step with a microphone over a long recording. Estimated effort: **weekend**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Capture | ScreenCaptureKit through a small native helper, with the system sharing picker | the supported path, and the one that does not put your app on a warnings list | | Processing | FFmpeg as a subprocess for assembly, trimming and export | it already understands the containers and the timestamps you are about to meet | | Runtime | Python 3.12 with FastAPI serving a local interface | only the capture has to be native, and everything else is easier if it is not | | Storage | SQLite for recordings and edits, media inside a project folder | the media is the data and it belongs somewhere a person can move | | Editing | An edit list applied at export, sources never modified | a trim you cannot undo destroys the take a second time | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **A Mac you can grant screen recording permission on** · free if you have one - Why: The permission flow, the picker and the capture framework are the first phase, and none of it can be tested anywhere else. - Get it: Any supported Mac, with the ability to change privacy settings. - Verify: A trivial capture helper records five seconds of the screen - [ ] **The Swift toolchain and command line tools** · free - Why: The capture helper is native, and the toolchain download is large enough to be worth starting before you need it. - Get it: Install Xcode or the command line tools from Apple. - Verify: swiftc --version prints a version - [ ] **FFmpeg with ffprobe** · free - Why: Assembly, trimming and export all shell out to it, and ffprobe is how you verify timestamps and drift. - Get it: Your package manager, or a static build from the official downloads page. - Verify: ffmpeg -version and ffprobe -version both print a version - [ ] **A recording of at least thirty minutes** · free - Why: Drift, disk growth and crash recovery all behave perfectly on a two minute clip and reveal themselves only on a long one. - Get it: Record yourself working for half an hour with a clap at the start and the end. - Verify: The clap appears at the same moment in both tracks at both ends - [ ] **Your disk budget and segment retention** · free - Why: This tool writes gigabytes without being asked, and deciding the limits after filling a disk means deciding them under pressure. - Get it: Write down the minimum free space to start recording and how long segments survive a successful export. - Verify: Both are in the environment and enforced in code ## Quick start Follow Phase 1 in `BUILD_PLAN.md`; it creates the project. Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - Multitrack compositing, transitions and titles. - Stock media libraries. - Hosted upload and sharing. - Camera compositing and keying. - production codecs, rendering speed, and media templates - hosted recording reliability - large-scale rendering - distribution and podcast hosting - professional codecs, effects, and collaboration If one of those is essential to you, that is the reason to keep paying for ScreenFlow, and the README should say so rather than pretend.
# Build brief · ScreenFlow The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a Mac screen recorder and trimmer that cannot lose a take to replace ScreenFlow. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Stack (fixed, do not substitute) - Capture: ScreenCaptureKit through a small native helper, with the system sharing picker · the supported path, and the one that does not put your app on a warnings list - Processing: FFmpeg as a subprocess for assembly, trimming and export · it already understands the containers and the timestamps you are about to meet - Runtime: Python 3.12 with FastAPI serving a local interface · only the capture has to be native, and everything else is easier if it is not - Storage: SQLite for recordings and edits, media inside a project folder · the media is the data and it belongs somewhere a person can move - Editing: An edit list applied at export, sources never modified · a trim you cannot undo destroys the take a second time ### Data model (create this before Phase 1) - recordings(id, started_at, ended_at, source_kind, state, segments_dir, bytes, recovered_from_crash) - segments(recording_id, ordinal, path, start_pts_ms, end_pts_ms, bytes, sealed_at) · written as they arrive and sealed as they close - tracks(recording_id, kind, device_name, sample_rate, channels, first_pts_ms) · screen, microphone and system audio each carry their own clock - projects(id, name, folder, created_at) · media referenced by relative path, so the folder can be moved or copied - edits(project_id, ordinal, recording_id, in_ms, out_ms, track_kind) · an edit list, never a rewritten source - exports(id, project_id, preset, path, settings_json, bytes, started_at, finished_at, exit_code) · the settings that produced the file, kept with it - No operation in this model modifies a source recording, which is the only reason a mistaken trim at midnight is survivable. ### Phase 1 · Capture through the supported path Build: Capture with ScreenCaptureKit from a small native helper. Request screen recording permission before capturing and declare the usage description in the bundle. Use the system content sharing picker to choose the display or window rather than enumerating sources yourself. Trap: Building your own window list and capturing through the older CoreGraphics calls still works today, prompts the person with warnings about software that can collect detailed information about them, and is the path Apple is closing; it also breaks quietly after an update nobody chose. Use ScreenCaptureKit with the system picker, and handle a denied permission as a first class state. Done when: Launching without permission shows the system prompt and an explanatory state, never a black recording. The usage description string is present in the bundle and visible in the system privacy settings entry. Source selection goes through the system picker and the application never enumerates windows itself, verified by review. Denying permission leaves the rest of the application usable for editing existing files. Do not build yet: No crash safety, audio alignment, trimming or export. ### Phase 2 · A take that survives the process dying Build: Write the capture to disk continuously as sealed segments rather than holding it and writing one file at stop. Check free space before starting and stop cleanly with a message when it runs low. On launch, find orphaned segments and offer to assemble them into a recording. Trap: Buffering the take and muxing one file when the person presses stop means a crash, a forced restart or a full disk at minute fifty destroys the entire recording, which is the single thing this kind of tool must never do. Seal a segment every few seconds and make a crashed session recoverable from what is already on disk. Done when: Killing the process mid recording leaves playable segments and the next launch offers to assemble them, tested by killing it. Free space is checked before recording starts and the estimate for the chosen settings is shown. Filling the disk stops the recording with a message and keeps everything captured so far. No code path holds more than a bounded amount of capture in memory, verified by review and by watching memory over a long take. Do not build yet: No audio alignment, trimming or export yet. ### Phase 3 · Picture and sound that stay together Build: Carry the presentation timestamps the capture pipeline gives you rather than assuming a frame rate. Record the microphone as its own track with its own clock and align on timestamps at assembly. Measure drift across a long take and record the measurement. Trap: Assuming a constant frame rate is safe because screen capture reports one is how a forty minute tutorial ends up with the mouse click arriving a second before the sound: the capture delivers frames when the screen changes, so the frame count and the wall clock disagree more the longer you record. Keep the timestamps from capture and assemble against them. Done when: A thirty minute recording with a sharp sound at the start and the end shows drift under a stated threshold at both, measured rather than assumed. Variable frame rate output is either preserved or converted with the choice recorded on the export row. Microphone and system audio are separate tracks that can be muted independently. A take with no audio device available still records video and says which track is missing. Do not build yet: No trimming, presets or project moves yet. ### Phase 4 · Trimming without re-encoding everything Build: Keep an edit list of in and out points per recording. At export, copy the stream where the cut falls on a keyframe and re-encode only the boundary pieces. Preview from the edit list rather than from a rendered file. The source is never modified. Trap: Re-encoding a twenty minute recording to remove two seconds at the front costs a long wait and a generation of quality every time somebody changes their mind, which they will, four times. Store an edit list, copy the stream where keyframes allow, and re-encode only the pieces at the boundaries. Done when: Trimming a twenty minute take completes in seconds rather than minutes, timed in the suite. The source file's checksum is unchanged after any number of edits, asserted. The exported file plays in at least two players other than the one you developed against. An edit can be undone because the edit list is data and the source is intact. Do not build yet: No presets, cleanup or project portability yet. ### Phase 5 · Disk, cleanup and a project you can carry Build: Keep media inside a project folder referenced by relative path, so moving or copying it still opens. Delete segments after a successful export according to a stated retention, and report what was removed. Record the exact settings behind each export preset with the exported file. Trap: Segments accumulating in a temporary directory forever is how a screen recorder quietly consumes two hundred gigabytes, and the person discovers it when the disk is full during the recording they actually needed. Apply a retention after a successful export, report the bytes removed, and show current usage in the interface. Done when: A project folder copied to another path opens with every recording and edit intact, tested. Segments are removed after a successful export according to the retention and the byte count is reported. Current disk usage by project is shown in the interface. Each export row stores the settings used, so the same preset produces the same encoder arguments a month later. Do not build yet: Nothing after this. ### Out of scope (and why) - Multitrack compositing with transitions, titles and animation, which is a full editor and several months rather than a weekend. - Stock media libraries, which are a licensing arrangement rather than a feature and are a standing annual cost in the paid product. - Hosted upload and sharing, which turns a local recorder into storage, bandwidth and an access control model you now operate. - Camera compositing and green screen keying, which need real time processing decisions that conflict with never dropping a frame. ### README must contain - That capture goes through ScreenCaptureKit and the system sharing picker, and which permission and usage description are required - That the take is written as sealed segments continuously, and how a crashed session is recovered on the next launch - That timestamps come from the capture pipeline and are never recomputed from a frame index - That sources are never modified and every edit lives in an edit list applied at export - The retention applied to segments after a successful export, and where disk usage is shown
# Agent instructions · ScreenFlow indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: ScreenCaptureKit through a small native helper, with the system sharing picker, FFmpeg as a subprocess for assembly, trimming and export, Python 3.12 with FastAPI serving a local interface, SQLite for recordings and edits, media inside a project folder, An edit list applied at export, sources never modified. Do not substitute. - Work one phase at a time, in order. Do not start a phase until every "Done when" item of the previous one passes. - Prefer the fewest moving parts that satisfy the step. No frameworks, services or dependencies the plan does not name. - Secrets live in `.env`, never in source or logs. Keep `.env.example` current when a variable is introduced. - Do not invent cryptography, security guarantees, APIs or compliance claims. - Add a focused test for every destructive, security-sensitive or data-loss path the plan names. - Run the project checks before declaring a phase complete, and record any deliberate shortcut in the README under "Tradeoffs". ## Known traps - Building your own window list and capturing through the older CoreGraphics calls still works today, prompts the person with warnings about software that can collect detailed information about them, and is the path Apple is closing; it also breaks quietly after an update nobody chose. Use ScreenCaptureKit with the system picker, and handle a denied permission as a first class state. - Buffering the take and muxing one file when the person presses stop means a crash, a forced restart or a full disk at minute fifty destroys the entire recording, which is the single thing this kind of tool must never do. Seal a segment every few seconds and make a crashed session recoverable from what is already on disk. - Assuming a constant frame rate is safe because screen capture reports one is how a forty minute tutorial ends up with the mouse click arriving a second before the sound: the capture delivers frames when the screen changes, so the frame count and the wall clock disagree more the longer you record. Keep the timestamps from capture and assemble against them. - Re-encoding a twenty minute recording to remove two seconds at the front costs a long wait and a generation of quality every time somebody changes their mind, which they will, four times. Store an edit list, copy the stream where keyframes allow, and re-encode only the pieces at the boundaries.
# Build plan · ScreenFlow A local screen recorder whose hard problems are capturing through the supported framework with permission handled properly, writing the take to disk continuously so a crash never destroys it, and keeping variable rate screen frames in step with a microphone over a long recording. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · Capture through the supported path Recording starts only after permission is granted, through the system's own picker. ### Steps 1. Put capture in a native helper One small process with one job, talking to the rest over a local socket. Files: `helper/Capture.swift`, `src/capture/client.py` 2. Declare the usage description and ask first A missing description string is a silent denial at the worst moment. Files: `helper/Info.plist` 3. Use the system sharing picker Less code, fewer prompts, and the sources the person actually expects. Files: `helper/Picker.swift` ### Done when - [ ] No permission produces a prompt and an explained state - [ ] The usage description appears in system settings - [ ] Sources come from the system picker only - [ ] Editing still works when permission is denied ### Watch out - Building your own window list and capturing through the older CoreGraphics calls still works today, prompts the person with warnings about software that can collect detailed information about them, and is the path Apple is closing; it also breaks quietly after an update nobody chose. Use ScreenCaptureKit with the system picker, and handle a denied permission as a first class state. ## Phase 2 · A take that survives the process dying Whatever was recorded before a crash is still on disk and playable. ### Steps 1. Seal segments as they close A few seconds each; the cost is negligible and the insurance total. Files: `src/capture/segments.py` 2. Recover orphans at launch The recovery path is the feature, so it runs automatically. Files: `src/recover.py`, `tests/test_recover.py` 3. Check free space before and during Refusing to start beats stopping at minute fifty. Files: `src/disk.py` ### Done when - [ ] A killed recording leaves playable segments - [ ] Launch offers to assemble orphaned segments - [ ] Free space is checked and estimated before starting - [ ] Memory stays bounded over a long take ### Watch out - Buffering the take and muxing one file when the person presses stop means a crash, a forced restart or a full disk at minute fifty destroys the entire recording, which is the single thing this kind of tool must never do. Seal a segment every few seconds and make a crashed session recoverable from what is already on disk. ## Phase 3 · Picture and sound that stay together A long recording is still in sync at the end. ### Steps 1. Keep capture timestamps end to end Never recompute a time from a frame index. Files: `src/capture/segments.py`, `src/assemble.py` 2. Keep audio as its own track Separate clocks, aligned at assembly, not merged at capture. Files: `src/audio.py` 3. Measure drift on a long take A number in the test output, not a feeling while watching. Files: `tests/test_drift.py` ### Done when - [ ] Drift over thirty minutes is measured and under threshold - [ ] The variable frame rate decision is recorded per export - [ ] Audio tracks are separate and independently mutable - [ ] A missing audio device still yields a video with a note ### Watch out - Assuming a constant frame rate is safe because screen capture reports one is how a forty minute tutorial ends up with the mouse click arriving a second before the sound: the capture delivers frames when the screen changes, so the frame count and the wall clock disagree more the longer you record. Keep the timestamps from capture and assemble against them. ## Phase 4 · Trimming without re-encoding everything Cutting the dead air off the front takes seconds and costs no quality. ### Steps 1. Store edits as a list, apply at export The timeline is data and the file is evidence. Files: `src/edits.py` 2. Copy the stream where keyframes allow Re-encode the boundary pieces only, and say when you had to. Files: `src/export/trim.py` 3. Assert the source checksum never changes One test that makes the whole model true. Files: `tests/test_source_untouched.py` ### Done when - [ ] A twenty minute trim completes in seconds - [ ] Source checksums are unchanged after editing - [ ] Exports play in two other players - [ ] Any edit can be undone from the edit list ### Watch out - Re-encoding a twenty minute recording to remove two seconds at the front costs a long wait and a generation of quality every time somebody changes their mind, which they will, four times. Store an edit list, copy the stream where keyframes allow, and re-encode only the pieces at the boundaries. ## Not in this build - Multitrack compositing, transitions and titles. - Stock media libraries. - Hosted upload and sharing. - Camera compositing and keying. ## After v1, if you want it - Cursor emphasis and click highlighting, which is a compositing pass over an existing timeline - Automatic silence detection to suggest cuts, once the edit list has proven itself - A second export preset tuned for a specific destination, with its settings recorded like the first
# Copy to .env and fill in. Never commit .env; this file documents it. # Required. Recordings, segments, edits and exports. DATABASE_PATH=./data/recorder.db # Required. Project folders holding media. Referenced relatively so they can move. PROJECTS_DIR=~/Movies/Recordings # Required. The native helper. Without it there is no capture at all. CAPTURE_HELPER_PATH=./helper/build/CaptureHelper # Required. Assembly, trimming and export. FFMPEG_PATH=/opt/homebrew/bin/ffmpeg # Required. How much of a take a crash can cost. Smaller is safer and slightly busier. SEGMENT_SECONDS=5 # Required. Recording refuses to start below this and stops cleanly if it is reached. MIN_FREE_BYTES=10000000000 # Required. After a successful export only. The cleanup reports what it removed. SEGMENT_RETENTION_DAYS=14
# ScreenFlow · product brief ## Problem ScreenFlow's solo core is compact: build a local Mac screen recording + editing workflow that imports user-owned media, applies the core edit or transformation, previews it, and exports a standard file. A competent builder can reach a useful personal version in one sitting, while the paid product mainly wins on media pipeline, hosting, distribution. ## Product outcome A recorder that asks for permission the supported way, writes every second of a take to disk as it happens, stays in sync after half an hour, trims without re-encoding the whole file, and cleans up after itself instead of filling the disk. ## Target user A builder who needs a maintainable product foundation, not a one-off demo. ## Required capabilities - Python 3.12 - FFmpeg - desktop with sufficient storage ## Explicit non-goals for v1 - Multitrack compositing, transitions and titles. - Stock media libraries. - Hosted upload and sharing. - Camera compositing and keying. - production codecs, rendering speed, and media templates - hosted recording reliability - large-scale rendering - distribution and podcast hosting - professional codecs, effects, and collaboration ## Success criteria - Permission and the system picker are confirmed in use - A killed recording is confirmed recoverable from segments - Drift over thirty minutes is confirmed measured and within threshold - Source checksums are confirmed unchanged after editing - Retention is confirmed to run only after a clean export
# Build brief · ScreenFlow The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a Mac screen recorder and trimmer that cannot lose a take to replace ScreenFlow. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Stack (fixed, do not substitute) - Capture: ScreenCaptureKit through a small native helper, with the system sharing picker · the supported path, and the one that does not put your app on a warnings list - Processing: FFmpeg as a subprocess for assembly, trimming and export · it already understands the containers and the timestamps you are about to meet - Runtime: Python 3.12 with FastAPI serving a local interface · only the capture has to be native, and everything else is easier if it is not - Storage: SQLite for recordings and edits, media inside a project folder · the media is the data and it belongs somewhere a person can move - Editing: An edit list applied at export, sources never modified · a trim you cannot undo destroys the take a second time ### Data model (create this before Phase 1) - recordings(id, started_at, ended_at, source_kind, state, segments_dir, bytes, recovered_from_crash) - segments(recording_id, ordinal, path, start_pts_ms, end_pts_ms, bytes, sealed_at) · written as they arrive and sealed as they close - tracks(recording_id, kind, device_name, sample_rate, channels, first_pts_ms) · screen, microphone and system audio each carry their own clock - projects(id, name, folder, created_at) · media referenced by relative path, so the folder can be moved or copied - edits(project_id, ordinal, recording_id, in_ms, out_ms, track_kind) · an edit list, never a rewritten source - exports(id, project_id, preset, path, settings_json, bytes, started_at, finished_at, exit_code) · the settings that produced the file, kept with it - No operation in this model modifies a source recording, which is the only reason a mistaken trim at midnight is survivable. ### Phase 1 · Capture through the supported path Build: Capture with ScreenCaptureKit from a small native helper. Request screen recording permission before capturing and declare the usage description in the bundle. Use the system content sharing picker to choose the display or window rather than enumerating sources yourself. Trap: Building your own window list and capturing through the older CoreGraphics calls still works today, prompts the person with warnings about software that can collect detailed information about them, and is the path Apple is closing; it also breaks quietly after an update nobody chose. Use ScreenCaptureKit with the system picker, and handle a denied permission as a first class state. Done when: Launching without permission shows the system prompt and an explanatory state, never a black recording. The usage description string is present in the bundle and visible in the system privacy settings entry. Source selection goes through the system picker and the application never enumerates windows itself, verified by review. Denying permission leaves the rest of the application usable for editing existing files. Do not build yet: No crash safety, audio alignment, trimming or export. ### Phase 2 · A take that survives the process dying Build: Write the capture to disk continuously as sealed segments rather than holding it and writing one file at stop. Check free space before starting and stop cleanly with a message when it runs low. On launch, find orphaned segments and offer to assemble them into a recording. Trap: Buffering the take and muxing one file when the person presses stop means a crash, a forced restart or a full disk at minute fifty destroys the entire recording, which is the single thing this kind of tool must never do. Seal a segment every few seconds and make a crashed session recoverable from what is already on disk. Done when: Killing the process mid recording leaves playable segments and the next launch offers to assemble them, tested by killing it. Free space is checked before recording starts and the estimate for the chosen settings is shown. Filling the disk stops the recording with a message and keeps everything captured so far. No code path holds more than a bounded amount of capture in memory, verified by review and by watching memory over a long take. Do not build yet: No audio alignment, trimming or export yet. ### Phase 3 · Picture and sound that stay together Build: Carry the presentation timestamps the capture pipeline gives you rather than assuming a frame rate. Record the microphone as its own track with its own clock and align on timestamps at assembly. Measure drift across a long take and record the measurement. Trap: Assuming a constant frame rate is safe because screen capture reports one is how a forty minute tutorial ends up with the mouse click arriving a second before the sound: the capture delivers frames when the screen changes, so the frame count and the wall clock disagree more the longer you record. Keep the timestamps from capture and assemble against them. Done when: A thirty minute recording with a sharp sound at the start and the end shows drift under a stated threshold at both, measured rather than assumed. Variable frame rate output is either preserved or converted with the choice recorded on the export row. Microphone and system audio are separate tracks that can be muted independently. A take with no audio device available still records video and says which track is missing. Do not build yet: No trimming, presets or project moves yet. ### Phase 4 · Trimming without re-encoding everything Build: Keep an edit list of in and out points per recording. At export, copy the stream where the cut falls on a keyframe and re-encode only the boundary pieces. Preview from the edit list rather than from a rendered file. The source is never modified. Trap: Re-encoding a twenty minute recording to remove two seconds at the front costs a long wait and a generation of quality every time somebody changes their mind, which they will, four times. Store an edit list, copy the stream where keyframes allow, and re-encode only the pieces at the boundaries. Done when: Trimming a twenty minute take completes in seconds rather than minutes, timed in the suite. The source file's checksum is unchanged after any number of edits, asserted. The exported file plays in at least two players other than the one you developed against. An edit can be undone because the edit list is data and the source is intact. Do not build yet: No presets, cleanup or project portability yet. ### Phase 5 · Disk, cleanup and a project you can carry Build: Keep media inside a project folder referenced by relative path, so moving or copying it still opens. Delete segments after a successful export according to a stated retention, and report what was removed. Record the exact settings behind each export preset with the exported file. Trap: Segments accumulating in a temporary directory forever is how a screen recorder quietly consumes two hundred gigabytes, and the person discovers it when the disk is full during the recording they actually needed. Apply a retention after a successful export, report the bytes removed, and show current usage in the interface. Done when: A project folder copied to another path opens with every recording and edit intact, tested. Segments are removed after a successful export according to the retention and the byte count is reported. Current disk usage by project is shown in the interface. Each export row stores the settings used, so the same preset produces the same encoder arguments a month later. Do not build yet: Nothing after this. ### Out of scope (and why) - Multitrack compositing with transitions, titles and animation, which is a full editor and several months rather than a weekend. - Stock media libraries, which are a licensing arrangement rather than a feature and are a standing annual cost in the paid product. - Hosted upload and sharing, which turns a local recorder into storage, bandwidth and an access control model you now operate. - Camera compositing and green screen keying, which need real time processing decisions that conflict with never dropping a frame. ### README must contain - That capture goes through ScreenCaptureKit and the system sharing picker, and which permission and usage description are required - That the take is written as sealed segments continuously, and how a crashed session is recovered on the next launch - That timestamps come from the capture pipeline and are never recomputed from a frame index - That sources are never modified and every edit lives in an edit list applied at export - The retention applied to segments after a successful export, and where disk usage is shown
# Architecture · ScreenFlow ## Stack | Part | Choice | Why | | --- | --- | --- | | Capture | ScreenCaptureKit through a small native helper, with the system sharing picker | the supported path, and the one that does not put your app on a warnings list | | Processing | FFmpeg as a subprocess for assembly, trimming and export | it already understands the containers and the timestamps you are about to meet | | Runtime | Python 3.12 with FastAPI serving a local interface | only the capture has to be native, and everything else is easier if it is not | | Storage | SQLite for recordings and edits, media inside a project folder | the media is the data and it belongs somewhere a person can move | | Editing | An edit list applied at export, sources never modified | a trim you cannot undo destroys the take a second time | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Capture | The native helper, permission and the system picker. | Not swappable for the deprecated capture calls or a self built picker. | | Segments | Continuous sealed writes and crash recovery. | Any segmenting, provided nothing large is held in memory. | | Assembly | Timestamp based alignment across tracks. | Any muxer, provided timestamps come from capture. | | Edits | The edit list and stream copying at export. | Any editor, provided sources are never modified. | | Lifecycle | Disk checks, retention and project portability. | Any policy, provided retention only runs after a clean export. | ## Configuration Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists. - `DATABASE_PATH` · required · Recordings, segments, edits and exports. - `PROJECTS_DIR` · required · Project folders holding media. Referenced relatively so they can move. - `CAPTURE_HELPER_PATH` · required · The native helper. Without it there is no capture at all. - `FFMPEG_PATH` · required · Assembly, trimming and export. - `SEGMENT_SECONDS` · required · How much of a take a crash can cost. Smaller is safer and slightly busier. - `MIN_FREE_BYTES` · required · Recording refuses to start below this and stops cleanly if it is reached. - `SEGMENT_RETENTION_DAYS` · required · After a successful export only. The cleanup reports what it removed. ## Production baseline - Security: least privilege, input validation at every boundary, secret redaction in logs, rate limits on abuse-prone paths, no invented security primitives. - Data: explicit schema and migrations, transactional writes where integrity matters, backup and restore procedures that have been exercised. - Integrations: adapters around third-party providers, idempotent webhook or job processing, bounded retries, timeouts. - Observability: structured logs with request or operation ids, an error-tracking hook, and health and readiness checks where a server exists. - Quality: unit tests for domain rules, integration tests at module boundaries, one end-to-end test of the critical path. ## Decision records For each dependency in the stack table, keep a short note: why it was chosen, its failure mode, and how it is replaced. Do not add infrastructure until a requirement in `PRODUCT.md` justifies it.
# Agent instructions · ScreenFlow product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: ScreenCaptureKit through a small native helper, with the system sharing picker, FFmpeg as a subprocess for assembly, trimming and export, Python 3.12 with FastAPI serving a local interface, SQLite for recordings and edits, media inside a project folder, An edit list applied at export, sources never modified. - Implement milestone by milestone from `MILESTONES.md`; keep each change reviewable and leave the application runnable at every commit. - Treat authentication, payments, encryption, imports, webhooks and destructive actions as high-risk boundaries when present. - Never invent cryptography or silently weaken a requirement to make a check pass. - Put every external service behind an interface with a deterministic fake for tests. - Add migrations and rollback or recovery notes for every persistent data change. - Log useful operational context without credentials, tokens, passwords or personal data. - Update documentation and run every check before completing a milestone. ## Known traps - Building your own window list and capturing through the older CoreGraphics calls still works today, prompts the person with warnings about software that can collect detailed information about them, and is the path Apple is closing; it also breaks quietly after an update nobody chose. Use ScreenCaptureKit with the system picker, and handle a denied permission as a first class state. - Buffering the take and muxing one file when the person presses stop means a crash, a forced restart or a full disk at minute fifty destroys the entire recording, which is the single thing this kind of tool must never do. Seal a segment every few seconds and make a crashed session recoverable from what is already on disk. - Assuming a constant frame rate is safe because screen capture reports one is how a forty minute tutorial ends up with the mouse click arriving a second before the sound: the capture delivers frames when the screen changes, so the frame count and the wall clock disagree more the longer you record. Keep the timestamps from capture and assemble against them. - Re-encoding a twenty minute recording to remove two seconds at the front costs a long wait and a generation of quality every time somebody changes their mind, which they will, four times. Store an edit list, copy the stream where keyframes allow, and re-encode only the pieces at the boundaries. - Segments accumulating in a temporary directory forever is how a screen recorder quietly consumes two hundred gigabytes, and the person discovers it when the disk is full during the recording they actually needed. Apply a retention after a successful export, report the bytes removed, and show current usage in the interface.
# Delivery milestones · ScreenFlow Estimated effort: **weekend** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · Capture through the supported path Recording starts only after permission is granted, through the system's own picker. ### Steps 1. Put capture in a native helper One small process with one job, talking to the rest over a local socket. Files: `helper/Capture.swift`, `src/capture/client.py` 2. Declare the usage description and ask first A missing description string is a silent denial at the worst moment. Files: `helper/Info.plist` 3. Use the system sharing picker Less code, fewer prompts, and the sources the person actually expects. Files: `helper/Picker.swift` ### Done when - [ ] No permission produces a prompt and an explained state - [ ] The usage description appears in system settings - [ ] Sources come from the system picker only - [ ] Editing still works when permission is denied ### Watch out - Building your own window list and capturing through the older CoreGraphics calls still works today, prompts the person with warnings about software that can collect detailed information about them, and is the path Apple is closing; it also breaks quietly after an update nobody chose. Use ScreenCaptureKit with the system picker, and handle a denied permission as a first class state. ## M2 · A take that survives the process dying Whatever was recorded before a crash is still on disk and playable. ### Steps 1. Seal segments as they close A few seconds each; the cost is negligible and the insurance total. Files: `src/capture/segments.py` 2. Recover orphans at launch The recovery path is the feature, so it runs automatically. Files: `src/recover.py`, `tests/test_recover.py` 3. Check free space before and during Refusing to start beats stopping at minute fifty. Files: `src/disk.py` ### Done when - [ ] A killed recording leaves playable segments - [ ] Launch offers to assemble orphaned segments - [ ] Free space is checked and estimated before starting - [ ] Memory stays bounded over a long take ### Watch out - Buffering the take and muxing one file when the person presses stop means a crash, a forced restart or a full disk at minute fifty destroys the entire recording, which is the single thing this kind of tool must never do. Seal a segment every few seconds and make a crashed session recoverable from what is already on disk. ## M3 · Picture and sound that stay together A long recording is still in sync at the end. ### Steps 1. Keep capture timestamps end to end Never recompute a time from a frame index. Files: `src/capture/segments.py`, `src/assemble.py` 2. Keep audio as its own track Separate clocks, aligned at assembly, not merged at capture. Files: `src/audio.py` 3. Measure drift on a long take A number in the test output, not a feeling while watching. Files: `tests/test_drift.py` ### Done when - [ ] Drift over thirty minutes is measured and under threshold - [ ] The variable frame rate decision is recorded per export - [ ] Audio tracks are separate and independently mutable - [ ] A missing audio device still yields a video with a note ### Watch out - Assuming a constant frame rate is safe because screen capture reports one is how a forty minute tutorial ends up with the mouse click arriving a second before the sound: the capture delivers frames when the screen changes, so the frame count and the wall clock disagree more the longer you record. Keep the timestamps from capture and assemble against them. ## M4 · Trimming without re-encoding everything Cutting the dead air off the front takes seconds and costs no quality. ### Steps 1. Store edits as a list, apply at export The timeline is data and the file is evidence. Files: `src/edits.py` 2. Copy the stream where keyframes allow Re-encode the boundary pieces only, and say when you had to. Files: `src/export/trim.py` 3. Assert the source checksum never changes One test that makes the whole model true. Files: `tests/test_source_untouched.py` ### Done when - [ ] A twenty minute trim completes in seconds - [ ] Source checksums are unchanged after editing - [ ] Exports play in two other players - [ ] Any edit can be undone from the edit list ### Watch out - Re-encoding a twenty minute recording to remove two seconds at the front costs a long wait and a generation of quality every time somebody changes their mind, which they will, four times. Store an edit list, copy the stream where keyframes allow, and re-encode only the pieces at the boundaries. ## M5 · Disk, cleanup and a project you can carry (production only) The tool does not fill the disk and a project folder still opens somewhere else. ### Steps 1. Reference media relatively Absolute paths are what stop a project from moving. Files: `src/projects.py` 2. Apply retention after a successful export After, and only after, the export exited cleanly. Files: `src/retention.py` 3. Store preset settings with the export So a file can be reproduced rather than remembered. Files: `src/export/presets.py` ### Done when - [ ] A copied project folder opens intact - [ ] Retention removes segments and reports bytes - [ ] Disk usage per project is visible - [ ] Export settings are stored with each file ### Watch out - Segments accumulating in a temporary directory forever is how a screen recorder quietly consumes two hundred gigabytes, and the person discovers it when the disk is full during the recording they actually needed. Apply a retention after a successful export, report the bytes removed, and show current usage in the interface.
# Operations · ScreenFlow ## Backup Back up project folders, which contain the media and the relative references; the database can be rebuilt from a folder but the footage cannot be re-recorded. A finished export belongs in the backup before the segments are cleaned up. ## Restore Copy a project folder back and open it; the relative references are what make this work from a different path. Confirm one export plays before trusting the restore. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring Free space against the minimum, orphaned segment directories from crashed sessions, and measured drift on the last long recording. Rising orphan counts mean the helper is crashing and nobody has noticed because recovery works. ## Incident checklist The incident is a lost take, and the recovery is the orphan assembly path, which is why it runs on launch rather than waiting to be found in a menu. 1. Contain the issue without destroying evidence or user data. 2. Record the timeline and affected scope. 3. Rotate exposed secrets and revoke compromised sessions or credentials. 4. Restore from a verified backup when needed. 5. Document the root cause, the remediation and the regression test. ## Release gate - [ ] Permission and the system picker are confirmed in use - [ ] A killed recording is confirmed recoverable from segments - [ ] Drift over thirty minutes is confirmed measured and within threshold - [ ] Source checksums are confirmed unchanged after editing - [ ] Retention is confirmed to run only after a clean export ## Launch constraint Do not market omitted ScreenFlow capabilities as implemented. The non-goals in `PRODUCT.md` remain user-visible limitations until they are deliberately delivered.
# Copy to .env and fill in. Never commit .env; this file documents it. # Required. Recordings, segments, edits and exports. DATABASE_PATH=./data/recorder.db # Required. Project folders holding media. Referenced relatively so they can move. PROJECTS_DIR=~/Movies/Recordings # Required. The native helper. Without it there is no capture at all. CAPTURE_HELPER_PATH=./helper/build/CaptureHelper # Required. Assembly, trimming and export. FFMPEG_PATH=/opt/homebrew/bin/ffmpeg # Required. How much of a take a crash can cost. Smaller is safer and slightly busier. SEGMENT_SECONDS=5 # Required. Recording refuses to start below this and stops cleanly if it is reached. MIN_FREE_BYTES=10000000000 # Required. After a successful export only. The cleanup reports what it removed. SEGMENT_RETENTION_DAYS=14
$ choose a build depth, inspect the files, then open the complete pack in your agent
ScreenFlow: People pay to avoid codec failures, upload limits, rendering surprises, lost recordings, and distribution chores.
xproduction codecs, rendering speed, and media templates
xhosted recording reliability
xlarge-scale rendering
xdistribution and podcast hosting
xprofessional codecs, effects, and collaboration
ScreenFlow pricing
| plan | monthly | annual (per mo) | what you get |
|---|---|---|---|
| screenflow | custom | — | Mac screen recorder and multitrack editor |
| screenflow super pak | custom | — | ScreenFlow plus first year of Stock Media Library |
| screenflow super pak+ | custom | — | ScreenFlow, first year of Stock Media Library and Premium Support |
free tierno free tier
billingone-time software purchase; optional stock-media and support components renew annually
hidden costsStock Media Library renews at $99/year; Premium Support renews at $59/year, and future major-version upgrades are separate purchases.
verified 2026-08-14 · source ↗
Vibecode ScreenFlow
Yes. A competent AI coding agent (Claude Code, Codex, Cursor) can build a usable personal ScreenFlow replacement in one session with the prompt on this page. It runs on your own machine or server with no subscription.
How much does ScreenFlow cost?
ScreenFlow's pricing is usage-based or varies by plan · The official pricing URL is recorded, but a stable current amount was not safely recoverable in this pass. Do not publish a number until rechecked..
What do I lose by replacing ScreenFlow?
Honestly: production codecs, rendering speed, and media templates; hosted recording reliability; large-scale rendering; distribution and podcast hosting; professional codecs, effects, and collaboration. If any of those are load-bearing for you, keep paying.
Is there an open-source alternative to ScreenFlow?
Yes: Kdenlive (Open-source non-linear video editor.). Using prior art is also vibecoding; the prompt is for when you want it exactly your way.