Skip to content

feat: Implement birthday mode with confetti animations and a celebrat…#611

Open
aviv926 wants to merge 4 commits into
damongolding:mainfrom
aviv926:birthday-mode-clean
Open

feat: Implement birthday mode with confetti animations and a celebrat…#611
aviv926 wants to merge 4 commits into
damongolding:mainfrom
aviv926:birthday-mode-clean

Conversation

@aviv926
Copy link
Copy Markdown

@aviv926 aviv926 commented Dec 13, 2025

This PR adds a new feature called "Birthday" which essentially makes a person appear for that day as the birthday person when photos belonging to them are displayed (with the addition of confetti and a frame indicating their name and age)

Important information:

  • A birthday person will not be displayed if there is no name for them in the immich UI

  • When 2 people have a birthday on the same day, both their names and photos belonging to them will be displayed.

image

Summary by CodeRabbit

  • New Features
    • Birthday celebration mode: automatically detects today's birthdays and shows a full-screen festive overlay with confetti, emojis, celebrant names and their current ages.
    • Configurable birthday setting: a new toggle lets you enable or disable birthday celebrations in the app.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Dec 13, 2025

Walkthrough

Adds a birthday celebration feature: new config flag, backend birthday detection and age calculation, view-data plumbing, frontend overlay/CSS, and confetti animation triggered at initial display and on image swaps.

Changes

Cohort / File(s) Summary
Configuration
config.example.yaml, config.schema.json, internal/config/config.go
Added birthday boolean to example config, JSON schema and Config struct (with tags and default:false).
Backend view & helpers
internal/routes/routes_asset_helpers.go, internal/routes/routes_home.go, internal/common/common.go
Added CheckBirthdays(ctx, config.Config, requestID, deviceID string); integrated birthday checks into view-data generation; extended common.ViewData with BirthdayModeActive bool, BirthdayPeople []string, BirthdayAges map[string]int.
Backend utilities
internal/utils/utils.go
Added CalculateAge(time.Time) int with leap-year handling; minor ParseSize tightening.
Frontend logic
frontend/src/ts/kiosk.ts
Added birthday state (birthdayModeActive, birthdayPeople, birthdayAges, imageCount), handleBirthdayMode overlay builder and triggerConfetti using canvas-confetti; initialisation wiring and HTMX image-swap confetti triggers.
Frontend styling
frontend/src/css/birthday.css, frontend/src/css/kiosk.css
New birthday.css overlay styles (gold frame, shimmer text, responsive layout); kiosk.css updated to import birthday.css (import order adjusted).
Templates
internal/templates/views/views_home.templ
Passed birthdayModeActive, birthdayPeople, and birthdayAges into frontend kioskData payload.

Sequence Diagram(s)

sequenceDiagram
    participant Config as Config
    participant Backend as Backend (Home handler)
    participant Check as BirthdayCheck
    participant Utils as Utils (CalculateAge)
    participant Template as Template Renderer
    participant Frontend as Frontend (Kiosk)
    participant UI as Birthday UI & Confetti

    Config->>Backend: birthday flag enabled
    Backend->>Check: Request today's birthdays
    Check->>Check: Filter people by date
    Check->>Utils: Compute ages
    Check-->>Backend: Return people + ages
    Backend->>Template: Populate ViewData (BirthdayModeActive, People, Ages)
    Template->>Frontend: Render kioskData with birthday fields
    Frontend->>UI: Build overlay and display names/ages
    Frontend->>UI: Trigger initial confetti
    Frontend->>UI: Trigger confetti on each image swap (htmx afterSwap)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Pay attention to timezone and leap-year edge cases in CalculateAge.
  • Verify idempotent logging and daily deduplication logic in routes_asset_helpers.go.
  • Confirm DOM lifecycle and event handling for overlay and repeated confetti triggers in kiosk.ts.

Possibly related PRs

Suggested labels

enhancement

Poem

🐰 Hopping through frames with shimmer and cheer,

I bring gold text, confetti and birthday cheer,
Names and ages gleam in a framed delight,
A kiosk party sparkling through the night,
🥕✨🎂

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 69.23% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main feature being added: birthday mode with confetti animations and celebration elements.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (7)
config.schema.json (1)

50-52: Add default/description to the new birthday schema field for clarity.
Right now it’s a bare boolean; adding a default: false (and a short description) makes tooling/UX nicer and keeps parity with config.example.yaml.

config.example.yaml (1)

20-23: Consider adding a one-line comment describing what birthday does.
This keeps the example config self-explanatory (e.g., “show birthday overlay/confetti for people whose birthday is today (requires named people in Immich)”).

internal/common/common.go (1)

103-108: ViewData additions look good; consider default initialisation for BirthdayAges.
If BirthdayAges is left nil, some consumers may need extra guarding; initialising to map[string]int{} where ViewData is constructed can simplify usage.

frontend/src/ts/kiosk.ts (3)

51-74: Consider making the new birthday fields optional (or defaulting them) to avoid runtime undefined surprises.
Because kioskData is parsed from JSON, older payloads or partial data can produce undefined even if the TS type says otherwise.

Also applies to: 81-85


137-140: Avoid unconditional logging of the full kioskData in production.
This can be noisy and may inadvertently expose config-derived information; consider gating behind debug/debugVerbose.


279-307: Confetti loop: consider clamping origins and respecting reduced motion.
origin.y can go negative (Math.random() - 0.2); if that’s intended, fine, but if not, clamp to [0,1]. Also consider skipping the animation when matchMedia('(prefers-reduced-motion: reduce)') is true.

frontend/src/css/birthday.css (1)

17-24: Remove redundant border declarations.

Lines 18–20 define border-image-slice, border-width, and an initial border property, but these are immediately overridden by the solid border declaration on line 23. The comments on lines 21–22 are also unnecessary once the redundant properties are removed.

Apply this diff to clean up the CSS:

 .birthday-frame {
     padding: clamp(10px, 2vh, 20px) clamp(15px, 4vw, 40px);
-    border: 4px solid;
-    border-image-slice: 1;
-    border-width: 4px;
-    border-radius: 15px; /* Note: border-image and border-radius conflict involved, using box-shadow for "border" or pseudo-element is safer for rounded gradient borders */
-    /* Let's use a simpler solid gold border with radius for now, or a double border */
     border: 5px solid #BF953F;
     border-radius: 20px;
     background: rgba(0, 0, 0, 0.6); /* Semi-transparent black background to pop against photos */
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c718a04 and 53cae7d.

⛔ Files ignored due to path filters (1)
  • frontend/pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (11)
  • config.example.yaml (1 hunks)
  • config.schema.json (1 hunks)
  • frontend/src/css/birthday.css (1 hunks)
  • frontend/src/css/kiosk.css (1 hunks)
  • frontend/src/ts/kiosk.ts (5 hunks)
  • internal/common/common.go (1 hunks)
  • internal/config/config.go (1 hunks)
  • internal/routes/routes_asset_helpers.go (3 hunks)
  • internal/routes/routes_home.go (1 hunks)
  • internal/templates/views/views_home.templ (1 hunks)
  • internal/utils/utils.go (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
internal/routes/routes_asset_helpers.go (3)
internal/common/common.go (1)
  • ViewData (96-108)
internal/config/config.go (1)
  • Config (210-442)
internal/immich/immich.go (2)
  • Person (131-139)
  • BirthDate (122-122)
🔇 Additional comments (7)
frontend/src/css/kiosk.css (1)

29-30: Import change looks fine; just ensure the ordering is intentional.
If birthday.css relies on variables/utility classes, this placement should work given the earlier imports.

internal/templates/views/views_home.templ (1)

103-105: Wiring new birthday fields into the frontend payload is consistent and clear.
One small robustness note: if BirthdayAges can be nil, it will serialise as null; either initialise to {} server-side or ensure the TS access is optional-chained.

internal/config/config.go (1)

378-380: LGTM!

The Birthday configuration field is properly defined with appropriate tags and a sensible default value of false. The placement in the struct is logical.

internal/routes/routes_asset_helpers.go (4)

31-37: LGTM!

The package-level variables for birthday logging are properly defined with a mutex for thread-safe access. This allows for deduplication of birthday log messages across requests.


724-749: LGTM!

The CheckBirthdays function correctly retrieves all named people, filters by matching birth month and day, and handles errors appropriately by silently skipping people without valid birthdates.


781-792: LGTM!

The birthday logging deduplication logic correctly uses a mutex for thread safety and prevents duplicate log entries for the same birthday people on the same day. The formatted log message with emoji is a nice touch for birthday celebrations.


794-801: LGTM!

The configuration override logic correctly restricts the kiosk to display only birthday people when birthday mode is active. Clearing other buckets (albums, tags, dates, memories) and disabling RequireAllPeople ensures the birthday celebration takes precedence.

Comment thread frontend/src/ts/kiosk.ts
@@ -1,5 +1,6 @@
import DOMPurify from "dompurify";
import htmx from "htmx.org";
import confetti from "canvas-confetti";
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🌐 Web query:

canvas-confetti npm TypeScript import default export types

💡 Result:

Short answer

  • Use the community types package: npm i --save-dev @types/canvas-confetti. That provides type declarations for canvas‑confetti. [1][2]
  • Import style depends on your tsconfig:
    • If you have "esModuleInterop": true (or "allowSyntheticDefaultImports"), use:
      import confetti from "canvas-confetti";
    • Otherwise use the namespace import:
      import * as confetti from "canvas-confetti";
      Either works at runtime; the former needs the TS interop flags to avoid type errors. [1][2]

If you still get "Could not find a declaration file" or ts(7016)

  • Install @types/canvas-confetti (preferred). [1]
  • Or add a minimal declaration file (e.g. src/types/canvas-confetti.d.ts) with:
    declare module "canvas-confetti";
    to silence TypeScript until you add proper types. [3]

References
[1] @types/canvas-confetti (npm / DefinitelyTyped). [2] @types/canvas-confetti package.json (UNPKG). [3] Example fix / declare module guidance (article).


🏁 Script executed:

# Check project configuration files
find . -maxdepth 2 -type f \( -name "tsconfig.json" -o -name "package.json" -o -name "vite.config.*" -o -name "webpack.config.*" \) | head -20

Repository: damongolding/immich-kiosk

Length of output: 117


🏁 Script executed:

# Check if canvas-confetti is in dependencies and which types package is used
cd . && (cat package.json 2>/dev/null | jq '.dependencies, .devDependencies' 2>/dev/null || echo "package.json not found")

Repository: damongolding/immich-kiosk

Length of output: 51


🏁 Script executed:

# Search for any existing canvas-confetti usage in the codebase
rg "canvas-confetti|confetti" --type ts --type js -B 2 -A 2

Repository: damongolding/immich-kiosk

Length of output: 1103


🏁 Script executed:

# Read tsconfig.json to check compiler options
cat -n frontend/tsconfig.json

Repository: damongolding/immich-kiosk

Length of output: 853


🏁 Script executed:

# Read package.json to check if @types/canvas-confetti is present
cat -n frontend/package.json | head -50

Repository: damongolding/immich-kiosk

Length of output: 1858


Install @types/canvas-confetti as a dev dependency.

The import style is correct given "esModuleInterop": true in your TypeScript configuration; however, @types/canvas-confetti is missing from devDependencies in frontend/package.json. Without it, TypeScript will raise a TS7016 error during type checking (which runs in your pnpm test step). Run pnpm add -D @types/canvas-confetti to resolve this.

🤖 Prompt for AI Agents
In frontend/src/ts/kiosk.ts around line 3, TypeScript emits TS7016 because the
package @types/canvas-confetti is not installed; add the missing type
definitions as a dev dependency by running: pnpm add -D @types/canvas-confetti
(or add it to frontend/package.json devDependencies and run pnpm install), then
re-run type checking to ensure the import import confetti from
"canvas-confetti"; is properly typed.

Comment thread frontend/src/ts/kiosk.ts
Comment thread internal/routes/routes_asset_helpers.go Outdated
Comment thread internal/routes/routes_home.go Outdated
Comment thread internal/utils/utils.go Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
frontend/src/ts/kiosk.ts (1)

3-3: Ensure @types/canvas-confetti is installed.

The import style is correct, but TypeScript will raise TS7016 during type checking if @types/canvas-confetti is missing from devDependencies. This was previously flagged.

🧹 Nitpick comments (1)
frontend/src/ts/kiosk.ts (1)

283-311: Consider respecting prefers-reduced-motion for accessibility.

The confetti implementation is well-structured with throttling and smooth animation. However, users who have enabled prefers-reduced-motion may prefer to skip the confetti effects.

You can add a check at the start of triggerConfetti:

 function triggerConfetti() {
+    if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
+        return;
+    }
     const duration = 3000;
     const animationEnd = Date.now() + duration;
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 53cae7d and 0c886e3.

📒 Files selected for processing (4)
  • frontend/src/ts/kiosk.ts (5 hunks)
  • internal/routes/routes_asset_helpers.go (3 hunks)
  • internal/routes/routes_home.go (1 hunks)
  • internal/utils/utils.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • internal/routes/routes_home.go
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2024-11-20T10:52:53.804Z
Learnt from: damongolding
Repo: damongolding/immich-kiosk PR: 192
File: frontend/src/ts/clock.ts:1-1
Timestamp: 2024-11-20T10:52:53.804Z
Learning: In the `frontend/src/ts` files of the `immich-kiosk` project, importing `{ format }` from `"date-fns"` causes a TypeScript error. The correct import statement is `import { format } from "date-fns/format";`.

Applied to files:

  • frontend/src/ts/kiosk.ts
🧬 Code graph analysis (1)
internal/routes/routes_asset_helpers.go (3)
internal/common/common.go (2)
  • New (28-41)
  • ViewData (96-108)
internal/immich/immich.go (3)
  • New (402-408)
  • Person (131-139)
  • BirthDate (122-122)
internal/utils/utils.go (1)
  • CalculateAge (752-773)
🔇 Additional comments (6)
internal/utils/utils.go (2)

750-773: LGTM! Age calculation correctly addresses previous concerns.

The implementation properly handles timezone normalization and Feb 29 birthdays. The logic is clear and correct.


775-784: LGTM! Leap year calculation is correct.

The function implements the standard leap year algorithm correctly.

frontend/src/ts/kiosk.ts (1)

212-281: LGTM! Birthday mode implementation addresses previous concerns.

The function properly prevents duplicate overlays, scopes event listeners to the kiosk element, and safely handles birthday data with optional chaining. The DOM construction and string formatting logic are correct.

internal/routes/routes_asset_helpers.go (3)

31-37: LGTM! Birthday logging state is properly protected.

The package-level variables for birthday logging deduplication are appropriately guarded with a mutex.


724-749: LGTM! Birthday detection logic is correct.

The function properly filters named people with birthdays matching today's month and day, handling missing or invalid birthdates gracefully.


762-806: LGTM! Birthday mode logic is correct and uses consistent age calculation.

The implementation properly detects birthdays, calculates ages using utils.CalculateAge (consistent with the codebase standard), and overrides the configuration to display only birthday people. The logging deduplication with mutex is a nice touch.

@damongolding damongolding added not-planned This request won’t be implemented. labels Apr 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

not-planned This request won’t be implemented.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants