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. ASplitMix64seed 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 matchingopacityandrotation.CelebrationView(Celebration/CelebrationView.swift) paints a frame for the current progress. Motion rides a singleTimelineView(.animation)clock; passingfrozenProgressrenders 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.