The UI read flat and boring. Reskin to one charm/TUI-inspired layout in two palettes (CSS custom properties): a warm "reader" light theme (sketch B) and a "cozy terminal" dark theme (sketch C). - Palette chosen in cascade order: :root light default; an OS-preference dark block scoped to :root:not([data-theme]) so it applies only absent an explicit choice; and :root[data-theme="dark"|"light"] set by a header toggle that outranks the media query by specificity and persists in localStorage (guarded, degrades to OS default). A <head> init script applies the stored choice before paint, so no flash of the wrong palette. - Charm touches via existing classes (no templ structure churn): monospace meta lines, accent uppercase section dividers with a trailing rule, pill buttons, a lifted/accent-edged expanded card. - Error/danger shades become --err-* tokens so they follow the theme, replacing three per-block prefers-color-scheme dark overrides. - Theme toggle wired into Layout and PublicLayout headers. BDD: docs/use-cases/visual_theme.feature un-pended, mapped in scenarioCoverage. TDD: internal/web/visual_theme_test.go (palettes, OS default, persisted toggle, expanded-card embed). Verified light+dark on list/reader/welcome via web-shot. Sketches kept as the design record. ui-spec.md as-built row added. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
67 lines
2.4 KiB
Go
67 lines
2.4 KiB
Go
package web_test
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// page renders any GET path's full HTML (helper for theme/structure assertions).
|
|
func page(t *testing.T, app interface {
|
|
Router() http.Handler
|
|
}, path string) string {
|
|
t.Helper()
|
|
rec := httptest.NewRecorder()
|
|
app.Router().ServeHTTP(rec, httptest.NewRequest(http.MethodGet, path, nil))
|
|
return rec.Body.String()
|
|
}
|
|
|
|
// TestThemeHasLightAndDarkPalettes: one layout, two palettes via CSS variables —
|
|
// a root light palette and a data-theme="dark" dark palette (ADR-032).
|
|
func TestThemeHasLightAndDarkPalettes(t *testing.T) {
|
|
app := newApp(t)
|
|
resetDB(t, rawPool(t))
|
|
html := page(t, app, "/welcome") // public, no DB needed
|
|
require.Contains(t, html, ":root", "light palette on the root")
|
|
require.Contains(t, html, `[data-theme="dark"]`, "explicit dark palette override for the toggle")
|
|
}
|
|
|
|
// TestThemeFollowsOSPreference: with no stored choice, the OS preference applies
|
|
// the dark palette automatically.
|
|
func TestThemeFollowsOSPreference(t *testing.T) {
|
|
app := newApp(t)
|
|
html := page(t, app, "/welcome")
|
|
require.Contains(t, html, "prefers-color-scheme: dark", "OS-preference dark default")
|
|
}
|
|
|
|
// TestThemeTogglePersists: every page carries a theme toggle control and a small
|
|
// script that flips data-theme and persists the choice.
|
|
func TestThemeTogglePersists(t *testing.T) {
|
|
app := newApp(t)
|
|
html := page(t, app, "/welcome")
|
|
require.Contains(t, html, "theme-toggle", "a theme toggle control")
|
|
require.Contains(t, html, "localStorage", "the choice is persisted")
|
|
require.Contains(t, html, "data-theme", "the toggle flips data-theme")
|
|
}
|
|
|
|
// TestExpandedCardEmbedsVideo: expanding a summarized card with a valid provider id
|
|
// includes the embedded video player (ADR-031/032).
|
|
func TestExpandedCardEmbedsVideo(t *testing.T) {
|
|
ctx := context.Background()
|
|
app := newApp(t)
|
|
p := rawPool(t)
|
|
resetDB(t, p)
|
|
require.NoError(t, deliver(ctx, app, videoX, "summary text"))
|
|
seedVideo(t, p, videoX, "X Title", "https://x", time.Time{})
|
|
|
|
html := body(t, do(t, app, httptest.NewRequest(http.MethodGet, "/v/"+videoX+"/expand", nil)))
|
|
require.Contains(t, html, "<iframe", "expanded card embeds a player")
|
|
require.True(t, strings.Contains(html, "youtube.com/embed") || strings.Contains(html, "youtube-nocookie.com/embed"),
|
|
"the embed is the YouTube player")
|
|
}
|