Files
fete/specs/022-header-image/spec.md
nitrix 6aeb4b8bca Migrate project artifacts to spec-kit format
- Move cross-cutting docs (personas, design system, implementation phases,
  Ideen.md) to .specify/memory/
- Move cross-cutting research and plans to .specify/memory/research/ and
  .specify/memory/plans/
- Extract 5 setup tasks from spec/setup-tasks.md into individual
  specs/001-005/spec.md files with spec-kit template format
- Extract 20 user stories from spec/userstories.md into individual
  specs/006-026/spec.md files with spec-kit template format
- Relocate feature-specific research and plan docs into specs/[feature]/
- Add spec-kit constitution, templates, scripts, and slash commands
- Slim down CLAUDE.md to Claude-Code-specific config, delegate principles
  to .specify/memory/constitution.md
- Update ralph.sh with stream-json output and per-iteration logging
- Delete old spec/ and docs/agents/ directories
- Gitignore Ralph iteration JSONL logs

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 20:19:41 +01:00

7.7 KiB

Feature Specification: Select Event Header Image from Unsplash

Feature: 022-header-image Created: 2026-03-06 Status: Draft Source: Migrated from spec/userstories.md

User Scenarios & Testing

User Story 1 - Select header image during creation or editing (Priority: P1)

The event organizer can search for a header image via an integrated Unsplash search during event creation or editing. The search is server-proxied — the client never contacts Unsplash directly. When an image is selected, the server downloads and stores it locally. Proper Unsplash attribution is displayed on the event page.

Why this priority: Core functionality of this feature. All other stories depend on an image being selectable and stored.

Independent Test: Can be fully tested by creating an event with a header image selected, verifying the event page renders the image served from the app's own domain with attribution.

Acceptance Scenarios:

  1. Given the organizer is on the event creation or editing form and the server has an Unsplash API key configured, When the organizer enters a search query, Then the client sends the query to the app's backend which proxies it to Unsplash and returns results — the client never contacts Unsplash directly.
  2. Given the organizer selects an image from search results, When the selection is confirmed, Then the server downloads and stores the image locally on disk and serves it from the app's own domain.
  3. Given an event has a stored header image, When a guest views the event page, Then the header image is rendered and Unsplash attribution (photographer name and link to Unsplash) is displayed alongside it.
  4. Given an event has a header image, When the organizer removes it, Then the image is no longer displayed on the event page.

User Story 2 - Event page renders header image (Priority: P1)

The guest-facing event page renders the selected header image if one is set. The image is served from the app's own domain, not from Unsplash's CDN, so no guest data or IP address is transmitted to Unsplash.

Why this priority: Directly impacts the visual presentation that motivated the feature.

Independent Test: Can be tested by loading an event page with a stored header image and verifying the image URL is on the app's domain and no network request is made to Unsplash domains.

Acceptance Scenarios:

  1. Given an event has a stored header image, When a guest opens the event page, Then the image is loaded from the app's own domain and no request is made to Unsplash or any third-party CDN.
  2. Given an event has no header image set, When a guest opens the event page, Then the page renders without a header image and no error is shown.

User Story 3 - Feature unavailable when API key not configured (Priority: P2)

If the server has no Unsplash API key configured, the image search feature is simply not shown in the UI. No error is displayed. Existing stored images continue to serve normally if the key is removed after images were already stored.

Why this priority: Required for graceful degradation — the feature must not break the app when the API key is absent.

Independent Test: Can be tested by starting the server without UNSPLASH_API_KEY set and verifying the image search UI is absent from the creation and editing forms, and that any previously stored images still render.

Acceptance Scenarios:

  1. Given the server has no UNSPLASH_API_KEY environment variable set, When the organizer opens the event creation or editing form, Then the image search option is not shown — no error, no broken UI element.
  2. Given the API key is removed from the config after images were already stored, When a guest opens an event page with a previously stored image, Then the image still renders from disk and only the search/select UI becomes unavailable.

User Story 4 - Image deleted with event on expiry (Priority: P2)

When an event expires and is deleted by the cleanup process (US-12), the stored header image file is deleted along with all other event data.

Why this priority: Privacy requirement — stored files must not outlive the event data.

Independent Test: Can be tested by creating an event with a header image, expiring it, running the cleanup process, and verifying the image file no longer exists on disk.

Acceptance Scenarios:

  1. Given an event with a stored header image has passed its expiry date, When the cleanup process runs (US-12), Then the image file is deleted from disk along with the event record and all associated data.
  2. Given an event with a header image is explicitly deleted by the organizer (US-19), When the deletion is confirmed, Then the image file is deleted from disk immediately.

Edge Cases

  • What happens when the Unsplash API returns an error or rate-limit response? — Server returns a clear error to the client; the organizer can retry.
  • What happens when disk is full and the server cannot store the downloaded image? — Server returns an error; the event can still be created/saved without an image.
  • What happens if an image download from Unsplash fails mid-transfer? — Server returns an error; no partial file is stored.
  • What happens if the UNSPLASH_API_KEY is set but invalid? — Server proxies the call and returns the Unsplash API error to the client (e.g. "unauthorized").

Requirements

Functional Requirements

  • FR-001: The server MUST expose an Unsplash image search proxy endpoint that accepts a query string, calls the Unsplash API server-side, and returns results to the client — the client MUST NOT contact Unsplash directly.
  • FR-002: When an image is selected, the server MUST download and store the image file locally on disk; the image MUST be served from the app's own domain.
  • FR-003: The server MUST store Unsplash attribution metadata (photographer name and Unsplash URL) alongside the image reference and display it on the event page.
  • FR-004: The organizer MUST be able to remove a previously selected header image.
  • FR-005: The guest-facing event page MUST render the header image if one is set, served from the app's own domain.
  • FR-006: If UNSPLASH_API_KEY is not configured, the image search UI MUST NOT be shown; no error is displayed.
  • FR-007: If the API key is removed after images were stored, existing images MUST continue to render from disk; only the search/select UI becomes unavailable.
  • FR-008: The server MUST NOT log any guest IP address or identifier when serving the stored image.
  • FR-009: When an event is deleted (US-12 or US-19), the server MUST delete the stored image file along with all other event data.
  • FR-010: The UNSPLASH_API_KEY environment variable and the persistent volume requirement for image storage MUST be documented in the README's self-hosting section.

Key Entities

  • HeaderImage: Stored image file on disk, associated with an event. Attributes: local file path, Unsplash image ID, photographer name, photographer URL, Unsplash attribution URL. Not independently stored — deleted with the event.

Success Criteria

Measurable Outcomes

  • SC-001: A guest opening an event page with a header image makes no network requests to Unsplash or any third-party domain.
  • SC-002: Removing the UNSPLASH_API_KEY does not break the app, the event creation form, or the rendering of previously stored images.
  • SC-003: After event expiry and cleanup, no image file remains on disk for that event.
  • SC-004: Unsplash attribution (photographer name and link) is visible on every event page that has a header image.
  • SC-005: The image search, download, and storage flow works end-to-end when a valid API key is configured.