Commit Graph
14 Commits
Author SHA1 Message Date
peter 1955fc87ee refactor(glass): R10 S3a — glass-card notification rows + perf-aware .overlay-backdrop
- NotificationsPanel per-row .glass -> glass-card (the rows are cards, not panel surface).
- New .overlay-backdrop recipe (4px blur = backdrop-blur-xs) with a data-perf override that
  DROPS the blur. The viewport-sized modal scrim blur (Modal/PlayerModal/Welcome) was a raw
  backdrop-blur-xs utility that survived perf mode while every .glass had its blur dropped —
  the most expensive blur in the app. Overlays keep their bg-black/NN.
- gotcha: write ONLY the standard backdrop-filter; a manual -webkit- line makes lightningcss
  drop the standard prop (webkit-only output -> no blur in Firefox).
2026-08-09 17:43:42 +02:00
peter 205f18a2e1 fix(a11y): address R9 code-review findings (glass shadow, focus dedup)
- #1: remove the 9 dead `shadow-*` from glass surfaces instead of letting them override the tuned glass
  box-shadow. The @layer components move made a JSX shadow-* win AND atomically drop the glass inset
  top-edge highlight; deleting the (always-dead) utilities keeps the intended glass depth incl. the
  inset sheen. Verified in-browser: a .glass box-shadow again includes its inset segment.
- #3: drop the per-element focus-visible outlines from the rail chrome buttons and SavedViews action
  buttons — the global button:focus-visible fallback already draws the identical 2px accent ring, so
  they were redundant. Verified: the pin button rings from the global rule alone.
- #5: unify the focus-within outline offset (offset-2) on the SearchBar and ShareDialog pills.
- #2 (double outline on the pill when an inner button is focused): NOT fixed — the clean fix
  (has-[input:focus] so only the input rings the pill) does not generate under Tailwind v4 (measured:
  no rule emitted). Kept the working focus-within, which is a11y-correct (every element still shows a
  focus indicator); the overlapping ring is an accepted cosmetic edge-case.
- #4 (weak ring on solid-fill selected items): deferred to R10 with the selected-paint tokenisation.

Gate green (tsc/eslint/prettier/knip/vitest 79).
2026-08-08 09:28:23 +02:00
peter 70bda98868 build(deps): migrate Tailwind CSS v3.4 → v4
Root-fixes the opacity-modifier seed bug behind R10 S0: the theme colors were
bare `var(--x)` hex with no <alpha-value>, so `bg-accent/40`, `ring-accent/40`,
`border-border/60`, `text-muted/70`, … (~99 sites across 45 files) emitted no
CSS rule at all — latent no-ops. v4 implements the opacity modifier via
`color-mix`, so the modifier now works with the existing hex vars, no per-scheme
token duplication needed.

- `npx @tailwindcss/upgrade`: `@tailwind` directives → `@import "tailwindcss"`,
  JS config → CSS `@theme` (colors, --font-sans, the z-index tokens preserved),
  classname renames (rounded/shadow/blur scale, outline-none→outline-hidden,
  ring→ring-2) across 56 files; PostCSS plugin → @tailwindcss/postcss,
  autoprefixer removed (v4 prefixes for you).
- `@theme` → `@theme inline` (load-bearing): the color vars are redefined at
  runtime under html[data-scheme]/[data-theme]; inline resolves them at the use
  site so per-scheme opacity tints track the active scheme (the shadcn v4 pattern).
- Revert one codemod false-positive: it rewrote the English word "rounded" →
  "rounded-sm" inside a releaseNotes.ts prose sentence.

Gate green: tsc(app+node) / eslint 0-err / prettier / vitest 79 / knip.
Verified in-browser: bg-accent/15 now computes a scheme-tracking color at 0.15
alpha across all 6 schemes × 2 themes; the hand-rolled glass border/shadow/
radius/backdrop-filter render unchanged under v4.
2026-08-08 00:50:05 +02:00
peter a6e3ef8122 feat(player): download works in fullscreen + de-dup the transport controls (R8 S5)
Two R8 S5 (player big-view control-bar) follow-ups:

- Modal now portals into `document.fullscreenElement ?? document.body` (reactive), the
  same fullscreen-aware root the popover platform uses. A node under <body> is painted
  beneath whatever holds the fullscreen lock, so the download dialog — a Modal, opened
  from the player's control bar — would open invisibly in true fullscreen. With that
  fixed, the `!isFullscreen` guard on the player's DownloadButton is dropped, so download
  joins add-to-playlist as working in true fullscreen. (Every Modal is now fullscreen-
  aware; the fix is at the primitive, not the call site.)

- The queue transport cluster (prev / counter / next + auto-advance + loop toggles) was
  duplicated inline in two places — the modal queue bar and the big-view docked control
  bar — with identical logic and only theme + label-visibility differing. Extracted a
  `<PlayerTransportControls variant="modal"|"docked">`; a mode/icon change now lands once.
  Behaviour-preserving (the docked bar's trailing divider and the wrapper rows stay at the
  call sites). Note the modal variant keeps its `bg-accent/15` active tint, which is a
  latent no-op under Tailwind v3's hex --accent — left as-is for R10 S0's styling audit.

Gate green (tsc/eslint/76 vitest). Device-UAT'd in real Chrome: transport controls render
correctly in both the modal queue bar and the docked bar; the download button now shows in
true fullscreen and its dialog portals into the fullscreen stage, fully visible and
centered. No console errors.
2026-07-31 03:24:49 +02:00
peter 7ed98956cc feat(overlay): shared layer-stack for topmost-only Escape across modals, popovers, players (R8 S3)
Replace the three mutually-unaware Escape systems — Modal's private modalStack,
the popovers' document listeners with a blanket stopPropagation, and the two
players' modalCount() checks — with one shared lib/layerStack.ts. Every open
dismissable layer registers in open order; a layer acts on Escape only when it
is topmost, and reads anyLayerAbove() to decide whether to defer. Coordination
is now a stack lookup, not event-propagation order, so it no longer depends on
which listener happens to fire first.

- Modal + useDismiss register via the stack; useDismiss drops the blanket
  stopPropagation (its documented side effect: it swallowed a player's own
  Escape while any popover was open).
- Both players register as a layer and defer via anyLayerAbove() instead of
  modalCount(); PlayerModal checks it before its maximise/fullscreen steps.
  A player MENU is a layer too, so it closes first and the next Escape reaches
  the auto-skip-cancel — fixing the documented PlexPlayer bug.
- AnchoredPopover routes non-hover Escape through the stack (floating-ui's own
  escapeKey off there) and registers as a layer, so a player/modal underneath
  defers once S4 migrates the in-player menus. FloatingTree still owns
  popover-vs-popover ordering; the stack adds the cross-type ordering.
2026-07-30 23:33:50 +02:00
peter 58b9691467 i18n(a11y): translate the modal + Welcome close buttons and the feed fallback (C-3.22)
House policy is HU+EN for every user-facing string. Modal's shared close button was a
hardcoded title="Close" (in every modal) — now t("common.close") + an aria-label.
Welcome's lightbox close aria-label and Feed's "this video" notification fallback are
translated too (new feed.thisVideo, HU+EN). The other Close hardcodes the review
listed were already fixed since.
2026-07-21 23:13:53 +02:00
peter 94dfa6a34b fix(player): Escape coordination, focused-Space, light-theme menus (U-C, U-3.1.4, U-3.3.12)
Three player/overlay interaction fixes (PlexPlayer changes share a file, hence one
commit):
- U-C (Escape double-close): useDismiss's Escape now stopPropagation()s (its document
  listener bubbles before a player's window listener, so one Escape closes only the
  popover, not the popover AND the player). Modal exports modalCount(); both players'
  Escape defers when a Modal is open above them, so Escape closes the dialog, not the
  player under it and then the dialog.
- U-3.1.4: PlexPlayer's Space now defers to a focused BUTTON/A (e.g. "Skip intro")
  instead of toggling playback — matching PlayerModal.
- U-3.3.12: the audio/subtitle menus used theme-adaptive glass-menu + forced
  text-white (white-on-white in light theme); they now use the player's own explicit
  dark panel (border-white/15 + bg-neutral-900/95), readable in both themes.
2026-07-21 22:29:40 +02:00
peter e257e2aca0 style(frontend): Prettier config + one-time repo-wide format pass
Prettier 3 (pinned), printWidth 100, double quotes — chosen to match the existing
hand-formatting and minimise reflow. This commit is ONLY the mechanical format pass
(`prettier --write`) plus the config/ignore/scripts, isolated so it never pollutes a
feature diff. Verified behaviour-neutral: typecheck, eslint (0 errors), and vitest
all green before and after. See .git-blame-ignore-revs — `git blame` skips this sha.
2026-07-21 02:26:40 +02:00
peter c91caffa5e refactor(z-index): name the stacking layers as a token scale
Replace ~48 magic z-index values (z-10..z-[9999] + 2 inline zIndex) with named
tailwind zIndex tokens (base/menu/chrome/paneltab/panel/rail/overlay/popover/
tooltip/tuner). Values are UNCHANGED — a rename, not a renumber — so layering is
identical; the names give one source of truth for the stack.
2026-07-18 19:11:25 +02:00
peter 4963337b04 fix(ui): only close a modal when the backdrop click starts on the backdrop
Pressing the mouse inside a dialog (e.g. to select text in an input), dragging
out, and releasing on the backdrop wrongly dismissed it: the click event fires
on the common ancestor of mousedown+mouseup (the backdrop), so the outside-click
handler ran. Track whether the press started on the backdrop and only close when
it both starts and ends there. Fixes all shared-Modal dialogs at once (downloads
editor/share, confirm, Plex collections + add-to-playlist, …).
2026-07-07 22:28:33 +02:00
peter 795569530b fix(nav): Back steps through sub-views and closes modals before leaving a page
In-app history only tracked the top-level page, so Back from a module sub-view
(e.g. a Messages thread) or with a modal open jumped straight to the previous
module. Add two history primitives: useHistorySubview (a module's sub-view rides
in history.state, so Back returns to its root first) and useBackToClose (a
mounted overlay occupies one history entry; Back closes the topmost, nesting-safe
so a button-close doesn't trip the modals underneath). Apply to the Messages page
views and to PlayerModal + the shared Modal. setPage now pushes a clean entry so
each page starts at its root.
2026-06-26 01:37:43 +02:00
peter b55a944e7c fix(ux): modal error dialog for server-refused actions + ESC closes only the topmost
- Duplicate-tag create/rename hit the uq_tags_user_name constraint and 500'd; now caught and
  returned as a clear 409 ('You already have a tag named …').
- New global error dialog (errorDialog store + ErrorDialog modal) for definitive server refusals;
  the api layer wiring + mount land with the rest of the round.
- Modal now keeps a stack so ESC dismisses only the top modal — an error over the tag editor
  closes the error and leaves the editor open.
2026-06-18 01:17:31 +02:00
peter d46be00d47 feat(ui): glassmorphism phase 1 — frost the app chrome
Apply the real frosted .glass surface (blur) to the few large chrome surfaces — the nav
sidebar, the header and all Modal-based dialogs (was glass-card / plain surfaces) — and
enrich the ambient backdrop a touch (three soft accent pools) so the glass has more to
refract app-wide. Popovers (notifications, language, account, add-to-playlist) were
already glass. Bulk feed cards stay glass-card (no per-card blur) for performance; the
existing perf-mode still disables blur. Phase 2 (ambient thumbnail mosaic / bg image +
toggle) deferred to end-of-project polish.
2026-06-16 01:11:50 +02:00
peter 82f0936ca7 feat(ui): About dialog, Release Notes, and new-version banner
About (in the account menu) shows frontend/backend/database versions + build.
Release Notes renders per-version highlights with a commit-SHA reference; a
dismissible banner appears once after the running build's version changes and
links into the notes. Adds a reusable Modal shell and the release-notes data
(detailed v0.1.0).
2026-06-15 00:06:57 +02:00