feat: render and preview a composition file, with no project to maintain - #1
Merged
Conversation
…rkspace Add packages/fluvie_presenter (barrel, test harness, obers_ui git dep) and the apps/slides shell to the pub workspace, the coverage gate list, and the goldens scope. Seed PROGRESS.md with the build plan and decision log.
…me clock LivePlaybackController maps elapsed wall time to frames (play, pause, exact seek, hold, playRange with a completing future, rate with no-jump rebasing) over the same RenderController capture uses; LivePlayer owns the Ticker, mounts preview mode, and republishes frames through RenderControllerScope. Both export from the barrel.
introspectTimeline(video) walks the declared scene trees (the same walk the media collectors use, now sharing declaredChildren) into registrations, resolves them through buildVideoPlan, and returns scene bounds plus per-element windows and animation spans as plain FrameSpan values, keyed by anchor, widget key, and walked widget instance. CollectibleChildren and the tree walkers join the barrel so consumers can extend the walkable surface.
LiveScenePlayer wraps fluvie's LivePlayer behind one presenter seam: it introspects the scene's absolute bounds and plays exactly that segment, holding the last frame. FluvieSlides carries the minimal config surface and stages the first scene flat. The slides app presents a demo deck, and the fluvie live-playback + introspection API gets its documentation page with compiled snippets.
Stop marks its children as a build step: absent before the step, then mounted on a FrameRebase so the authored entrance plays from the reveal moment (or past it, for the settled state back-navigation lands on) while ambient time keeps flowing. Outside a presentation it is a transparent passthrough, and it implements CollectibleChildren so fluvie renders of the same deck still see the stopped media. fluvie side: LocalMotionScope detaches a subtree from the composition registrar (CompositionRegistrarScope.none), so live content can mount and resolve locally at any point of playback — documented in live-playback.md.
compileSlidePlans walks each scene for Stops (document order, explicit order override, duplicate orders rejected), measures every step's entrance settle from the introspected enter spans (scene-relative, nested stops excluded from their parents), and pre-validates that stopped elements use only locally-resolvable triggers. AnimationIntrospection now carries the declared trigger so consumers can tell composition-level placement apart.
A Riverpod notifier holding (slide, step) plus how the position was reached: forward advances play entrances, backward moves and jumps land on held states. next/back walk the flat position order across slides; jumps clamp to the deck. The compiled plans arrive through slidePlansProvider.
SlideView mounts each scene as its own single-scene composition stretched to the slide horizon on a free-running clock: forward advances reveal stops at the current frame (entrances play from that moment), backs and jumps land on settled states, ambient loops never pause. Slide changes play the authored boundary transition mapped to a presenter-level blend (crossFade, slide, wipe, zoom; flag to cut). FluvieSlides compiles the deck, owns the provider scope, and re-mounts per deck. Goldens pin a slide at step 0, a middle step, and its final step; a byte-comparison test pins back-navigation parity.
PresenterShell letterboxes the slide into any viewport on a flat stage, mounts its own obers_ui theme scope (PresenterTheme carries the tokens plus the stage color), and draws the minimal HUD: a slide counter and a flat progress line over the whole position order, both toggleable. FluvieSlides provides missing ambient scopes so the presenter can be someone's whole runApp. Goldens pin the letterboxing at two viewport aspects.
PresentationShortcuts binds the Keynote and reveal.js muscle-memory set (arrows, Space and Shift+Space, PageUp/PageDown for remotes, Enter, Home and End, digits-then-Enter jumps, F and F5, Esc with jump-buffer precedence, O, S, B and period, W, H) plus tap-to-advance and horizontal swipes, all onto a PresenterHandlers value so tests record calls without pumping the shell. It grabs focus on mount so keys work immediately, on the web too.
…eens FullscreenController picks its platform by conditional import: the browser Fullscreen API on the web, window_manager on desktop behind a testable bridge seam, SystemChrome immersive modes on mobile with tracked state, and an honest no-op elsewhere. BlankScreenNotifier holds the B/W cover over the stage; the position underneath holds while it is up.
PresenterShell binds the input surface to the controller and chrome state: navigation clears the blank screen (then the overview) before it moves, Esc peels overlays before leaving fullscreen, F toggles, digits jump, H hides the HUD. startFullscreen requests fullscreen post-frame, the FluvieSlides flags seed sidebar and notes visibility, and a custom PresenterTheme paints the stage. Goldens pin the black and white covers.
SlidePreviewService queues renders behind a concurrency cap, deduplicates in-flight requests, caches into an LRU with invalidation, notifies when a preview lands, and can pre-generate the whole deck. The pixels come from SlidePreviewFrame (a slide settled on a held clock — the deterministic still) rendered on PreviewRenderHost, a hidden one-at-a-time stage painted beneath the opaque presenter stage and captured through its repaint boundary.
SlideSidebar shows one tile per slide — the cached preview once it lands, a flat placeholder until then — with the current slide highlighted and kept in view. Click jumps to the slide at its base step. T toggles it (N is wired for the notes panel ahead), and the shell now lays the hidden preview host, stage, sidebar, and overlays out in one paint order.
O opens a grid of every slide over a dimmed stage, reusing the sidebar's preview cache so nothing renders twice; picking a slide navigates and closes, Esc just closes. Goldens pin the sidebar (ready and placeholder tiles, current slide marked) and the grid.
SpeakerNotes carries the prose and the glanceable highlight bullets. It renders nothing and takes no space; in a scene it is the slide's default, inside a Stop it belongs to that step. The structural walkers see it in both places.
compileNotes resolves SpeakerNotes into SlideNotes aligned with the compiled plans: scene notes are the default every step inherits; a step note replaces the text (when it has one) and appends its highlights; a nested stop keeps its note for its own later step; several notes in one scope merge in document order. One source of truth for the notes panel and the Phase 6 speaker window.
NotesPanel shows the position's prose on the left and the highlight bullets down the right, follows navigation as steps reveal, and hides behind N or the showNotes flag. FluvieSlides compiles the notes next to the plans and provides both; the shell lays the panel under the stage row. A golden pins the panel with text plus highlights and the empty state.
PresentationSyncChannel carries JSON-safe SyncMessages (position updates and navigation requests) between the presenting and speaker windows: a BroadcastChannel on the web, an in-memory pair in tests, and an honest null where no transport exists. Unknown payloads fail loud with a FormatException so version-skewed peers never desync silently.
SpeakerNextPreview renders what the next input produces — the next step of this slide, or the next slide's base step — as a settled still on a held clock, and says 'End of deck' instead of guessing at the end. SpeakerView lays it out with the notes, the highlight rail, the slide indicators, and a tick-counted elapsed clock that stays testable on the fake test clock.
S opens the speaker view: a popup onto the speaker route on the web, an app-supplied launcher on desktop (the provider is the seam), the in-app notes panel on mobile, and an open-this-URL instruction card whenever no window can open (cleared like every overlay). FluvieSpeaker is the second window's root: same compiled deck, inputs sent as navigation requests when a channel exists, self-navigating without one. A golden pins the speaker layout.
SpeakerSyncBinding glues the presentation scope to the channel: the presenting window applies navigation requests through its controller (so forward moves keep forward semantics), broadcasts every position change, and announces itself for late-joining speakers; the speaker end follows instantly and never echoes. FluvieSlides and FluvieSpeaker now pin their whole presentation state inside their own provider scope, so host-app scopes above cannot capture it and a deck swap resets everything together.
The app boots to a flat obers_ui picker of the bundled tutorial decks or opens a local .fluvie file (file_picker + the fluvie spec loader, with friendly parse errors). Presenting mounts FluvieSlides; on the web the speaker route (#/speaker) resolves the same deck through a localStorage handoff and mounts FluvieSpeaker, so the S popup presents in lockstep.
Plain slides, builds with Stop, speaker notes with highlights, a media-heavy deck (bar chart, code, typewriter), and one full end-to-end talk using most of the tool. Every deck compiles into presentable plans under test, and a golden pins each one's representative slide so the tutorial cannot rot. Chart reveals are spelled out absolutely — the deck rule the docs teach.
Fifteen pages in the package's documentation/ folder: the map, four getting-started pages (five lines running, builds, notes, inputs), five guides (speaker window, sidebar and overview, fullscreen and blanking, web, deploying), three advanced pages (the stepping clock model, custom navigation, performance and previews), and the shortcuts + FAQ reference. Every dart fence is a compiled #docregion from the tutorial decks or the new presenter snippets file, and the melos docs:snippets / docs:lint gates now cover the presenter docs alongside the root docs.
The melos-driven jobs already pick the new workspace members up; this adds the presenter lcov to codecov and a release web build of apps/slides so the just-visit-and-present path cannot silently break.
…ounts them An instant slide change disposed the outgoing clock synchronously, but the old player's ticker still fires once before the rebuild lands (transient frame callbacks run before build), hitting a disposed RenderController. Replaced clocks now hand off to a post-frame callback.
…deck Compile throughput, preview generation, stepping cost, and the preview cache bound, tagged render so the default suite skips it. This harness is what caught the retired-clock race.
…b guide Also spells out in the input-map dartdoc why the key handling is one Focus.onKeyEvent switch instead of Shortcuts and Actions: the digit jump buffer and the Shift+Space chord are stateful across key events.
The input-map mechanism, the absent playback-state sync message (the hold model has no play or pause to share), and index-keyed previews with deck-swap invalidation, plus the perf harness results.
A remounted LivePlayer hands the controller a fresh ticker whose elapsed starts over at zero; handleTick treated that as time before the epoch and seeked back toward frame 0, replaying whatever had already played. A backward elapsed jump now rebases at the current frame.
When the boundary blend settled, the incoming stage moved from inside SlideTransitionBlend to being the root child; the structure change remounted the whole stage, its player restarted the ticker, and the slide replayed its entrance from frame 0 (visible on every crossFade, slide, zoom, or wipe). Each mounted slide now anchors its stage with a GlobalKey so it reparents instead of remounting.
Five small ghost buttons top-right (sidebar, notes, overview, speaker window, fullscreen) reusing the exact handler set the keyboard fires, with the shortcut in the tooltip. The shell now brings its own Overlay and a fallback density scope, so the buttons render standalone too; H hides the strip with the rest of the HUD.
An accent border and a soft tint on hover, plus a click cursor — in the sidebar and the overview grid alike (one tile serves both).
…ullscreen PresenterHandlers grew its one optional intention: onClose (through FluvieSlides). When wired, the strip shows a close button set apart on the right. The strip also now tracks the presenter's fullscreen transitions: four seconds after entering it fades out, hovering its corner brings it back, and five seconds later it hides again — windowed presenting keeps it always on.
…luvie tour The video deck embeds real HTML video players (with controls and sound) as plain slide children; fluvie's Clip belongs to the file renderer and stays silent in live playback, so a talk gets an actual player instead. The mp4s under assets/videos/ are gitignored — drop your own in. The tour deck explains fluvie in fourteen slides, every one carrying speaker notes that describe exactly what is on screen, so the speaker window can be verified slide by slide.
The app hands FluvieSlides an onClose instead of overlaying its own X, and the picker scrolls now that seven decks outgrow a small window.
The birthday clip in the deck was a raw source asset with no audio track at all — no player could make it sound. The deck now embeds the rendered video (fluvie mixed the music in at encode time), and the players carry autoplay + playsInline: Chromium starts them sounding once the page has been interacted with, Firefox wants the site allowed once, and the controls remain the fallback either way.
The spec learns free placement: a reserved id (preserved verbatim, never minted by fluvie — identity policy belongs to the editing tool) and a transform in canvas fractions (x/y anchor point, optional w/h, rotation, anchor). A new Placed element lays the fractions out through one public mapping (Placement.rectFor), buildElement wraps transformed elements in it, and scenes carry a layout marker (canvas or stack) with stack as the default so version 1 files render unchanged. Schema, validation, and the Dart printer move in the same commit.
A first-class top-level editor key: preserved verbatim through the round-trip, deliberately open inside, and stripped from digest() so annotating a document never invalidates render caches — only render-affecting content moves the digest. The conformance corpus pins the lockstep: every fixture must validate clean, re-serialize canonically, keep a stable digest, build a Video, and print to valid Dart.
Skeleton, barrel, analysis and golden config mirroring the presenter's; wired into the coverage gate and the goldens scope. publish_to: none while obers_ui stays a git pin.
The document is the .fluvie JSON itself behind an immutable wrapper: typed mutations deep-copy, revalidate through the spec parser, and return a new document; loading mints stable element ids. Sealed EditorCommands are the only way a deck changes; DocumentHistory rides obers_ui's OiUndoStack with the editor's merge policy in the callback, so a drag's stream of transforms undoes as one step that lands before the drag.
CanvasViewport is the editor's own camera (programmatic zoom commands, wheel-to-point zoom, one shared screen-to-canvas mapping — OiPinchZoom is gesture-only); SlideDeriver memoizes single-scene videos per slide content; EditorCanvas holds the derived slide at its settled frame and matches the presenter's render byte for byte. The slides app picker gains an Edit section: the bundled demo spec or your own .fluvie, read-only for now.
Placed implements CollectibleChildren, so timeline introspection and the media collectors reach placed elements instead of treating the wrapper as a leaf.
Epic 2.1. SceneGeometry resolves every element's shape through the one Placement mapping (rotation-aware, separating-axis marquee, z-ordered point hits) — never the widget tree. SelectionController holds the ids (not undoable, Figma-style); CanvasInteraction wires click / shift-click / empty-drag marquee / Space-pan / hover over the shared camera, and SelectionChrome paints the boxes and marquee in viewport space. The fluvie barrel now exports decodePlacement so the editor shares fluvie's single geometry codec.
A richer worked example: an intro card, one scene per day, and an outro, over a music bed. Each day is annotated with its number and date and groups that day's media — photos and inline video clips together — into a scattered, staggered collage, all data-driven off one day list through a single tile builder. Registered as 'fluvie render days_recap'. Drop real photos and clips into the gitignored assets/days_recap/ and point the day list at them to fill it with your own content.
A live preview mounted no media resolver, so `Clip` and `Background.video` had nothing to paint and fell back to their placeholder. Only images resolved, through the async `Image.asset` fallback. PreviewMediaScope runs the render's own pre-pass (preResolveAll, then preResolveCompositionClips) and mounts the same ImageResolverScope, so preview paints through the capture painters rather than a second, divergent decode path. Desktop's resolver streams clip frames, and that window is filled only by the capture loop, which a preview does not have. Preview therefore passes streamClipFrames: false and decodes every planned frame up front, bounded by maxClipEdge (720 by default) so a full-HD clip does not hold hundreds of megabytes of decoded frames. The proxy moves resolution only: a clip still lays out at its source size, so preview geometry matches the render. Failure is never fatal. No ffmpeg on PATH, no WebCodecs decoder wired, or an unreadable source leaves the scope unmounted and the composition still shows, with each media element on its own fallback, reported through FlutterError in debug so the cause is never silent.
A capture harness had to assemble the render itself: pre-resolve media, extract clip frames, rasterize snapshots, parse captions, analyse reactive audio, build the capture shell, stage the audio mix, then loop the frames. The pieces to do that live under src/, so the harness fluvie init writes could not reach them and hardcoded NoMediaResolver instead. A composition that declared an Image or a Clip threw FluvieRenderException in capture, and the only fix was importing package:fluvie/src/. renderVideo owns that sequence. It takes a Video and derives everything from it, so a caller supplies no registry, no media list, and no geometry. The host contributes only what it alone can: pumpWidget, pumpFrame, setViewSize, and runAsync to escape fake async for real IO. With resolver null it builds the platform resolver through resolverScope, but only when the composition actually declares media, so a media-less render still needs no ffmpeg. buildCaptureShell, ImageResolverScope, SnapshotCaptureScope and MediaRepository all stay private: the public surface is one function. The untrusted size bound moves with it and keeps reading FLUVIE_BLOCK_FILE_SOURCES inside fluvie's own lib/, since a define is substituted into the library that names it and generated harness text cannot carry one. parseAspect/parseQuality/parseExportFormat/parsePosterTime and writeRenderProgress move onto the pipeline surface too, so every harness reads a --dart-define the same way instead of each parsing its own.
…ns on Covers what a caller depends on and cannot see: the declared size wins unless an aspect overrides it (and an aspect re-derives from the longer declared edge, never a lossy VideoSize->Aspect map), frameCountOverride shortens the capture, a media-less composition renders with no resolver and therefore no ffmpeg, and two renders of the same video are byte-identical. The cache report is exercised cold then warm against a temp cache root, so it asserts real hits rather than that the callback fires. assertRenderWithinBounds is tested directly, including the reason it checks each axis before any multiply: a 2^31 x 2^31 canvas wraps its byte product to a small value in 64-bit and would otherwise slip past.
Two harnesses existed and had silently diverged. The gallery's was the
real one (media, audio, clips, snapshots, aspect, quality, poster,
progress) but lived in an example's test tree where nothing could depend
on it. The one `fluvie init` scaffolded was a weaker fork: it hardcoded
NoMediaResolver, read 6 of the 14 dart-defines, and so accepted
--aspect/--quality/--poster and silently ignored them. No test guarded the
drift.
runCaptureHarness is now a thin adapter over fluvie's renderVideo: it
supplies the flutter_test mechanics and the gallery's offline fakes, and
nothing else. The CLI's generated harness calls the same function with the
platform's real resolver, so the two cannot drift again.
CompositionEntry is now {key, video}. Its width, height, fps, frameCount
and mediaSources were all derivable from the Video, and holding a
`Widget Function()` instead of a `Video Function()` is what forced
harness_media to look each entry up by lesson key to find the Video it
could not walk. That coupling is gone.
The demo composition becomes a real Video rather than a raw VideoScope
tree, so every entry renders through one path. Its assertions are
behavioral (pixels differ between frames and repeat), and they still pass.
Two behavior changes, both intended. A track-bearing composition now
stages its audio whether or not its key names a lesson, because the mix is
derived from the Video. And a clip's embedded audio joins the mix, which
the gallery harness never did.
…maintain A Fluvie project was a whole Flutter app: a previewer main.dart, a capture harness, a hand-maintained registry map, platform folders, and a pile of dev dependencies. None of that is the video. A project is now a composition file, an assets/ folder, and a pubspec. `fluvie render ./lib/my_video.dart --out out.mp4` renders it and `fluvie preview ./lib/my_video.dart` runs it live with hot reload. Both generate what they need per invocation, so there is nothing to keep in step. The entry point is a top-level `Video build()`; --entry names another. This reuses the mechanism the Playground already ships rather than inventing one: stage a generated harness that statically imports the user's file, and point captureTestArgs(harnessPath:) at it. Render needs no hidden project at all, because `flutter test` requires only a pubspec in its working directory. Only preview needs a real app, because `flutter run` is hard-gated on the platform directory existing. The CLI owns the pubspec's `flutter: assets:` block and re-derives it every run. Flutter enumerates a declared asset directory with a NON-recursive listSync, so a bare `assets/` bundles only its top level: the first assets/images/ a user creates would be silently missing at runtime with no build error. Enumerating every subdirectory is what "the CLI manages the project" has to mean. The preview app lives in ~/.cache/fluvie/preview/<hash>/, outside the project, because a nested pubspec breaks resolution inside a pub workspace and this repo is one. That costs two things, both handled: the user's dependency_overrides are copied in (pub applies overrides only from a resolution's root, so a contributor path-overriding fluvie would otherwise preview against pub.dev's copy and see different pixels), and assets are symlinked rather than reached by a `../` asset path, which Flutter accepts and then silently writes outside the bundle. A composition must live under lib/. A relative import cannot escape a package, so the preview app can only reach it through a package: URI. Rendering works either way; preview says so plainly rather than emitting an import the compiler cannot resolve. resolveProjectDir now probes for a pubspec depending on fluvie instead of for a committed harness, which no longer exists. The probe is section-aware: fluvie_cli's own `executables: fluvie:` entry would otherwise match and resolve the CLI package as the project. The frame cache is off for a file target unless --cache. The render digest keys on the config and the composition key, never the composition, so an edited file with the same size and frame count would replay stale frames. fluvie_server already worked around this for code renders. Failed capture output now keeps its head as well as its tail. It kept only the last 4 KiB, which discarded the Dart compile error printed first, and "my composition does not compile" is the dominant failure for a file target. The capture subprocess now gets the gate-resolved ffmpeg on PATH. It extracts clip frames itself, so a downloaded ffmpeg encoded fine while every Clip failed to decode. fluvie_server's Playground now stages through the same shared harness, so it no longer needs examples/gallery on the render host, and two verbatim copies of the harness template delete. set_version.sh missed apps/, so a bump left apps/slides pinned and made the whole workspace unresolvable. It also built its package list from directory names, so a leftover artifact directory sharing a pub.dev package's name had that third-party constraint rewritten to our release version. BREAKING: `fluvie init` no longer scaffolds an app, a harness, or a registry, and is no longer interactive (--yes, --path, --render are gone). `fluvie render <key>` still works for a project that kept its harness. See documentation/reference/migration.md.
… no record Every getting-started path taught a workflow that no longer exists: `fluvie init --yes`, a scaffolded capture harness, a registry, and `flutter run` for preview. start-a-project is rewritten around the real one, and render/preview/init references follow. Two claims were wrong rather than stale. The CLI README told users they need FFmpeg on PATH, contradicting installation.md's auto-download, and it never documented `fluvie list` at all. The decision log gets row 29 and, while there, the nine named decisions the code cites (D-CLI, D-CaptureShell, D-Audio, D-Mix, D-Reactive, D-CaptionsRender, D-Snapshot, P12-SNAP, AUDMIX-WIRE) that existed only as code comments and appeared in no markdown. D-CLI is the decision this change supersedes and it had no written record anywhere. The docs sidebar gains rendering-surface and managing-ffmpeg, which were built and reachable by URL but linked from nowhere. Versions go to 0.3.0 across the workspace.
…king
Coverage sat at 93.65%, under the 97% gate, but most of that was an
artifact: init_support_test still named symbols the dead-code cleanup
removed, so it failed to compile and took its whole suite down with it.
Repointing it at compositionSlug alone restored 97.45%.
The genuinely untested behavior was the preview app's wiring, and it is the
part with the least obvious failure mode. The dependency_overrides copy now
has tests for an override from the project's own pubspec, from
pubspec_overrides.yaml, and from a workspace-root ancestor, plus a relative
override path becoming absolute. Without that copy a contributor who
path-overrides fluvie previews against the published package while their
renders use the local one, so the two disagree on pixels with nothing to
point at.
Also covered: a stamp match reuses the app rather than re-running
flutter create (which is not idempotent over an existing tree), a
composition outside lib/ fails with a stated reason instead of an
unresolvable import, the Windows copy fallback for asset symlinks, and the
capture subprocess getting the gate-resolved ffmpeg on PATH.
One of the new tests passed for the wrong reason and is fixed: it asserted
`contains('path: /tmp/fluvie')`, which substring-matched the project's own
path dependency because the temp directory name starts with fluvie, so it
would have passed whether or not the override file was ever read.
Coverage is now 99.42%. The remaining uncovered lines are the
Platform.isWindows/isMacOS host branches and the interactive
Process.start of flutter run.
Welcome to Codecov 🎉Once you merge this PR into your default branch, you're all set! Codecov will compare coverage reports and display results in all future pull requests. Thanks for integrating Codecov - We've got you covered ☂️ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A Fluvie project is now a composition file, an
assets/folder, and apubspec. No app, no
main.dart, no capture harness, no registry, noplatform folders.
fluvie init --name my_clip fluvie preview ./lib/my_clip.dart # live, hot reloads on save fluvie render ./lib/my_clip.dart --out out.mp4Why
Two capture harnesses existed and had silently diverged. The one
fluvie initscaffolded hardcoded
NoMediaResolverand read 6 of the 14 dart-defines, so itaccepted
--aspect/--quality/--posterand ignored them, and any compositiondeclaring an
Imageor aClipthrew in capture. The real harness lived in anexample's test tree where nothing could depend on it.
renderVideoinpackage:fluvieis now the one capture entry. It derivesgeometry, media, audio, captions and reactive audio from the
Videoitself; thehost supplies only
pumpWidget,pumpFrame,setViewSizeandrunAsync.buildCaptureShell,ImageResolverScope,SnapshotCaptureScopeandMediaRepositoryall stay private.Notable
flutter: assets:block and re-derives it per run.Flutter enumerates a declared asset directory non-recursively, so a bare
assets/silently missesassets/images/foo.pngwith no build error.lib/. A relative import cannot escape a package, sothe preview app (which lives outside the project) can only reach one through a
package:URI.~/.cache/fluvie/preview/, outside the project,because a nested pubspec breaks resolution inside a pub workspace. It copies
dependency_overridesacross, since pub applies them only from a resolution'sroot.
Matroska does not store, and vp9-with-alpha decodes through
libvpx-vp9. Clipfps now comes from the declared rate rather than frames-over-duration, which
read 29.86 for a 30fps WebM.
Dart compile error printed first.
fluvie_server's Playground no longer needsexamples/galleryon the host.Breaking
fluvie initno longer scaffolds an app, a harness or a registry, and is nolonger interactive.
fluvie render <key>still works for a project that keptits harness. See
documentation/reference/migration.md.Verification
melos run format,analyze --fatal-infos,lint, all Dart and Fluttersuites, the doc snippet and fence gates, and the website build are green. All 30
goldens pass with no PNG touched. Coverage passes the 97% gate on every package
(fluvie_cli 99.42%).
init->render->previewverified end to end onscratch projects outside any workspace, with pixels inspected.