Pond-level branding: override instance logo and favicon per pond #307

Closed
opened 2026-08-01 06:37:47 +02:00 by opus-5 · 0 comments
Contributor

Depends on #306.

A pond admin should be able to override the instance logo and favicon in
the pond settings, so a pond carries its own identity.

Resolution order

One place decides which asset applies, extending what #306 built:

  1. The pond's own asset, when set.
  2. Otherwise the instance asset.
  3. Otherwise the shipped default (favicon) or instance.name as text
    (logo).

A logo set belongs to one level — variants are never mixed across
levels.
A pond that uploads only a light logo uses that logo in dark
mode too. It does not fall back to the instance's dark variant.

This is deliberate (decided 2026-08-01): a logo silently swapping to a
different image when the viewer switches theme is a change nobody
ordered. A design that looks wrong is more honest than one that is
quietly substituted, and the pond admin can see and fix it.

The pond settings screen warns when the dark variant is missing, exactly
as the instance screen does (#306). The warning is advisory — it names
the consequence, and the pond admin may ignore it. Nothing is blocked.

Only when a pond sets no logo at all does the instance's set apply,
again as a set.

Worth an explicit test in both directions: pond-light-only stays on its
own logo in dark mode, and a pond with nothing set inherits both instance
variants.

Storage and quota

Pond assets live under the branding directory established in #306,
scoped per pond, and are covered by the same backup registration.

They count against the pond's storage quota, charged before the write
the way attachments are (apps/api/src/files/files.service.ts:153).
Without that, branding uploads would be a way around the quota — and
replacing a logo repeatedly would let a pond admin consume disk with no
ceiling.

Purging a pond must remove its branding assets too. The purge standard is
absolute: after it, nothing referencing the pond survives, rows or files
(apps/api/src/trash/pond-purge.e2e.db.test.ts). Extend that test rather
than trusting the new code path.

Pond favicon

The favicon is a document-level resource, so a pond's favicon is applied
by swapping the <link rel="icon"> href when the pond route is entered,
and restoring the instance favicon on leaving.

The limitation is accepted and should be recorded in the code comment:
the swap necessarily happens after first paint, so opening a pond
link directly shows the instance favicon briefly before it changes.
Avoiding that would require server-rendering index.html, which is #179's
territory and deliberately out of scope here. In a pinned tab — where
distinguishing ponds matters most — the tab is already open, so the swap
is the normal case rather than the exception.

Take care that the swap is driven by the resolved pond, not by the raw
route parameter, so an unreadable or non-existent pond slug does not leave
a stale icon in the tab.

Permissions

Pond admins manage their own pond's branding, in the existing pond
settings screen. Follow the project's 404/403 policy: a user who cannot
see the pond gets 404, one who can see but not administer it gets 403.

Security

Unchanged from #306 and non-negotiable at this level too, since the
uploader is now an ordinary pond admin rather than the operator: SVG
rejected, magic bytes validated server-side, size caps enforced, content
type pinned on serving, no image parsing in the api.

Accessibility

  • The pond logo link's accessible name is the pond name in the pond
    context — it must not silently keep announcing the instance name.
  • The pond settings section reuses the upload and crop control from #306,
    including its keyboard operability; it must not grow a second,
    drag-only cropper.
  • A "reset to instance logo" control is a real, labelled control, not an
    empty file field.
  • The section joins apps/web/e2e/a11y.spec.ts per the standing rule.

Acceptance criteria

  • A pond admin can set, replace and clear a pond logo and favicon.
  • Clearing falls back to the instance asset, and clearing that falls
    back to the default — verified at each level.
  • A pond with only a light logo keeps showing that logo in dark mode,
    and the settings screen warns about the missing dark variant without
    blocking anything.
  • A pond with no logo at all inherits both instance variants.
  • Entering a pond swaps the tab icon; leaving restores the instance
    one.
  • Uploads are charged to the pond's storage quota, and a pond at its
    limit is refused with a clear message.
  • Purging a pond deletes its branding files, asserted in the existing
    purge test.
  • A non-admin member cannot change pond branding.
  • pnpm lint, pnpm typecheck, pnpm i18n:check, api suite and the
    a11y spec pass.
Depends on #306. A pond admin should be able to override the instance logo and favicon in the pond settings, so a pond carries its own identity. ## Resolution order One place decides which asset applies, extending what #306 built: 1. The pond's own asset, when set. 2. Otherwise the instance asset. 3. Otherwise the shipped default (favicon) or `instance.name` as text (logo). **A logo set belongs to one level — variants are never mixed across levels.** A pond that uploads only a light logo uses *that* logo in dark mode too. It does **not** fall back to the instance's dark variant. This is deliberate (decided 2026-08-01): a logo silently swapping to a different image when the viewer switches theme is a change nobody ordered. A design that looks wrong is more honest than one that is quietly substituted, and the pond admin can see and fix it. The pond settings screen warns when the dark variant is missing, exactly as the instance screen does (#306). The warning is advisory — it names the consequence, and the pond admin may ignore it. Nothing is blocked. Only when a pond sets **no** logo at all does the instance's set apply, again as a set. Worth an explicit test in both directions: pond-light-only stays on its own logo in dark mode, and a pond with nothing set inherits both instance variants. ## Storage and quota Pond assets live under the branding directory established in #306, scoped per pond, and are covered by the same backup registration. They **count against the pond's storage quota**, charged before the write the way attachments are (`apps/api/src/files/files.service.ts:153`). Without that, branding uploads would be a way around the quota — and replacing a logo repeatedly would let a pond admin consume disk with no ceiling. Purging a pond must remove its branding assets too. The purge standard is absolute: after it, nothing referencing the pond survives, rows or files (`apps/api/src/trash/pond-purge.e2e.db.test.ts`). Extend that test rather than trusting the new code path. ## Pond favicon The favicon is a document-level resource, so a pond's favicon is applied by swapping the `<link rel="icon">` href when the pond route is entered, and restoring the instance favicon on leaving. The limitation is accepted and should be recorded in the code comment: the swap necessarily happens **after** first paint, so opening a pond link directly shows the instance favicon briefly before it changes. Avoiding that would require server-rendering `index.html`, which is #179's territory and deliberately out of scope here. In a pinned tab — where distinguishing ponds matters most — the tab is already open, so the swap is the normal case rather than the exception. Take care that the swap is driven by the resolved pond, not by the raw route parameter, so an unreadable or non-existent pond slug does not leave a stale icon in the tab. ## Permissions Pond admins manage their own pond's branding, in the existing pond settings screen. Follow the project's 404/403 policy: a user who cannot see the pond gets 404, one who can see but not administer it gets 403. ## Security Unchanged from #306 and non-negotiable at this level too, since the uploader is now an ordinary pond admin rather than the operator: SVG rejected, magic bytes validated server-side, size caps enforced, content type pinned on serving, no image parsing in the api. ## Accessibility - The pond logo link's accessible name is the pond name in the pond context — it must not silently keep announcing the instance name. - The pond settings section reuses the upload and crop control from #306, including its keyboard operability; it must not grow a second, drag-only cropper. - A "reset to instance logo" control is a real, labelled control, not an empty file field. - The section joins `apps/web/e2e/a11y.spec.ts` per the standing rule. ## Acceptance criteria - [ ] A pond admin can set, replace and clear a pond logo and favicon. - [ ] Clearing falls back to the instance asset, and clearing that falls back to the default — verified at each level. - [ ] A pond with only a light logo keeps showing that logo in dark mode, and the settings screen warns about the missing dark variant without blocking anything. - [ ] A pond with no logo at all inherits both instance variants. - [ ] Entering a pond swaps the tab icon; leaving restores the instance one. - [ ] Uploads are charged to the pond's storage quota, and a pond at its limit is refused with a clear message. - [ ] Purging a pond deletes its branding files, asserted in the existing purge test. - [ ] A non-admin member cannot change pond branding. - [ ] `pnpm lint`, `pnpm typecheck`, `pnpm i18n:check`, api suite and the a11y spec pass.
opus-5 added this to the M33 — Tweaks & Feinschliff milestone 2026-08-01 06:37:47 +02:00
opus-5 added the
effort:M
backend
frontend
labels 2026-08-01 06:37:47 +02:00
Sign in to join this conversation.
No project
No Assignees
1 Participants
Notifications
Due Date
The due date is invalid or out of range. Please use the format 'yyyy-mm-dd'.

No due date set.

Dependencies

No dependencies set.

Reference: stwaidele/dorfteich#307
No description provided.