Skip to content

PresentationDocument: media-free slide deck in FCPKitDSL #38

Description

@leogdion

Files

  • Create Sources/FCPKitDSL/PresentationSlide.swift
  • Create Sources/FCPKitDSL/PresentationDocument.swift
  • Delete Sources/FCPKitDSL/RGBDocument.swift
  • Modify Tests/FCPKitDSLTests/FCPTimeIntervalTests.swift — see below

Placement: library type, thin CLI command

PresentationDocument and PresentationSlide are public and shipped in the
library
, so users browsing FCPKitDSL can read the showcase and tests can import
it. The executable holds only a thin command that invokes the library type — not
a duplicated copy (decision 2026-08-02).

RGBDocument is the counter-example to clean up in the same pass. It is currently
duplicated across Sources/FCPKitDSL/RGBDocument.swift and
Sources/fcpxml-dsl/RGBDocument.swift. Demo scaffolding belongs only in the CLI,
so delete the library copy and keep the executable's.

That deletion breaks Tests/FCPKitDSLTests/FCPTimeIntervalTests.swift, which imports
the library copy. That test is really about FCPTimeInterval, not about
RGBDocumentgive it a small local fixture document rather than moving it to a
CLI test target.

API

/// A slide in a ``PresentationDocument``: a heading over a solid color background.
public struct PresentationSlide: Sendable {
  public var heading: String
  public var subheading: String?
  public var background: Color
  public var duration: FCPTime
  public init(
    heading: String,
    subheading: String? = nil,
    background: Color,
    duration: FCPTime = .seconds(6)
  )
}

/// A media-free slide deck showcasing FCPKitDSL, built from color generators
/// and anchored titles.
public struct PresentationDocument: Document {
  public let projectName: String
  public let slides: [PresentationSlide]
  public let transitionDuration: FCPTime

  /// The default FCPKit feature deck.
  public static let featureShowcase: [PresentationSlide]

  public init(
    projectName: String = "FCPKit Presentation",
    slides: [PresentationSlide] = PresentationDocument.featureShowcase,
    transitionDuration: FCPTime = FCPTime(numerator: 1)
  )

  public var body: some DocumentContent { ... }
}

The body loops slides, interleaving Transition(.crossDissolve) between them and
anchoring a styled Title on lane 1 of each Color background. This exercises
Issue 2's ColorGenerator promotion, Issue 3's title modifiers, and the result
builder's buildArray / buildOptional — a genuine showcase rather than a contrived
one. If if index > 0 inside the for proves awkward through the builder, fall back
to a helper returning DocumentGroup.

Dissolve-safe title timing

Anchored items are not automatically included in a primary-storyline transition,
so a title spanning a dissolve would hard-cut while its background dissolves. We
design around this rather than discovering it at import.

placeOverlapping (Layout+Packing.swift:130-142) sets a clip's
start = overlap.previous (= T/2) and shrinks its duration by both overlaps.
Anchored offset is relative to the parent's trimmed start, so a title at
offset: .zero already begins exactly where the incoming dissolve ends. Only the
tail needs trimming:

titleDuration = slideDuration - (incomingT / 2) - (outgoingT / 2)

with incomingT / outgoingT zero for the first and last slide. The title then sits
entirely inside the non-overlapping region and hard-cuts nowhere visible. Assert this
formula in tests.

Deck content

Seven slides at 6s with 1s dissolves lands ~36s, inside the 30-45s target. Assert on
the packer's computed sequence duration rather than hand arithmetic.

# Heading Background Showcases
1 FCPKit near-black Color(white: 0.08) title positioning
2 Typed FCPXML model deep blue
3 Ordered spine, preserved teal
4 SwiftUI-shaped DSL purple alignment-positioned subheading
5 Resource interning orange
6 DTD-validated output green
7 brightdigit/FCPKit near-black

Use Helvetica so the video reproduces on any machine.

Show more than cross dissolves. A deck with seven identical dissolves undersells
the library. Once Issues 7 and 8 land, vary the transitions and apply at least a few
clip modifiers (opacity, blend mode, scale) across the deck so the video demonstrates
range rather than repetition.

Two constraints on that variety:

  • The 30-45s target is a soft guide, not a hard gate. If showing more capability
    costs a few seconds, take the seconds — but assert on the packer's computed
    duration so the number is never guessed.
  • Every effect used must have a UID that is either derivable from the Motion template
    catalog or captured from a real Final Cut export (see Issue 8). Do not invent UIDs.

Until Issues 7 and 8 land, the deck ships with cross dissolves and is revisited
afterward; this issue is not blocked on them.

Tests

New Tests/FCPKitDSLTests/PresentationDocumentTests.swift:

  • Spine children alternate ["video", "transition", "video", "transition", ...]
    (copy the assertSpineChildNames helper pattern from FeaturePairAcceptanceTests).
  • Each video has exactly one anchored title at lane == "1".
  • Every text-style-def id in the document is unique — ties Issue 1 to the showcase.
  • Each title's duration matches the dissolve-safe formula above.
  • Sequence duration falls in the 30-45s range.
  • Effect interning: exactly one Custom generator effect and one Basic Title
    effect despite seven slides, proving ResourceStore dedup.
  • assertDTDValidates(generatedData) — the end-to-end gate.

Acceptance criteria

  • FCPKIT_REQUIRE_DTD=1 swift test passes.
  • Both new files stay under the 225-line warning.

Conventions

  • MIT header block on every new file (see Scripts/header.sh).
  • Swift Testing (import Testing, @Test) for new tests, per .claude/agent-notes.md. Include "Tests" in either the parent enum or the child struct, never both.
  • Doc comments on every public declaration.
  • Keep files under 225 lines (SwiftLint file_length); prefer Type+Modifiers.swift splits.
  • Run swift test and swift run fcpxml-diff schema-completeness Tests/FCPKitTests/TestData before each PR. Prefer opening a PR over merging.

Full spec with context and rationale: docs/planning/demo-presentation-video.md

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestready-for-agentFully specified, ready for an AFK agentv0.1.0FCPKit v0.1.0 milestone work

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions