Skip to main content

Shipping celebrations

Shipping should feel good. When you open a pull request or push straight to the remote, kiln throws a little confetti — a burst of paper scraps across the window and a banner that springs in, holds, and fades. It's the dopamine hit for getting work out the door (#488).

When it fires

The celebration rides the same two publishing verbs the Changes surface offers:

  • Open PR — the full lifecycle (carve a topic branch if needed, push, gh pr create). A successful open plays the pull request opened burst. If the workflow falls back to a plain push, you get the pushed burst instead.
  • Push — the solo "crack on" path that pushes the current branch straight to its remote with no PR. A successful push plays the pushed burst.

Both run through AppState.celebrate(_:), so any future success path can join in with one call. The trigger is meaning, like Cue: a surface says "this shipped" and the overlay picks the words, the glyph, and the colour.

How it's built

The animation is hosted once at the app root (RootView) and driven by app.celebration. The particle maths lives in a pure, seeded model so it's testable and renders identically every time:

  • ConfettiField (Celebration/Celebration.swift) is a deterministic firework burst. A SplitMix64 seed makes a fixed set of scraps, each with a launch angle, reach, spin, size, colour, and delay. offset(for:at:) is a pure function of the shared timeline progress — pieces fly out along their angle and gravity bends the path back down — with matching opacity and rotation.
  • CelebrationView (Celebration/CelebrationView.swift) paints a frame for the current progress. Motion rides a single TimelineView(.animation) clock; passing frozenProgress renders one fixed frame, which is how the design-system gallery story and the screenshot tests capture it without a running animation.

A successful burst also plays the success cue, so the celebration lands on the ear as well as the eye.

Turning it off

Celebrations default on. Flip Celebrate shipping off in Settings ▸ General ▸ Feedback to silence them everywhere, or shadow it per-project with celebrateShipping in .kiln config — the same project → user → default layering as every other gated setting.