---
name: groundslate-seo-audit-video
description: Turn an SEO audit into a branded, narrated video. An SEO audit skill (the seo-audit skill from Claude's Marketing plugin, or whichever is installed) does the research, Claude Design builds the scene system from the brand, and GroundSlate renders every scene deterministically, speaks the narration in three local voices over ducked music beds, and files the finished film. Use when the user wants an SEO audit, site review or competitor comparison delivered as a video.
---

# The SEO audit, delivered as a film

You run the whole production. An SEO audit skill is the researcher: use
the seo-audit skill from Claude's Marketing plugin when it is available, or
whichever SEO audit skill is installed.
Claude Design is the art department, GroundSlate is the render engine, the
voice and the library, and the user answers three questions at the start,
approves the look on two style frames, and reviews one draft at the end.
Between those moments, proceed on the defaults
and your own judgement; ask only when an answer would genuinely change the
film, a site behind a login, a brand font that cannot be found, a finding
too sensitive to narrate unchecked.

## Three questions up front

1. The site, and up to a handful of competitors to measure it against.
2. The brand: a Claude Design brand kit, a brand skill you carry, a folder
   of brand assets (logo, fonts, colours), or nothing, in which case you
   derive a clean system from the audited site's own look. The brand may be
   the client's or the agency pitching them; ask whose name goes on it, and
   hold the logo file before you author a scene. When an agency is making
   the film about a client's site, get both logos, the client's for the
   findings and the agency's for the close. The brand lives with you
   and Claude Design, never in GroundSlate; GroundSlate keeps the mechanics
   (fonts in its font library, the logos in its asset store, approved pages
   as named kits).
3. Who watches it: the client's leadership, your own team, or a prospect.
   That sets the tone and how technical the narration gets.

Everything else is defaulted, and stated once when you accept the job:
1920x1080 at 30fps, eight minutes, narrated by three distinct
local voices, music beds across the whole film ducked beneath every spoken
line, filed in a project named for the site and the quarter.

## Preflight, before any promise

- The profile first. get_context, then confirm the profile with the user
  by name before anything is created: the project, the imports and the
  film all land in the active profile, and the user can switch profiles at
  any moment. Once confirmed, pass profile= on every library call, never
  on machine-wide ones (the queue, voice_status, media policy).
- composition_doctor: the render path (Chromium, ffmpeg) proves itself now,
  not at scene twelve. Run the printed fixes if it reports gaps.
- voice_status for the narration engine; relay its fix sentence verbatim if
  weights are missing.
- Fonts: import_fonts on the brand's font folder once, list_fonts for the
  families the library now resolves, then check_fonts on each page once it
  is written. render_html refuses missing fonts rather than substituting, so
  resolve them before authoring.
- Brand media: import_asset every logo the film carries (both of them when
  an agency is presenting a client's site) and any supplied imagery into
  the profile's asset store; get_asset resolves them to paths at build
  time. Files must sit under a granted folder before import.
- The project: search_projects for one named for the site and the quarter,
  and create_project (kind business) when it is not there. Hold its prj_
  id for delivery and pass it on everything that binds, not only the final
  film; commit_to_library attaches to an existing project and refuses an
  unknown one, so the binder exists before anything renders.
- The project is not just where the film lands, it is where everything the
  film is made of and everything it makes lives. Renders go to Scratch
  Outputs, which may be deleted, so anything the production created and did
  not bind is in a folder that does not survive the quarter. Three doors, by
  kind. Media pulled off the web binds as it arrives: pass project_id to
  fetch_asset and it is a member in one call. Media you created during the
  production, a cutout from remove_background, a frame you pulled from a
  clip, binds in two: index_image makes it a catalog row and
  add_media_to_project binds it, and that result carries `unresolved`, which
  is non-empty when a reference matched nothing and was therefore never
  added, so read it rather than assuming. Finished renders go through
  commit_to_library with the project. Final outputs only there: a draft scene
  render is throwaway and rebuilds from its own page, and committing thirty
  of them buries the film they were drafts of.
- import_asset is a different thing and the two are easy to confuse. It puts
  a file in the profile's asset store so a render can call it by name, which
  is reuse across every project rather than membership of this one. A logo
  can want both. Doing one is not doing the other.
- Text has its own door, and this production makes a lot of it. The audit
  findings, the script, the per-scene briefs, the chart data and the
  continuity note are working material: they are not media to bind and not a
  render to commit, and until there was somewhere to put them they lived in
  the conversation and died with it. write_project_file keeps them in the
  project's own folder, under an area prefix (`notes/`, and the areas
  list_project_files reports back). Ask the one question the tool asks: will
  you need this file if the conversation ends right now? The audit you spent
  an hour researching answers yes. Write it before you design a scene from
  it, and the next session opens with read_project_file instead of running
  the research again.
- project_manifest before you plan the cut, and again when a render cannot
  find its footage. It copies nothing and it answers with per-drive online
  status, so the report is "the Sony drive is unplugged and holds twelve of
  these clips" rather than twelve paths the user has to interpret. Collecting
  a project into an export bundle is the user's own action in the app, on the
  project screen; the manifest is the whole of what an agent may do here and
  it is the half that matters before a render.

## The audit is yours; the pages are GroundSlate's

Run the SEO audit skill (the Marketing plugin's seo-audit when available,
otherwise the one installed) against the site and competitors: keywords, on-page,
technical, content gaps, backlinks, the full sweep. Harvest the sites
while you are there: a finding about a page shows that page on screen,
not a graphic standing in for it. Target at least sixty graphics assets
pulled from the pages of the site and its competitors, page screenshots,
hero shots, product photography, logos, icons, textures, all saved under
a granted folder. Two doors bring them in, and which one you use
depends on where the file came from. A file that already exists on the web,
a logo, a press image, product photography, an illustration, comes in
through fetch_asset with its URL and the project's id: it downloads,
indexes, becomes a searchable clip and joins the binder in one call, and the
source receipt records where it came from. Never route a found file through fetch_media, which is for media you
generated at a provider and claims a generator that does not exist for a
photograph somebody else took. Screenshots you captured yourself are local
files, so those go through index_folder as before. Bind everything to the
project with add_media_to_project, and import_asset the ones scenes will
reference by name, the logos and the background plates. GroundSlate captures pages itself: `capture_page` for a still and
`record_page` for footage, WebKit on your Mac, cookie banners refused at
capture time. Prefer a real capture of the page over a graphic that stands in
for it, and prefer `record_page(scroll=True)` over a still you then have to
animate. It signs in to nothing on its own, so a page behind a login needs
`open_browser_window` and the user, and capture answers to their site policy
which only they can change.

Leave fetch_asset's `recognize` off, which is its default. A site's news
page is full of photographs of real people, and enrolling the client's
staff or a competitor's executives as identities in this profile's People
catalog is not something an audit should do on its way past. Fill in what
you actually know on the receipt, the page you found it on, what the page
called it, the credit and the licence, and leave the rest empty rather than
inventing it.

The harvest is the film's art department, and it is spent, not stored.
Every asset you capture earns its way onto the screen: as the evidence in
a scene, as the plate a background is built from, as a mark, an icon or a
texture. Every scene carries captured imagery; a flat panel or an invented
graphic standing in for a page you already have is the harvest wasted, and
it is the difference between a film about the site and a film of it. Keep
the tally as you author, the asset, the scene it lands in and the moment it
appears, and account for it at the draft. An asset that never reaches the
screen is either placed or it was not worth capturing, and you say which.

Then distill. A film carries fewer numbers than a report, so choose the ones
that move a decision:

- One headline verdict the cold open can land in a sentence.
- A ranked handful of findings per area, each with the number, the evidence
  and what it costs the business.
- Quick wins separated from strategic work, because that split is the plan.
- Competitor deltas stated as gaps to close, not tables.

## The film: a scene system, not a slideshow

Cold open with the verdict. One scene per finding area. At the midpoint
the verdict returns, restated as the gap the plan will close, timed to a
bed change so the film audibly turns toward the answer; eight minutes
needs a second hook. Quick wins as a rapid sequence. The plan. A close
that names the next review.

Have Claude Design build the scene system from the brand: a title scene, a
stat scene, chart scenes, an evidence scene with a screenshot panel, a
list scene. Consistency of palette, type and logo treatment is what makes
it read as designed rather than generated; inside that system, vary
everything else so the film never settles:

- One statement per scene, set large. The scene's argument is a single short
  line at a size you could read from the back of a room, and it is the only
  thing on screen the viewer is asked to read. Cut it until it fits that
  size; a line that will not cut is two scenes.
- The film never sets prose small. Small type is only ever verbatim
  evidence: a filename, a tag, a heading lifted off the page, a column
  pulled straight from the source. Evidence is looked at rather than read,
  which is why it can be dense. Seven identical filenames stacked in
  monospace make their point as a shape, before a word of it is read, so set
  it aligned and repeating with the part that matters marked, and the
  density becomes the argument. A sentence set small is the failure this
  rule exists to stop, and a stack of them behind bullet points is a slide.
- If a line is worth saying but not worth setting large, the narration says
  it and the screen stays quiet. Most small type on a scene is the voice's
  job leaking onto the picture.
- Fill the frame. A layout that leaves half the frame empty is type set too
  small, not a frame too big.
- Motion is how a thing arrives and leaves, never a state it lives in. Type
  enters, settles, and is then completely still until it goes. Nothing
  breathes, floats, bobs or drifts while a viewer is reading it.
- The layout is fixed before the first frame. Every element the scene will
  ever show has its position from t=0, and an arrival is opacity and a short
  move into a slot that was already there. Nothing already on screen moves
  because something new appeared. A list that creeps upward as each line
  lands is moving the lines a viewer has just started to read, and it is the
  most common way a scene comes out feeling like it is drifting.
- One thing moves at a time, and it is whatever the narration is on. While
  the statement lands, the evidence is still; while a row of evidence is
  marked, the statement is still.
- Small type does not move at all. It cuts or fades in place, then holds.
  The still-type rule governs type; pictures are the layer that moves.
- Composition varies between scenes, never inside one. A full-bleed word
  here, a corner-anchored stat there, an oversized numeral bleeding off the
  edge: that is one decision per scene, made before the first frame and
  held, so no two neighbouring scenes compose the same and neither one
  wanders.
- Imagery has many jobs and a blurred plate behind type is the weakest of
  them. Every scene decides how its captures appear, and the treatments are
  a vocabulary to spend rather than one move repeated: the page as the
  subject, full frame and sharp, scrolling or pushing into the headline
  under discussion; a capture held in a browser or device frame, angled and
  sliding; two competitors' pages side by side with the difference between
  them visible; a grid of many captures at once where the repetition is the
  finding, fifty-six identically named images shown as fifty-six thumbnails
  rather than described; a detail crop magnified out of a page with the rest
  ghosted around it; a product shot or a mark cut off its background and
  composed into the frame; a capture revealed through the type, or the type
  knocked out of it. The blurred plate is the fallback, not the default. The cutout is
  remove_background: Apple's subject lift, on device and near instant, and it
  writes a transparent PNG at the source's own resolution with the subject
  still in its original position, so it drops back over the untouched
  original as a layer. Stills only, a video is refused rather than looped.
  Check `foreground_fraction` on the way past, because near 1.0 means almost
  nothing came off and near 0.0 means it kept a speck, and an image with no
  subject writes nothing rather than handing you an empty cutout. The edge
  carries a soft halo at very large sizes, so a cutout is right for a
  composite, a layout or an overlay and wrong for a silhouette held full
  frame.
- Vary the treatment the way you vary the chart forms, and keep a record of
  which scene used which. No treatment carries two scenes running, and a
  film where every capture ended up behind the type as wallpaper has thrown
  away the harvest even though every asset technically appeared.
- When the narration names a company, that company is on screen while it is
  named, as itself: their page, their mark, their product shot, sharp and
  legible and in the foreground. A viewer hearing about Puma should be
  looking at Puma. A blurred texture of somebody's photography behind a line
  of type is not that, and it is the difference between showing the field
  and decorating a slide about it.
- Type is still, pictures move. That is the whole motion rule stated once.
  When a capture is the subject, the frame moves over it as a function of t,
  a push into the detail, a scroll down the page, a pull back to reveal
  where it sat, a rack from one crop to the next, and the type holds
  perfectly still against it. When the statement is the subject, the type
  arrives and the picture holds. A plate behind type drifts slowly, and that
  slow ground under still type is what reads as expensive, while type doing
  the drifting is what reads as a template.
- A scene whose ground is flat colour is a scene that ignored the capture
  sitting in the asset store.
- Treat the captures rather than pasting them. list_effects is a catalog of
  named looks for one clip, grade, stock, print, glass, bloom and camera
  moves, each row carrying its own knobs and its cost, and apply_effect bakes
  the chosen one onto a file. A capture graded into the brand's palette and
  sitting under a glass or print treatment reads as photographed; the same
  capture dropped in raw reads as a screenshot in a deck. Call list_effects
  first and pass only the knobs on the row you picked: a knob that is not on
  that row is refused, not ignored. Hold one look across the film the way a
  film holds one stock, and do not spend a different treatment per scene.
  There is a second catalog, list_ai_effects, that runs neural models per
  frame for depth and subject work. It is genuinely different and genuinely
  slow, it needs models the user installs themselves, and each row carries a
  measured cost per frame: quote that cost before you apply one to anything
  long, and default to the ffmpeg looks.
- Mix the chart forms, and let the shape of the data pick the form rather
  than habit. A share of a whole is a pie or a donut drawing itself round.
  A ranking is bars, but a ranking that changed is a slope or a bump chart
  between two dates. A count is a waffle grid filling in, or one huge
  numeral. A spread of competitors is a scatter or a positioning map.
  Coverage is a treemap or a grid filling cell by cell until the hole is
  obvious. A funnel is a funnel, a hierarchy is a tree, a change over time
  is a line drawing itself in. Where none of the standard forms fits the
  finding, invent the graphic the finding needs; a bespoke picture of one
  specific problem is the most memorable thing a film like this can carry,
  and two bar charts in a row is the report again.
- Build charts out of the harvest wherever the data has a face. Competitor
  marks are the points on a positioning map, favicons are the units in a
  count, real thumbnails are the cells of a coverage grid, and a page
  screenshot is the thing a callout points at. A chart made of the actual
  brands beats the same chart made of coloured rectangles, and it spends the
  assets in the same move.
- Evidence scenes show the real page, and the page obeys the motion rules
  too. Scenes are HTML, so the capture drifts, zooms to the problem or
  scrolls the page as a function of t, and the mark draws itself on as the
  evidence is named. A screenshot parked in a static panel is the one place
  the film goes dead. A captured page is evidence, so its own
  text is not the film's type and does not answer to the rules above. The
  framing carries the load instead: hold it on the line that matters, so
  what a viewer reads is the claim and not the whole page.
- Nothing stays on screen for more than ten seconds. The clock is per
  element and it starts when that element appears, not when the scene does,
  so a headline that arrived at t=2 is due at t=12 even though the scene
  runs to twenty. Any element still there at ten, a chart, a screenshot, a
  line of type, gets replaced, reframed or moved on, and a scene that has
  nothing to change at ten seconds is two scenes.
- And nothing leaves before it can be read. A line of type stays up at
  least as long as it takes to say it aloud, and nothing a viewer needs
  exits mid-word. The ceiling keeps the film moving; this floor keeps it
  legible.

Pace the film as a curve, not a metronome. The cold open is the fastest
sequence in the film, evidence scenes get room to breathe, the quick wins
come rapid-fire, and no two adjacent scenes run the same length.

Scenes carry designed content only: the number, the evidence, the words
being narrated. No numbering of any kind, no slide or scene numbers, no
"finding three of seven", no progress dots, no section labels, no footer
with the site name on every scene, no caption that names the chart it sits
over. That furniture is a deck labeling itself; this film's structure lives
in the narration and the design.

No em-dashes in anything the film sets. Not in a headline, a stat label, a
strap, a chart label or a card. A comma, a colon, or two lines, and most
lines that reach for a dash wanted to be two lines anyway. At headline size
an em-dash is a bar of dead space across the frame, which is why the rule
bites harder here than on a page. Verbatim evidence is the exception and is
never edited: a captured page, a quoted tagline or a line lifted off a
competitor keeps whatever punctuation its author used, because the moment
you tidy it, it stops being a quotation.

The film is an argument, so its data is honest. Every number the narration
speaks is the number the screen shows at that moment, and both trace back
to the audit; a chart's scale never flatters or exaggerates a gap. The
first inflated bar a viewer catches costs the whole argument.

Every scene is a self-contained HTML page obeying the render contract:
window.__duration (or data-duration) set to the scene's planned length, and
window.__seek(t) that places the visual state exactly at time t. The picture
must be a pure function of the timeline: no clocks, no live randomness, no
fetches, data baked into the page. Charts draw as functions of t, so a
counter at t=3.0 shows the same digits on every render, and the kinetic
type obeys the same law: every entrance, settle and exit is computed from
t, never from a running animation clock. That determinism is what makes
drafts honest and re-renders safe.

## Three voices hold the clock

- Cast three voices from list_voices before writing a line: a lead who
  opens the film, lands the verdict and closes with the plan; a second who
  reads the findings and the evidence; a third for the competitor deltas
  and the quick wins. Audition with preview_voice; the contrast between
  them should be audible inside one sentence.
- Budget the script before writing it. The local voices read at
  documentary pace, roughly 140 to 180 words a minute, so eight minutes
  holds around 1,100 to 1,300 words across the whole film. Weigh the
  scenes inside that budget; a script that ignores it is a twelve minute
  film discovered after the narration is synthesized.
- Write the script scene by scene, marked for who speaks, as spoken prose:
  full sentences with subjects and verbs, the way a documentary narrator
  talks, never labels or fragments read aloud.
- The narration never reads the screen. The screen carries the number; the
  voice tells the story around it, who is losing what, and what changes
  when it is fixed, with the data as the proof. Say the number, then what
  it costs: "Four of the ten money pages load in over five seconds. Slow
  pages are the first thing a buyer leaves." A spoken number always
  matches the one on screen; a mismatch is a bug, in the scene or in the
  script.
- Speak it with synthesize_speech at temperature 0, one file per scene in
  that scene's voice.
- Then time the picture to the words. transcribe each narration file with
  word timestamps and use them as the scene's cue sheet: the number lands
  on screen as it is spoken, the mark on the screenshot appears when the
  evidence is named, the next line of type waits for its words. Set each
  scene's duration from the timestamps, the last word plus a breath, then
  stretched to the bed's next cut point so the scene changes on the music. This
  is where the ten second rule is won or lost: the words decide the length,
  so a line of narration that runs sixteen seconds has just bought a
  sixteen second scene, and that scene owes the viewer a change of picture before
  ten. Plan the change with the cue sheet rather than discovering it at the
  render.
- Fix the fit before you render. Too loose, dead air after a word while the
  screen waits, trim the silence out of the file with render_ffmpeg and
  re-transcribe. Too tight, a visual with no room to land before the next
  line, rewrite the line shorter or give the scene the extra second. The
  cue sheet is only done when nothing on screen leads or trails its words.

## The soundtrack is one mix

- Eight minutes is too long for one track, so the film carries four or
  five music beds, each owning a stretch of it: one for the cold open, one
  or two across the finding areas, a faster one under the quick wins, a
  closer for the plan. Secure them when you accept the job, not at the
  mix. `generate_music` writes them here, one brief per bed, instrumental,
  with the seed on the record so a bed the user liked comes back exactly.
  Tracks the user supplies work too, and your own music connection is
  there when you carry one. If `generate_music` reports that the model is
  not downloaded on your Mac, relay that sentence and stop: the download
  is the user's to start in the app.
- The beds are the film's other clock. Run audio_beats on each one before
  timing a single scene and cut on its suggested cut points, the strongest
  downbeats, never on every beat: scene changes, transition moves and the
  type's biggest hits all land on the grid of whichever bed is under them.
  Change beds at area boundaries, on a cut point of the outgoing track, so
  the music turning over reads as the film changing chapter. With no beds,
  the words alone hold the clock.
- Scenes render silent, so the soundtrack is one continuous mix: narration
  laid at each scene's offset, each bed laid over its stretch and ducked
  under every spoken line with mix_audio, restored in the gaps, the argv
  run through render_ffmpeg, and the finished track handed to the join as
  render_sequence's audio_path, one track laid across every seam.
- A transition overlaps its two scenes rather than adding time, so the film
  runs shorter than the scenes do by the length of the moves. Take the total
  off render_sequence with plan_only, which renders nothing, and build the
  mix to that number rather than to the cue sheet.

## Render, join, deliver

- Style frames before the film. Render the cold open and one evidence
  scene at draft and show them before authoring the rest: a wrong
  direction costs two scenes, not thirty. Read them against the rules while they
  are cheap to fix: one statement set large, no prose set small, a fixed
  layout with nothing creeping, nothing sitting past ten seconds, and
  captured imagery in every layer. A scene
  system that breaks those at the style frame breaks them thirty times over.
- Render every scene with render_html at draft while iterating: identical
  frames and timing, only compression differs, so the draft is the honest
  review copy.
- Where a run of scenes belongs together, author it as one page rather than
  as separate files joined afterwards. author_film takes the scenes and the
  seams between them and writes the whole self-contained page in one call:
  the stage, the absolutely positioned scenes, the transition kit wiring,
  `window.__duration`, `window.__seek` and `window.__READY__`. None of that
  is a creative decision and every hand-written line of it is a chance to
  break the seek contract, which is the failure that renders cleanly and
  looks right until somebody counts the frames. The seams inside that page
  are the free ones. Use it for the quick wins sequence and any run that
  shares a ground; keep render_sequence for the joins between the areas of
  the film, where a rendered move is the point.
- Join the scenes with render_sequence: the scene files in order, and one
  transition entry per seam, N-1 of them or it refuses. Give the seams inside
  a sequence "cut" and the area boundaries a move that matches the film's
  energy, so the moves keep meaning. One call encodes every frame once,
  however many scenes there are, and quality is a tier there too, so join at
  draft while the film is still moving.
- Stay in the light tier. Thirteen of the thirty-one moves are cost: light and
  the other eighteen run a per-pixel kernel at five to thirty seconds a join,
  paid again on every draft pass. The whole dynamic family is heavy, so spend
  one at a couple of real boundaries rather than at every one, and read cost
  off list_transitions rather than off a name. There is a second library that
  runs inside the page and costs nothing to join, browsable with
  list_transitions(engine="html") and vendored with
  get_kit("transition kit"); a scene turn is free there and heavy here.
- Sample frames off the joined film and look at them before you call it done.
  A path cannot show you a substituted typeface or a finding sitting on the
  wrong scene; a frame can.
- The full draft is the big checkpoint: show the user the draft film with
  the script. Findings change wording here, scenes reorder here, and
  nothing has been spent on final quality yet. Bring the accounting with
  it: the assets that reached the screen and where, the ones that did not,
  and any scene that had to set prose small, with what the narration could
  have said instead. List the treatment and the graphic form each scene used
  while you are there, and the longest any single element stayed up: two columns of that list is where a film that thought
  it was varied turns out to be one move repeated. A rule nobody checked at
  the draft is a preference.
- On approval: re-render scenes at high, re-join at high with the mix as
  audio_path,
  commit_to_library into the project, name_video with the site and the
  quarter, and confirm with list_project_outputs. What that list should
  show is the whole body of work, the captures, the assets the production
  made and the film, not one file with everything it was built from left
  loose in Scratch Outputs.
- save_kit the approved scene pages as a kit under the
  client's name, and add_project_note a continuity note beside it: the
  three voice ids, the beds, and the quarter's headline numbers. Next
  quarter's audit is the same three questions rebuilt in the same look and
  the same voices, with a delta chart drawn from recorded numbers rather
  than memory.

## GroundSlate renders the web itself

This is the art department for anything that lives on a site, and it beats
sending a human for screenshots.

- `capture_page` is a still, rendered by WebKit on your Mac. `device` is
  desktop (1440x900) or mobile (390x844 with a phone user agent, so the site
  serves its real mobile layout). `full_page` for the whole scroll, `scale`
  for retina. For a 9:16 still use `device="mobile", width=360, height=640,
  scale=3`, a true phone viewport upscaled rather than a squeezed desktop.
- `record_page` is VIDEO of a page, and `scroll=True` walks it top to bottom
  across the recording. That is what turns a static site into footage. Record
  at 24fps: a scrolling page repaints continuously and holds 7 percent of its
  frames at 24 against 16 to 26 percent at 30, measured.
- `consent="reject"` dismisses a cookie banner by REFUSING it, at capture
  time, so the banner is never in your plate. There is no accept option and
  there should not be one.
- `session="shared"` uses the durable signed in session from a window the user
  opened, so a page behind a login can be captured. `open_browser_window` puts
  a real window on their screen with your `reason` written above it, for a
  login or a verification step. Nothing here solves a challenge for you and
  nothing should try.
- Capture is off until the user turns it on, and then answers to their site
  policy. `browser_policy` reads it. `request_browser_site` ASKS, with your
  reason, and the user decides in GroundSlate. You cannot grant it, and asking
  twice increments a counter rather than creating a second request.

Use it for the real artefact rather than a recreation. A film about a website
should contain that website, moving, with its own type and its own
photography, not your drawing of it.

## Show them, every round

A round of work ends when the human has seen it, not when you believe it is
good. `request_review(source, ask, agent=, profile=)` puts the file in front
of them with a timeline they can comment on, regions they can draw and a
verdict bar. Do this at the end of every round, unprompted. Describing a
render in prose is the thing the tool exists to replace.

- ALWAYS pass an `ask`. With one, the window carries Approve and Request
  changes. WITHOUT one it is a SHOW: it still accepts notes and offers no way
  to submit them, so a human can write thirty notes into a window that cannot
  send them. Use a show only to put an asset in front of somebody with nothing
  to decide.
- ALWAYS pass `agent=`. Unnamed, you are recorded as the person: nothing is
  reused, `withdraw_review` refuses everything including your own, and the
  app's raise skips your review entirely.
- READ `placed` BACK AND SAY WHAT IT SAYS. `opened` means the window is coming
  up, `waiting` means the app is running but not frontmost so its Dock icon is
  bouncing and the review is on the Dashboard, `saved` means the app is shut
  and it will be there next launch. Telling somebody to take a look when their
  app is closed is exactly what that field exists to prevent.
- Poll `review_status` for the verdict and the notes. Park on `review_wait`
  only when the verdict genuinely blocks the next step; it is a blocking wait
  on a human.
- One live ask per file. `withdraw_review` takes back a superseded question of
  your own. It refuses a review that has been decided, one carrying comments,
  and anybody else's, which is correct: never delete somebody's writing.

Every note carries an anchor: `t`, sometimes `t_end`, and a `region` with a
normalised box plus a `where` in words. Work from the anchor rather than your
reading of the sentence. Map each note to the scene that owns that moment and
write the map into the project with `write_project_file` before you start, so
the round survives you.

Then report per note, one block each, DONE or NOT DONE or PARTIAL, with the
evidence. A claim is not evidence. "Added kinetic text" is a claim; "probe
returned clean, rendered 562 frames, the three lines land at 2.94, 4.10 and
6.84 inside the safe area" is evidence. If a note cannot be done, say NOT DONE
and why, and never report a note done that you did not verify. Ask for the
next round the same way.

## Everything happens inside GroundSlate

The tools run on the user's Mac, in the background, on a queue. They are
faster than you and they cost none of your context. Every minute you spend
hand rolling a filtergraph is a minute the machine was idle and your context
was being spent on something nobody can read afterwards. Reach for the tool
first, every time.

- Authored pages: `write_composition` writes your HTML to a real path the
  renderers can open, and vendors sibling files beside it. Before it existed
  an agent authoring scenes had to reach for a filesystem bridge outside
  GroundSlate. Do not go back to that. `preview_html` to check it,
  `render_html` to bake it.
- Assembly: `render_sequence` takes N clips and N-1 seams in one call and
  encodes every frame exactly once. `author_film` expresses scenes and seams
  as one page composition. Both enforce the identity below; a hand rolled
  concat does not.
- Ducking: `mix_audio` IS the broadcast sidechain. Pass voice and music, get
  `ffmpeg_argv` back, hand it to `render_ffmpeg`. Note the one parameter that
  is not in decibels: `threshold` is a linear amplitude because that is what
  the compressor takes, and a dB value is refused rather than misread.
- Effects: `list_effects` and `apply_effect` for the graded looks,
  `list_ai_effects` with `preview_ai_effect` before `apply_ai_effect`. The AI
  effects run on models the user installs in the app, Models then Effects;
  until they are there `apply_ai_effect` answers `effect_not_ready`, so say so
  and stay with the graded looks.
- Transitions: `list_transitions` catalogs both engines, the ffmpeg one and
  the page one (`engine="html"`), and `transition_motion` gives you a move's
  actual curve and what it drives. Choose on the MEASURED `seam_seconds`, not
  the `cost` tier, which is wrong in both directions at the edges.
- The web: see the capture section. GroundSlate renders pages itself now.

And do not bake early. A film that exists only as a finished mp4 has to be
rebuilt in full for a one word change. Keep the edit as data for as long as
you can, and bake the deliverable last.

## The project is the source of truth, and the test is a second agent

Everything this production learns lives in the project, not in the
conversation and not in a scratch directory. The test is concrete: a
DIFFERENT agent, tomorrow, given only the profile and the project id, must be
able to pick the work up and continue it. If they cannot, the production
failed, however good the film is.

- `create_project` early and hold the `prj_` id. Pass it to everything that
  binds, not only the final render.
- Media from the web binds as it arrives: `fetch_asset` with `project_id`.
  Media you made binds in two steps, `index_image` then
  `add_media_to_project`, whose result carries `unresolved` for references
  that matched nothing. Read it rather than assuming.
- Text is working material and it has its own door. The brief, the script,
  the per scene cue tables, the note map from each review round, the voice
  ids, the measurements you took and the ones that surprised you: all of it
  goes in with `write_project_file` under an area prefix. `add_project_note`
  records a decision AND the reason for it, which is the part a later agent
  cannot reconstruct.
- `project_manifest`, `list_project_files` and `read_project_file` are how
  that agent resumes. Write for them.
- `commit_to_library` the finals only, with the project. Renders live in
  Scratch Outputs, which may be emptied.

Anything you leave only in the conversation is gone when the conversation
ends. That includes the reason you chose a voice, the cue table you measured,
and which of the client's notes you decided not to act on and why.

## Timing is frames, and frames are the currency

- A transition OVERLAPS its two shots, it never adds time. A cut runs
  `sum(clip frames) - sum(seam frames)`. Do the arithmetic in frames.
- Declare durations that land on whole frames at your fps. 18.15 seconds at
  30fps is 544.5 frames and floors to 544, one short of what you meant, and
  nothing tells you.
- ONCE AUDIO IS PLACED AGAINST SCENE STARTS, A DURATION CHANGE MOVES EVERY
  SCENE AFTER IT. Freeze scene durations before you place a single cue, or
  accept that you will re-derive every cue downstream by hand.
- Verify frame counts with `ffprobe` on the delivered file, not from the job
  record. Jobs can vanish from a shared queue without writing a file and
  without a terminal state.
- Anything that passes through `minterpolate` needs an explicit constant frame
  rate normalisation afterwards. Its timestamps do not land on the frame grid,
  and the next step drops frames without saying so.
- Keep the whole film one pixel format and one colour range. A single
  full range clip among limited range ones is a black level jump at that
  splice, and a frame count will never show it.

## Motion has a floor, and a still is not a scene

Both of these are measured, not aesthetic.

- A drift below about 0.3 px per frame emits IDENTICAL frames. A ground
  crushed to near black cannot register motion whatever it travels. Clear
  8 px per second of visible travel on a ground you can actually see, and
  check the render for repeated frames rather than assuming.
- Text over a moving background is not a moving scene. The plate drifts, so a
  freeze check passes, and it still reads as a slide with video wallpaper.
  The test: IF YOU PAUSED THE PLATE, WOULD THE SCENE STILL BE MOVING? Measure
  the type and graphic layers on their own, separately from the background,
  and know which of your scenes have grounds that are not tagged as plates,
  because those flatter the number.
- Generated video has its own failure modes worth knowing before you spend an
  hour of GPU on them. A ping pong loop built by reverse and concat returns to
  its first frame on a fixed period and reads as a loop however slow it is.
  Bright emissive elements, neon, filaments, blown highlights, smear and plume
  frame to frame and look like smoke. Naming camera equipment in a prompt gets
  the equipment rendered into the shot. And a generated plate almost always
  needs a blur before it stops reading as generated.
- Prefer authoring the camera move yourself over asking the model for one. A
  scale driven from `t` is exact, adjustable without regenerating, and cannot
  smear. A model's camera move is whatever it decided.

## An instrument that cannot fail is not evidence

- Prove the red before you trust the green. Run your check against something
  you know is broken and confirm it reports the fault. A checker that skips
  elements with children will pass every headline in the film.
- Whisper is a reconstructive model. Its language model restores the plural it
  expects, so a transcript will tell you a word was spoken when the audio does
  not contain it. Never use a transcript as evidence for an inflection, a word
  ending, or anything acoustic. Measure the acoustics.
- Beware a default that is indistinguishable from a real answer. It passes
  every test and is wrong exactly where it matters.
- When two instruments disagree, the one that can be wrong in the direction of
  the common case is the suspect. Look at the thing itself; looking settles
  cases that measurement cannot.
- Measure the population the claim is about, and confirm on a different axis
  than the one that produced the claim.

## Report like a producer

- While the audit and renders run in the background (job_events), keep
  building; never make the user wait on a progress bar you could have
  worked through.
- Say what resolved and what did not after every batch step; unresolved and
  found false are answers.
- End with where the film is, what it is called, and the three findings that
  matter most, in one sentence each.
