Skip to content

Technology

| Concern | Choice | Notes | | Language and toolkit | Dart 3.13, Flutter 3.47 (stable) | One code base for the five platforms of the product (below); the same code runs in Chrome for previews and in widget tests. | | Comps preparation | filmopen_comps, crypto ^3.0.6, http ^1.6.0, image ^4.3.0 and archive ^4.3.0 | Pure Dart JSON/bundle preparation and authenticated publishing. Crypto hashes immutable originals and previews; HTTP transports bounded requests with cancellation; image reads an original’s size and creates separate JPEG previews; archive builds the tests’ PNG fixtures. | | Film exchange | filmopen_exchange, filmopen_format and crypto ^3.0.6 | Pure Dart selection, projection and JSON export. SHA-256 identifies projected sources and deterministic document content. | | State | flutter_riverpod 3 | Notifier / AsyncNotifier / Provider / FutureProvider; no code generation. | | Assistant Sessions’ record | filmopen_state and crypto ^3.0.6 | SHA-256 of a version’s canonical JSON, so the session’s record says whether a file is still as the session left it (§7.4). | | Field assistant UI | filmopen_assistant_ui | Imports Flutter, Riverpod, filmopen_state, filmopen_ui, filmopen_l10n and filmopen_dictation. The forms, browser and root import its public barrel. It owns no keys, HTTP client or project writer. | | Synopsis voice conversation | OpenAI WebRTC on Windows, Android and Linux; Grok WebSocket with record and Windows waveOut; native DeviceCredentialStore; shared_preferences_windows ^2.4.1 as a dev dependency | Every build on Windows, Android and Linux carries it, with no compile-time switch, and uses normal preferences, library, logs, Usage and saved provider keys, with WebRTC audio for OpenAI and PCM16 mono 24 kHz playback for the retained Windows socket path. Only the agent build’s assistant fixture redirects writable state and supplies an absolute Windows preference filename, with memory credentials, and it installs no voice service. OpenAI signaling disables turn detection until the host configures the session. The configured WebRTC session uses far-field input noise reduction and semantic turn detection at low eagerness; automatic replies and interruption remain enabled. PCM routes retain their server VAD configuration. Its turn log, on both routes, names fixed event kinds with the transport, local elapsed/readiness/mute/playback state, the item’s identity, the person’s words and the model’s answer where the event carries them, two booleans identifying the effective noise/turn configuration and a provider error whole; audio is never logged, and the key never travels on the data channel. This transport is the host’s own, outside the canonical plug-in API. | | Native WebRTC audio | flutter_webrtc 1.6.2+hotfix.3 (MIT), declared only by filmopen_platform | The platform peer owns Windows, Android and Linux audio, an optional ordered text data channel and device cleanup behind provider-neutral interfaces. Signaling and provider event interpretation remain host responsibilities. Android declares ACCESS_NETWORK_STATE and CHANGE_NETWORK_STATE for native route monitoring, in addition to internet and audio permissions. Android obtains microphone permission before constructing the native peer, because native recording may initialize when the answer is installed. Permission checking does not start a recorder, and a refused or canceled check cannot create a peer. Before the first Android peer, the SDK selects software echo cancellation and noise suppression. Android capture translates processing flags to the native optional-constraint format while preserving the selected input; Windows and Linux constraints and routing use the desktop SDK path. Other platforms report unavailable. Linux capture, audible playback and cleanup require physical desktop acceptance. | | Screenplay documents | xml ^7.0.1 and archive ^4.3.0 in filmopen_fdx | Final Draft, Word and Fade In XML; ZIP members selected in memory by content, with declared sizes bounded before inflation. RTF uses a small tokenizer without another dependency. No external entities, network access or source execution. | | Video and sound on Android | AndroidX Media3 1.11.1 (Apache-2.0) and ffi ^2.1.0 (BSD-3-Clause) in filmopen_engine_android | The Android engine (§19.16): Media3’s player on the platform’s own MediaCodec decoders and AudioTrack output; no third-party codec. Its notice is registered on the licence page at start (licence.media3.*). | | Positioned PDF text | pdfium_dart 0.3.1 (MIT) and ffi ^2.1.4 (BSD-3-Clause) in filmopen_fdx | PDFium chromium/7811 supplies character origins, loose boxes and generated-character flags; pages are read serially without rendering. Coordinates are points from the left and baselines from the top; rows group within a quarter median glyph height, runs split over one and a half median loose-box widths. A misread apostrophe between an ASCII letter and lowercase ASCII letter enables document-wide repair of eight Mac Roman punctuation values; other values and already-correct curly punctuation stay as extracted. PDFium-marked hyphens become dashes in place, without dehyphenation. The native asset build hook downloads and bundles a target binary from bblanchon/pdfium-binaries at build time. The binary’s upstream BSD-3-Clause and Apache-2.0 notices are separate from the wrapper’s MIT licence. | | Folder picker | file_selector | Desktop only; hidden on the web. filmopen_sharing uses it for the Sharing card’s folder picker. Also Settings → Plugins’ Install from file… (filmopen_plugins_ui), a zip; and the timeline dock’s save location for Export MP4… and Export OTIO… (filmopen_timeline_ui, getSaveLocation), off the web and mobile. | | Preferences | shared_preferences (SharedPreferencesWithCache, SharedPreferencesAsync) | Theme, language, viewer handle, sidebar width, the docked assistant’s width, verbose logs, recent projects, library folder, the account-signed-in hint, each provider key’s state (providerKeys.<owner>/<slot>, §6.7), and a recording’s receipts, the voice box and the consent’s mark (support.receipts, support.voice, support.consentSeen, §6.8). It is a runtime dependency of filmopen_keys. See §6.4 for where the file lives. The Provider keys and plug-in host tests use shared_preferences_platform_interface’s in-memory store, a dev dependency in both packages. | | Application folders | path_provider | The app-support folder (preferences, shared library) and, on mobile, the log folder. Desktop log folders are computed from the environment (§6.4). | | File browser and URL schemes | url_launcher | The mobile “show in folder” branch (desktop uses the OS command directly), and opening the browser for account sign-in and the account pages on filmopen.ai. In filmopen_plugin_host, the platform-configured OAuth sign-in browser. In filmopen_keys, a platform’s guide and keys page, and in filmopen_plugins_ui, a plug-in’s guide (§9.4). In filmopen_sharing, the new-project dialog’s Top up, which opens the account page. In filmopen_browser, a posted comp’s Open in browser (§9.3). | | Account | supabase_flutter | The filmopen.ai account (§6.6): sign-in with Google, Discord, GitHub or an e-mailed code (PKCE), the session. Public client configuration only; no service key anywhere in the app. Declared by filmopen_account, and a dev dependency of the root package too: test/account_profile_test.dart builds a stand-in client over a MockClient to drive a sign-in callback through the link door without a socket. The app itself reaches Supabase only through filmopen_account. | | Short identifiers | crypto ^3.0.6 | In filmopen_store: AppLog.digestOf, the first 12 hexadecimal characters of a value’s SHA-256 — a stable identifier made from a value, such as an operation’s id; a log line writes the value itself (§13); never for security. | | Library warnings | logging | The Supabase libraries report a failed callback exchange or a persist error through package:logging only; account_auth.dart forwards their warnings into the app log as account.libraryWarning, codes only. | | JSON tree | voo_json_tree 0.1 (MIT) | Only in filmopen_forms: the tree the JSON editor opens on (§9.3). The package draws and navigates the tree; editing a value is the editor’s own (a tap on a value asks for it and writes it into the document the text shows), since the package reports no edits. Its toolbar is hidden (its text is English only). It brings flutter_bloc, bloc, provider, equatable, voo_responsive and voo_tokens into the build as transitive dependencies; nothing in FilmOpen uses them. | | Prose editor | filmopen_text_ui | A reusable editor and controller for canonical mentions and literal fields (§9.8). Depends only on Flutter, filmopen_format, filmopen_ui and filmopen_l10n; shown text and canonical source are kept separate. | | Website and documentation site | filmopen_state/consts.dart | kFilmOpenDomain is dev.filmopen.ai, the development site (since 18 September 2026), in a build that has development mode (kDevelopmentModeAvailable), and filmopen.ai in a release build or one built with FILMOPEN_ENFORCE_GUARDS; kFilmOpenSiteUrl is its https:// root. Every link to the website (the specification in About, the account page) and every sentence naming it (the account’s popup and the sign-in dialog) reads it. The documentation site is not the website: kFilmOpenDocsDomain is docs.dev.filmopen.ai in a build that has development mode, where a page is published before the released docs have it, and docs.filmopen.ai otherwise, and kFilmOpenDocsUrl its root. The guides are its pages — a platform’s How to get a key (<docs>/<guide>/, the route its platform file gives, ended with the slash the site ends a page with; guideAddress, which says no slash of the route’s own twice and whose host is the documentation site’s whatever the route holds), See the platforms (<docs>/docs/platforms/) and the account’s help (<docs>/docs/guides/account/) — since the website serves none of them: on 18 September 2026 it answered /docs/… with its home page, and its development host did not answer at all. Sign-in itself goes to the Supabase project (FILMOPEN_AUTH_URL), which is not this domain. | | Brand marks | flutter_svg | The provider marks on the sign-in dialog’s buttons (assets/providers/, see its README for sources); the FilmOpen logo itself is a PNG (assets/branding/). | | Credentials | flutter_secure_storage | The account session and the PKCE verifier on desktop (§6.4), declared by filmopen_account; and a user’s provider keys (§6.7), declared by filmopen_platform, whose DeviceCredentialStore is the only code that calls it for them. DPAPI on Windows, the Keychain on Apple platforms, libsecret on Linux. | | Key checks | http 1.6 | In filmopen_keys, a runtime dependency: the one check request Save makes to the address a platform file names (§6.7), over https to one of its hosts, following no redirect, given up after 15 seconds, its answer’s text read within that limit and logged. Its MockClient stands in for every platform in the tests. The other packages that send a request of their own declare it too (the rows Device key, Google Drive, Media insertion, Plug-in requests, Picker pictures and Sending a recording), and the application itself, whose comp poster (§6.17) owns the client filmopen_comps publishes through; elsewhere http is a dev dependency, for in-process server stand-ins — in filmopen_render_ui the MockClient that answers add-media‘s addresses through the importer’s debugMediaHttpClient. | | Test clocks and preferences | fake_async ^1.3.1 (dev: filmopen_dictation, filmopen_platform), shared_preferences_platform_interface ^2.4.1 (dev: filmopen_keys, filmopen_state, filmopen_support) | A dictation’s timers and the microphone’s frames are driven by a fake clock in their tests (§9.6), and the Provider keys tests and the support package’s read the in-memory preferences shared_preferences documents for tests. Neither ships in a build. | | Microphone | record 7.1 | Only in filmopen_platform: dictation’s audio as 24 kHz mono PCM16 (§9.6), started only on a gesture. BSD-3-Clause; on Windows through record_windows over Media Foundation; the Linux implementation links only GTK and runs parecord while recording, which the Linux agent build never does, since it records from a fake. | | Live transcription | web_socket_channel 3.0, stream_channel 2.1 | In filmopen_dictation, — web_socket_channel only there, stream_channel in the MCP server too (below): both speech services’ live transcription, OpenAI’s and xAI’s, over a WebSocket whose handshake follows no redirect (§9.6), carried as a StreamChannel so that the tests drive a session through a StreamChannelController without a socket. | | Deep links | app_links | The browser’s sign-in result arriving as ai.filmopen.desktop://auth/callback — through filmopen_platform’s IncomingLinks door (§6.6), the one door on every platform; on Windows and Linux also single-instance forwarding (a second launch hands its command line to the running instance and exits). Declared directly because the Windows runner links the plugin by name. | | Localisation | flutter_localizations, intl 0.20, Flutter gen-l10n | ARB files in packages/filmopen_l10n/lib/l10n/, one per language; generated code committed; flutter gen-l10n runs inside that package. | | Timeline engine | MLT v7.42.0 (LGPL-2.1-or-later) and FFmpeg 7.1.5 (LGPL-2.1-or-later), built per machine by scripts/build-mlt-linux.sh / build-mlt-windows.ps1; SDL2 2.32 (zlib); on Linux, for the subtitles filter, libass 0.17.4 (ISC) with FreeType 2.14.2 (FTL), HarfBuzz 12.3.2 (MIT), FriBidi 1.0.16 (LGPL-2.1-or-later), Fontconfig 2.17.1 (MIT), libpng 1.6.59 (PNG Reference Library License) and zlib 1.3.1 (zlib), built from their releases and shipped; on Windows MSYS2’s libass 0.17.5 (ISC) with its DLL closure — FreeType 2.15.0, HarfBuzz 14.6.0, FriBidi 1.0.17, Fontconfig 2.18.3, libpng 1.6.59, libunibreak 7.0, Brotli 1.2.0, bzip2 1.0.8, Expat 2.9.0, GLib 2.90.1, libintl, PCRE2 10.49 and Graphite2 1.3.15 — shipped beside the executable; ffi ^2.1.0 (BSD-3-Clause) in filmopen_engine_mlt | A scene plays and exports through MLT on Linux and Windows (§19.16), over a C shim reached through dart:ffi and a pixel-buffer texture; GPL and nonfree are off and refused by the build; the libraries ship dynamically linked in the application’s folder, their notices in third_party/mlt-engine/. The web and mobile use the fake. | | Timeline engine, the second | libmpv: mpv 0.41.0 (LGPL-2.1-or-later) with libplacebo and its own LGPL FFmpeg, built from source per machine by scripts/build-mpv-linux.sh / build-mpv-windows.ps1; media_kit 1.2.6, media_kit_libs_linux 1.2.1 and, vendored in packages/filmopen_engine_mpv/third_party/, media_kit_video 2.0.1 and media_kit_libs_windows_video 1.0.11 (MIT) | Chosen at build time (--engine mpv, FILMOPEN_ENGINE=mpv; §19.16), the default on no platform and not shipped; a build carries libmpv only when it names it; its notices in third_party/mpv-engine/. | | Android | the Android runner (android/: Kotlin, the Gradle Kotlin DSL, the Android Gradle plugin the template pins), the debug keystore | The application id is ai.filmopen.app (the code’s namespace stays ai.filmopen.filmopen), the label FilmOpen, the minimum SDK Flutter’s default (24), the compile and target SDK Flutter’s default too (36 — the emulator’s Android 17 is API 37, a preview, and a preview is no target), INTERNET declared for every build; the merged manifest also carries RECORD_AUDIO, which the record plugin brings for dictation and Android asks the person for at first use. flutter_deeplinking_enabled is false in the manifest: FilmOpen reads its links itself through the IncomingLinks door (§6.6), and Flutter would otherwise push every callback as an unknown route and put its URL in a Flutter error. The build-time keys reach Gradle as they reach Dart — Flutter’s dart-defines project property, decoded in app/build.gradle.kts — so the Google redirect scheme of the Android OAuth client is a manifest placeholder filled from the keys folder, and inert in a build without the client. The launcher icons are derived by scripts/make_icons.py (an adaptive icon over the dark ground). Every build is signed by the debug keystore; a release keystore belongs in ../filmopen-keys/android/. scripts/run-android.* and scripts/build-android.* pass the keys as the Windows wrappers do | | Build-time keys | Flutter’s --dart-define-from-file, String.fromEnvironment | A secret a build needs — today the Google OAuth client, tomorrow Apple’s signing material — lives in one folder outside the checkout and reaches the Dart code as a compile-time constant (docs/FilmOpen-Dev-Keys.md). scripts/dev-keys.sh finds the folder (FILMOPEN_KEYS, else filmopen-keys beside the repository, so every shares one), the run-/build-windows wrappers pass the flag, and the agent build’s launcher passes it too. A build without it is not broken: every value is empty and whatever needed one says so — which is the state every check and every contributor without the keys builds in, and a check pins it (DriveClientConfig.current.isConfigured is false in a test run, so a key put back into the source fails the suite). Three lines keep it out of git: the folder, .gitignore, and scripts/check-no-keys.sh, which reads what is staged and prints the file and line but never the value | | Stock plug-ins | scripts/stage_plugins.py (Python’s standard library), git archive | The app repository carries no plug-in: the plug-ins repository, a checkout (FILMOPEN_PLUGINS, else filmopen-plugins beside this repository), is a build input. The script reads its packages at a ref (--ref, else FILMOPEN_PLUGINS_REF, else origin/dev) — every top-level folder holding its own manifest — into plugins/stock.json, git-ignored, with the ref and the commit; the wrappers, the gate and scripts/test_all.* run it first, and without the checkout it says what to clone | | Build stamp | --dart-define FILMOPEN_BUILD, FILMOPEN_COMMIT, FILMOPEN_BUILT_AT; filmopen_state/build_stamp.dart | What a build says it is (§6.6a): CI’s run number and commit (dev and local when unset, kAppBuild and kAppCommit in lib/consts.dart) and the moment it was built, an instant in UTC to the second, which every script that builds or runs the app passes — run-windows, build-windows, run-android and build-android in their .cmd, .ps1 and .sh forms (the .cmd asks PowerShell for the time, as a command, which the execution policy allows), agent-run.sh, and the agent layer’s launcher. A build made without a script — a bare flutter run, the web preview — has no moment, and its stamp says so by leaving it out: Flutter has no way to put a build’s moment into Dart but a define. | | Device key | pointycastle ^4.0.0 (MIT), http ^1.6.0 | In filmopen_device (§6.6b), the only package with pointycastle: http for the registration’s one request, following no redirect, given up after 15 seconds, answered in the tests by a MockClient stand-in for the website’s route; pointycastle for the device’s P-256 key — the point arithmetic that makes the public half from the private scalar, ECDSA with SHA-256 and RFC 6979’s deterministic nonce, and SHA-256 itself. Pure Dart, and already in the workspace through another package’s dependencies before it was declared here. A key made inside the Secure Enclave or the Android Keystore would need native code instead (§16). | | Google Drive | http, crypto, flutter_secure_storage, url_launcher | filmopen_drive (§6.10) provides sign-in, the HTTP client and the resumable sharing target; the application reaches it through filmopen_sharing, with no root import or direct dependency on filmopen_drive. The root directly depends on filmopen_sync, owns its service and supplies Sharing’s target resolver; Sharing constructs the lazy account and passes the application’s incoming-link stream for Google redirects without reading credentials at startup. Every code exchange and every token refresh is an explicit HTTP request, omitting client_secret where the client has none (Android’s). FilmOpen builds its consent address with offline access, PKCE and state, handles desktop loopback or caller-supplied Android redirects, and keeps tokens in the credential store. The package has no media store, folder preferences or cache. | | FilmOpen Cloud sync | filmopen_cloud, filmopen_format, filmopen_sync, filmopen_store, http ^1.2.2; crypto ^3.0.6 as a dev dependency, for its tests alone | Pure Dart bearer-only Cloud client and whole-transfer sync target. The account callback and domain are supplied by filmopen_sharing; the client performs no sign-in, refresh or replay. All dependencies are already resolved by the workspace. | | Media insertion and thumbnails | image ^4.3.0 (MIT), crypto ^3.0.6, convert ^3.1.1, http ^1.2.2 | image, convert and http belong to filmopen_insertion (§6.13): thumbnail decoding, encoding and stock pictures; streamed SHA-256 measurement with crypto; and URL input fetched through an injectable client, with MockClient in tests. Insertion also uses crypto for binding fingerprints and replay checks. filmopen_sync (§6.14) uses crypto for document MD5 comparison with a target, never for security. filmopen_media depends only on filmopen_format at runtime and has no hashing or cache dependency. | | Dropping files | desktop_drop ^0.8.4 (Apache-2.0), with the dbus (MPL-2.0) and universal_platform (MIT) it brings; dbus, which reads a Linux sandbox’s portal, is compiled into every build but the web’s | Only in filmopen_insertion_ui (§9.3), behind file_drop.dart in threes: files dragged from the system onto a place’s add tile, through the platform’s own drag and drop on Windows, Linux, macOS and Android. Its Dart side only listens, and only once a place is drawn; its native side registers the window as a drop target when the application starts. On Linux the runner then puts the list of files (text/uri-list) first among what the window asks a source for (linux/runner/my_application.cc): the package asks first for the document portal’s transfer key, which a host file manager such as Dolphin offers too and the portal can refuse, leaving the drop with no file; the key is still taken from a source that offers no list, such as a sandboxed app. The package asks only whether a drop lies in the tile’s box, so the seam asks a hit test as well: a drop under a dialog, a menu or a drawer, or on a page hidden behind another, reaches nothing. The web build takes no drop (file_drop_stub.dart), since a browser’s drop names no file of this machine; the package’s web half still catches the page’s drags from start, and settleFileDrops, called once in main, answers its messages with nothing, so a file dragged over the web page is ignored quietly. Text or a link dropped there still throws inside that half. | | Job presentation | filmopen_jobs_ui, Flutter Material widgets | The Activity and Share pages (§9.7) draw public JobViews from insertion and sync, and Activity’s Files the sync’s public SyncStatus, with LinearProgressIndicator and wrapped action buttons. It sends JobActions to the owning service and reads no journal; it adds no third-party dependency. | | Issue recorder images | image ^4.3.0 (MIT) | Pure Dart JPEG encoding of the issue recorder’s screenshots (§6.8, as JPEG, since Flutter paints only PNG or raw pixels), and packages/filmopen_recorder/bin/compare_shots.dart, which replay uses to compare a person’s picture with an agent’s. Declared by filmopen_recorder and by filmopen_insertion for the thumbnailer (moved from filmopen_media, the row above). | | Sending a recording | archive ^4.3.0 (MIT), crypto ^3.0.6, http ^1.6.0 | In filmopen_support (§6.8): archive writes a recording’s zip in memory, on another isolate, each entry at one fixed time so that the same folder gives the same bytes; crypto its SHA-256, which the ticket announces; http the ticket from the website and the upload to a support box — over https, following no redirect, each request abortable at its limit (thirty seconds for the website’s answer, a minute without progress for an upload, whose body is pulled in pieces so that progress is seen). MockClient.streaming stands in for the website and the boxes in its tests; no test reaches a socket. Its words are the app’s (§10); intl writes the megabytes of a report’s size. | | Packages | a pub workspace (Dart 3.6+) | workspace: in the root pubspec.yaml, resolution: workspace in each of the packages under packages/, one pubspec.lock at the root. A package imports only what its own pubspec declares: that is the boundary (§4.1). Every package that imports Flutter says so; the format, the store and the test support import none. | | Plug-in engine | QuickJS-NG v0.16.2 (MIT, vendored), hooks ^2.0.0, code_assets ^1.1.0, native_toolchain_c ^0.19.0, logging ^1.3.0, ffi ^2.1.4 | Only in filmopen_js: QuickJS-NG’s release amalgamation and FilmOpen’s C shim, compiled by the package’s build hook (hook/build.dart, native_toolchain_c’s CBuilder) for whichever platform is built — flutter build, flutter test and dart test all run it — and bound with @Native through ffi’s UTF-8 helpers. The hook’s tooling runs at build time and ships nothing; the engine is compiled into the application. Chosen over a Flutter FFI plugin with CMake, whose library exists under neither flutter test nor dart test. | | Plug-in hashes | crypto ^3.0.6 | In filmopen_plugins and filmopen_plugin_host: the SHA-256 of each stock file the app installs into the library (plugins/.stock.json) and, of every file of a plug-in’s folder for its consent (§6.11). The host also uses SHA-256 for stable job/chain terminal Usage ids. filmopen_render_ui uses crypto for SHA-256 of each composite source’s canonical JSON, including its open draft; the pure render package retains caller-supplied hashes without a digest dependency. | | Plug-in requests | http ^1.6.0, http_parser ^4.1.2 (BSD-3-Clause) | In filmopen_plugin_host, http_parser supplies the explicit SDP and JSON multipart media types for direct OpenAI WebRTC signaling. ctx.http’s one door, with the credential added there, following no redirect off the platform’s hosts (§6.11); a MockClient stands in for every platform in its tests. | | Picker pictures | filmopen_picker, http ^1.6.0; fake_async ^1.3.1 (dev) | Imports Flutter, filmopen_state for the catalog seam and filmopen_store for logging. Pictures load on demand from package assets or HTTPS with a 10-second timeout. Encoded bounds and signatures are checked before decoding; HTTP bodies are buffered first. Tests use MockClient and fake time without sockets. | | Plug-in archives | archive ^4.3.0 (MIT) | In filmopen_plugins: reading a plug-in’s zip for Install from file… (§6.11), in memory, every entry checked before anything is written. Pure Dart. filmopen_support writes a zip with it too (Sending a recording). | | Tests of the pure packages | test | filmopen_format, filmopen_store and filmopen_test_support depend on no Flutter at all; the first two have tests of their own, which run on dart test. | | Agent transport | flutter_driver, flutter_test (in the SDK) | Flutter’s driver extension, installed only by the agent entrypoint test_driver/agent_main.dart (§4.3) through packages/filmopen_automation, a dev dependency of the app: nothing under lib/ or in a product package imports it, and no release build reaches it. flutter_test, which the driver’s binding is built on, for its test messenger, where the agent build installs the clipboard asks (§4.2). | | Agent build’s links | url_launcher_platform_interface (a dev dependency of the application, BSD-3-Clause, flutter.dev) | Only test_driver/agent_modes.dart’s WithheldLinks: the agent build’s door for a link outside the app, which opens none and logs the link’s host alone (§4.3). The product reaches links through url_launcher. | | Agent server | dart_mcp 0.5, stream_channel, vm_service 15 | Only in packages/filmopen_mcp, the command-line MCP server that drives a running app over the driver (§4.3); it reads its own command line, with no argument library, and makes the driver’s VM service connection itself with vm_service, the client flutter_driver uses. The app carries no MCP code. | | Lints | flutter_lints 6 | flutter analyze at the root covers every package. | | Tests | flutter_test | Unit, fixture and widget tests, one suite per package, run together by scripts/test_all.sh (§12) |

A PDF that PDFium cannot open, including an encrypted or damaged file, raises PdfOpenException with reason pdf_open_failed and PDFium’s named error code. The caller’s event list retains the open and failure records. Textless pages are reported as counts; recognition is separate from text extraction.

The native library’s release licence and component notices are retained verbatim in third_party/pdfium/LICENSE and shown under PDFium on the application’s open-source licence page. Android packaging excludes libpdfium.so: PDF import is desktop only.

The platforms of the product are Windows, Linux, Android, macOS and iOS. Platforms enabled: windows; linux (linux/ GTK embedder), which is also the agent layer’s runner (launch(platform: linux) under Xvfb); android (a device or the emulator, driven by the agent layer over adb); and web, the debug build for previews. iOS and macOS are not configured yet; the code has branches for them (paths, file reveal) that compile but have not run. Android is configured and runs — a runner and a device, the app installed and driven — but has no native timeline engine yet (scenes play on the fake, §19), no release signing and no store listing, and a phone’s projects live in the app’s own folder.

flutter pub get # resolves the whole workspace: one lockfile at the root
flutter analyze # the root and every package
flutter test # the application's own tests (test/); a package's run inside it
scripts/test_all.sh # every suite: dart test in the pure packages, flutter test in the others and at the root (test_all.ps1 on PowerShell)
python scripts/check_catalogue.py # the model catalogue (Project Specification §14.6–§14.9) and the pages in docs/platforms
python3 scripts/gen_plugin_api.py # the plug-in types and reference from the interface definition; --check runs in test_all
cd packages/filmopen_l10n && flutter gen-l10n # after editing an ARB file; commit the generated files
flutter run -d windows
flutter run -d chrome
flutter run -d web-server --web-port 8642 --web-hostname localhost # headless preview
flutter run -d windows -t test_driver/agent_main.dart # the agent build (§4.3): the real host, then agent.json
dart run packages/filmopen_mcp/bin/filmopen_mcp.dart serve # the MCP server over stdio; --help lists the one-shot commands
flutter build windows --release # build\windows\x64\runner\Release\filmopen.exe

The Windows toolchain needs Visual Studio Build Tools with the C++ desktop workload; flutter doctor must show Visual Studio – develop Windows apps as OK.

Application sync ownership is in filmopen_state, which declares filmopen_sync and uses its service, environment and rules contracts. No third-party dependency is introduced.