Files
filmcraft/CLAUDE.md
T
Dylan M. Taylor c5c1693989 Use chrono instead of hand-rolled UTC conversion (#2662)
* Use chrono for UTC/calendar conversion instead of hand-rolled copies

Replaces the hand-rolled civil_from_days / days_from_civil copies (app log
timestamp, Media Browser date column, auto-save file-name stamp, MXF header
timestamp, golden-image attribution date) with chrono as a workspace
dependency. The existing chrono entry on apps/filmcraft (clock feature) was
lifted to the workspace. Output formats are unchanged and pinned by tests.
AGENTS.md now says time goes through chrono.

Follow-up to storytold/cadcraft#1785

* Never panic on a clock outside chrono's range; keep 5-digit years unprefixed

chrono's From<SystemTime> unwraps internally, so a clock past year 262143
panicked. logging::timestamp and golden::today now read the duration since the
epoch and go through the fallible DateTime::from_timestamp, reading an
out-of-range or pre-epoch clock as the epoch as the old saturating code did.
timestamp, today and media_browser::format_date write the year with {:04}
rather than %Y, which prefixes a '+' past year 9999.

Tests cover the far-future and year-10000 cases for timestamp and format_date.

* Don't build an unrepresentable SystemTime in the far-future test

UNIX_EPOCH + Duration::from_secs(u64::MAX / 2) overflows Windows'
SystemTime and panicked before timestamp() ran, failing the Windows
test job. Build it with checked_add and only assert where it exists.

* time: preserve out-of-range years instead of the epoch (FredApps review)

`DateTime::from_timestamp(..).unwrap_or_default()` changed the existing output:
inputs the old hand-rolled civil-from-days converted (e.g. `10_000_000_000_000`
s → 318857-05-20 17:46:40) collapsed to `1970-01-01 00:00:00`. Restore the old
conversion domain with a cycle-based helper: reduce by whole 400-year Gregorian
cycles (146_097 days), convert the in-range remainder with chrono, and carry the
year back as i128 (saturating to the output type). Only the year leaves chrono's
range; month, day and time of day are always exact.

Sites (all UTC):
- apps/filmcraft logging.rs `timestamp` (SystemTime, u64 secs): pre-1970 still
  clamps to the epoch; a far-future clock keeps its year. u64::MAX/2 s →
  "292277026596-12-04T15:30:07.000Z", 10e12 s → "318857-05-20T17:46:40.000Z"
  (was "1970-01-01..."). SystemTime cases built with a checked add (Windows-safe).
- crates/engine media_browser.rs `format_date` (u64): 10e12 s → "318857-05-20
  17:46", u64::MAX → "584554051223-11-09 07:00" (was "1970-01-01 00:00").
- crates/format autosave.rs `civil` (i64 + i32 offset): keeps years, e.g.
  i64::MAX → 292277026596-12-04 15:30:07, i64::MIN → -292277022657-01-27. The
  separate `unix + offset_secs` overflow guard (checked_add) is kept: only an
  overflowing shift (old code wrapped it to garbage) reads as the epoch.
- crates/mxf from_unix (i64): keeps month/day/time, clamps only `year` to the
  u16 field as before. 10e12 s → year 65535, May 20 17:46:40; -10e12 s → year 0,
  Aug 13 06:13:20.
- crates/testkit golden.rs `today` (u64): same helper; pinned via `utc_parts`.

chrono stays the only calendar logic (NaiveDate::from_num_days_from_ce_opt on the
in-cycle remainder). No new chrono features; still default-features = false, std.

* Name the 400-year range extension as the one exception to the time rule

The rule said never to hand-roll calendar math, but the range fix
(shifting by whole 400-year Gregorian cycles so dates past chrono's year
range keep their old output) is cycle arithmetic, so the rule and the code
disagreed. The rule now names that exception: the library still computes
month, day and time, and only whole cycles are carried in the year.

* Say the library computes month and day; time-of-day is plain arithmetic

Narrows the range-extension wording: the date library computes the
calendar fields, and splitting seconds into hours, minutes and seconds
is ordinary arithmetic, which is what the code does.
2026-10-23 11:18:37 -04:00

6.1 KiB
Raw Blame History

FilmCraft — instructions for agents

FilmCraft is a clean-room, open-source, pure-Rust non-linear video editor targeting Adobe Premiere Pro parity (and beyond). Native on macOS, Windows, Linux; web via WASM. Sibling of ../photocraft (Photoshop), ../pdfcraft (Acrobat) and ../vectorcraft (Illustrator; formerly DrawCraft), with the same conventions.

Start every session here

plan/ is maintainer-local (gitignored, not in the repo). Each step names the public equivalent to use if you don't have it.

  1. Read plan/STATUS.md (current milestone, next unchecked task, blockers). Public: ROADMAP.md, then docs/gaps.md (ranked shortfalls) and docs/roadmap.md (current focus).
  2. Read the task in plan/execution-plan.md §3, the relevant section of plan/architecture.md, and the README/docs of the crate you touch. Public: docs/architecture.md, docs/contributing.md, docs/testing.md. Visual/behaviour reference: plan/premiere/ (local only, never committed).
  3. Follow the autonomous operation protocol (plan/execution-plan.md §7; public summary: docs/agents.md §5–7). Don't stop to ask unless it lists the decision as the user's.

Non-negotiables

Read AGENTS.md first; its rules override everything here.

  • Never crash (AGENTS.md §0, standard: craftrules/standards/never-crash.md). People depend on FilmCraft; a crash loses their work, so this outranks feature work. No unwrap/expect/panic!/unreachable!/todo!/unimplemented! or unsafe outside tests: return Result<T, E> and propagate with ? (ok_or, let … else, if let). Treat every number from media, project files, presets or CLI/MCP/control params as hostile: get() instead of [i], checked/saturating math, no division by possibly-zero values, capped allocations, bounded recursion. lock().unwrap_or_else(PoisonError::into_inner); background jobs run under catch_unwind. The panic hook + UI catch_unwind (crates/ui-egui/src/crash.rs) are the last-resort guard, not a licence. Every crash fix gets a regression test that panicked before. In particular (§1): never use Adobe iconography, images or any other Adobe asset. Every asset must be openly licensed (OSS / public domain / Creative Commons, or original work by a contributor) and must have a <file>.attribution sidecar plus an entry in ATTRIBUTION.md. cargo xtask assets enforces this.
  • Fonts live in storytold/craft-fonts, never in this repo (no new font files here; AGENTS.md §1.9, standard: craftrules/standards/fonts.md). Build with them: git clone https://github.com/storytold/craft-fonts ../craft-fonts && CRAFT_FONTS_DIR="$PWD/../craft-fonts" cargo run -p filmcraft (crates/text/build.rs embeds them as filmcraft_text::fonts::CRAFT_FONTS, used for Japanese in the UI and titles). Unset, CRAFT_FONTS is empty and everything must still build, test and run; never add craft-fonts to a Cargo.toml. Tests on its glyphs skip when it is empty, so run the gates with and without CRAFT_FONTS_DIR when you touch fonts.
  • Clean-room. Never read/disassemble anything inside Adobe app bundles (names/listings only). Never copy Adobe icons, presets, LUTs, fonts. Never copy GPL/LGPL/AGPL code (FFmpeg, x264, x265, MLT, Kdenlive, Shotcut, Olive, LAME…). ffmpeg/ffprobe run only as external test oracles / fixture generators — never linked or shipped.
  • Pure Rust in the product. OS media APIs only via Rust bindings in crates/platform (hardware decoding; the one crate allowed unsafe, see AGENTS.md §0.3 and docs/adr/0001-platform-ffi.md), optional, behind traits, with our decoders as the fallback.
  • Layering (docs/architecture.md §1, enforced by cargo xtask layers): nothing below L5 depends on egui/eframe/winit/rfd/cpal. L0 codec/container crates depend only on filmcraft-bitstream.
  • Exact time: all time is filmcraft_time::Tick (254 016 000 000/s). Never use f64 seconds for edit math.
  • Time goes through chrono (workspace dependency). Never hand-roll UTC/calendar conversion (days-to-date algorithms, leap-year math); a crate owns only its output format string. The one exception is range extension: past the years chrono can represent, shift the date by whole 400-year Gregorian cycles (146,097 days, after which the calendar repeats), let chrono compute the month and day, and add the cycles back to the year (splitting seconds into hours, minutes and seconds is plain arithmetic).
  • Everything is a command (crates/engine): id, label, menu path, shortcut, params, enabled(), run(). UI, CLI, control channel and MCP all dispatch by id.
  • Everything is agent-drivable: every interactive widget registers an automation id; UI state is serde so the control channel can read/set it.
  • Quality gates before every commit: cargo fmt --check, cargo clippy --workspace --all-targets -- -D warnings, cargo test --workspace, cargo xtask layers, cargo xtask assets, cargo xtask wasm (checks L0–L4 crates). cargo xtask ci runs them all.
  • Commits: one task id per commit (M2.4: CABAC residual decoding). Only green states. End messages with the attribution line required by the environment.

Running and looking at the app

  • cargo run -p filmcraft -- --control 9876 opens the desktop app with the JSON-lines control server (see docs/control-protocol.md).
  • For UI work, look at the result: drive via the control channel and take ui.screenshot, compare with plan/premiere/screenshots/ (maintainer-local; see docs/agents.md §3).
  • Parallel agents: separate git worktrees and CARGO_TARGET_DIR=target/agent-<name>; keep every Cargo.toml valid at all times (the crates/* glob means one broken manifest breaks everyone).
  • Test fixtures: generate with ffmpeg into target/fixtures/ (never commit media). See docs/testing.md.