fix(sharing): make public note colors follow the visitor's theme

The public share page picked the note background from the light palette
unconditionally, while app.html applies data-theme="dark" on every page.
A visitor in dark mode therefore got a light pastel card rendered with the
dark theme's light text colour — washed out and barely readable.

Emit both palettes as custom properties and let [data-theme] select one, so
the correct colour lands on the first paint of this SSR page instead of
flashing after hydration.

Reported by a user over email.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Frederic Cabassut
2026-07-31 12:03:39 +02:00
committed by Fred Cabassut
co-authored by Claude Opus 5
parent 794bb639a6
commit fbb2f95c59
2 changed files with 66 additions and 4 deletions
+18 -4
View File
@@ -1,5 +1,5 @@
<script lang="ts">
import { NOTE_COLORS } from '$lib/utils/colors.js';
import { getNoteColor } from '$lib/utils/colors.js';
import { renderMarkdown } from '$lib/utils/markdown.js';
import { linkifyText } from '$lib/utils/checklist.js';
import type { NoteColor, PublicAttachment } from '$lib/types/index.js';
@@ -12,8 +12,12 @@
const { data } = $props();
// Both palettes are emitted as custom properties so the card follows the visitor's theme on the
// first paint — the CSS variables in app.css switch on [data-theme], which app.html sets before
// hydration, so a JS-resolved single colour would flash the wrong palette on this SSR page.
const noteColor = data.color as NoteColor;
const bgColor = NOTE_COLORS[noteColor]?.bg ?? NOTE_COLORS.default.bg;
const bgLight = getNoteColor(noteColor, false);
const bgDark = getNoteColor(noteColor, true);
const renderedContent = renderMarkdown(data.content);
@@ -55,8 +59,8 @@
<div class="mx-4 w-full max-w-2xl py-10">
<article
class="rounded-sm border border-[var(--border-subtle)] shadow-[var(--card-shadow)] overflow-hidden"
style="background-color: {bgColor}"
class="note-surface rounded-sm border border-[var(--border-subtle)] shadow-[var(--card-shadow)] overflow-hidden"
style="--note-bg: {bgLight}; --note-bg-dark: {bgDark}"
data-testid="shared-note"
>
<div class="px-6 py-5">
@@ -128,3 +132,13 @@
</a>
</div>
</div>
<style>
.note-surface {
background-color: var(--note-bg);
}
:global([data-theme='dark']) .note-surface {
background-color: var(--note-bg-dark);
}
</style>
+48
View File
@@ -121,6 +121,54 @@ test.describe('Public Note Sharing', () => {
await expect(card.getByTestId('sharing-indicator')).toBeVisible();
});
test('Scenario: Shared note follows a dark-mode visitor theme', async ({
authenticatedPage: page,
browser
}) => {
// Given a publicly shared note colored "fog"
const note = await createNoteViaApi(page, 'Dark Visitor Note', 'Readable in dark mode');
await page.request.patch(`/api/notes/${note.id}`, { data: { color: 'fog' } });
const token = await createShareViaApi(page, note.id);
// When a visitor whose system prefers dark mode opens the share URL
const context = await browser.newContext({ colorScheme: 'dark' });
const publicPage = await context.newPage();
await publicPage.goto(`/s/${token}`);
// Then the note is rendered on the dark "fog" surface, so its text stays legible
await expect(publicPage.locator('html')).toHaveAttribute('data-theme', 'dark');
await expect(publicPage.getByTestId('shared-note')).toHaveCSS(
'background-color',
'rgb(34, 46, 58)'
);
await context.close();
});
test('Scenario: Shared note follows a light-mode visitor theme', async ({
authenticatedPage: page,
browser
}) => {
// Given a publicly shared note colored "fog"
const note = await createNoteViaApi(page, 'Light Visitor Note', 'Readable in light mode');
await page.request.patch(`/api/notes/${note.id}`, { data: { color: 'fog' } });
const token = await createShareViaApi(page, note.id);
// When a visitor whose system prefers light mode opens the share URL
const context = await browser.newContext({ colorScheme: 'light' });
const publicPage = await context.newPage();
await publicPage.goto(`/s/${token}`);
// Then the note is rendered on the light "fog" surface
await expect(publicPage.locator('html')).not.toHaveAttribute('data-theme', 'dark');
await expect(publicPage.getByTestId('shared-note')).toHaveCSS(
'background-color',
'rgb(212, 228, 237)'
);
await context.close();
});
test('Scenario: Copy link button copies to clipboard', async ({
authenticatedPage: page
}) => {