Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions api/test/nowplaying-data.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ test('plex names the player from the device title', async () => {
title: 'Arrival',
duration: 6000,
viewOffset: 3000,
Player: { state: 'playing', title: 'Living Room TV', product: 'Plex for Apple TV' },
Player: { state: 'playing', title: 'Living Room TV', product: 'Plex for TV' },
},
],
},
Expand Down Expand Up @@ -62,14 +62,14 @@ test('jellyfin prefers DeviceName over Client', async () => {
const r = await dataFn(
ctxFor({ provider: 'jellyfin', jellyfinUrl: 'https://jf.example.com', jellyfinKey: 'k' }, [
{
DeviceName: 'Bedroom iPad',
DeviceName: 'Bedroom Tablet',
Client: 'Jellyfin Web',
NowPlayingItem: { Type: 'Movie', Name: 'Dune', RunTimeTicks: 100 },
PlayState: { PositionTicks: 25, IsPaused: true },
},
]),
);
assert.equal(r.sessions[0].player, 'Bedroom iPad');
assert.equal(r.sessions[0].player, 'Bedroom Tablet');
assert.equal(r.sessions[0].state, 'paused');
assert.equal(r.sessions[0].progress, 0.25);
});
Expand Down
90 changes: 39 additions & 51 deletions docs/design-system.md

Large diffs are not rendered by default.

6 changes: 5 additions & 1 deletion docs/frontend.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,14 @@ The admin writes the whole config with `POST /api/config`; the dashboard reads i

## Browser support

Safari and iOS Safari 16.2 and newer, and current Chrome, Edge and Firefox. 16.2 is where `color-mix()` lands, which the admin's category chips derive their tints from.
WebKit 16.2 and newer, and current Chrome, Edge and Firefox. 16.2 is where `color-mix()` lands, which the admin's category chips derive their tints from.

There is no build step and no autoprefixer, so every vendor prefix is written by hand. The support floor, the list of prefixes that are still needed and the reason each one stays are at the top of `ui/css/tokens.css`; `ui/test/vendor-prefix-ratchet.test.mjs` fails on any prefix outside that list. Note that "an unprefixed property sits beside it" is not a reason to remove one: `-webkit-backdrop-filter` is paired everywhere and still required.

### Platform identifiers

Some vendor names appear in the source as identifiers, not as references. `-apple-system` and `BlinkMacSystemFont` are font-family keywords. `apple-touch-icon` and the `apple-mobile-web-app-*` meta names are web platform names read at add-to-homescreen time. `showLabels.ios` is a stored config key. User-agent patterns match device strings. None of these can be renamed. Prose describes behaviour in neutral terms instead.

## Colour

`ui/css/tokens.css` holds the palette, the roles and the semantic sets. See [docs/design-system.md](design-system.md).
Expand Down
4 changes: 2 additions & 2 deletions nginx/dashboard.conf
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ server {
add_header Content-Type "application/manifest+json" always;
}

# ── iOS apple-touch-icon ──
# Served directly. iOS reads this path at add-to-homescreen time and does not
# ── apple-touch-icon ──
# Served directly. The platform reads this path at add-to-homescreen time and does not
# follow a redirect.
location = /apple-touch-icon.png {
alias /icons/favicon.png;
Expand Down
4 changes: 2 additions & 2 deletions playwright.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
not covered here yet.

Two browsers on purpose. WebKit is not decoration: the documented support
floor is Safari 16.2, and the last two layout and keyboard problems found by
hand were Safari-specific.
floor is WebKit 16.2, and the last two layout and keyboard problems found by
hand were WebKit-specific.

BASE_URL points at a running Stackyard. The workflow starts the built image;
nothing is started here, because the thing worth testing is the image that
Expand Down
4 changes: 2 additions & 2 deletions ui/admin/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
<meta name="theme-color" content="#0d1117">
<title>Stackyard Settings</title>
<script src="/js/mobile-class.js?v=fe95da0d"></script>
<link rel="stylesheet" href="/css/tokens.css?v=cbcdec0a">
<link rel="stylesheet" href="/css/tokens.css?v=f8135c6f">
<link rel="stylesheet" href="/css/admin.css?v=2635f968">
<link rel="stylesheet" href="/css/widget-config-form.css?v=53b91d5f">
</head>
Expand Down Expand Up @@ -288,6 +288,6 @@ <h1 class="sec-title" id="ev-title">General</h1>
</div></div>
</div>

<script type="module" src="/js/admin.js?v=7562a9d2"></script>
<script type="module" src="/js/admin.js?v=4a92bad6"></script>
</body>
</html>
2 changes: 1 addition & 1 deletion ui/css/dashboard.css
Original file line number Diff line number Diff line change
Expand Up @@ -507,7 +507,7 @@
.sra { margin-inline-start:auto; color:rgba(255,255,255,.25); font-size:var(--fs-footnote);line-height:var(--lh-footnote);letter-spacing:var(--tr-footnote)}

/* ── Mobile safe area ──
iOS reports an inset after the first paint and again after a rotation.
The platform reports an inset after the first paint and again after a rotation.
These stay in CSS so the browser keeps them current. Nothing reads them
into JavaScript. */
body.is-mob {
Expand Down
12 changes: 6 additions & 6 deletions ui/css/tokens.css
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* ── Browser support ─────────────────────────────────────────────────────────
Safari and iOS Safari 16.2 and newer, and current Chrome, Edge and Firefox.
WebKit 16.2 and newer, and current Chrome, Edge and Firefox.
16.2 is where color-mix() lands, which the chip tints in admin.css derive
from. There is no build step and no autoprefixer, so every vendor prefix in
this project is hand-written and has to earn its place.
Expand All @@ -8,15 +8,15 @@
set (-webkit-flex, -webkit-align-items, -webkit-justify-content and friends),
-webkit-transform, -webkit-transition, -webkit-animation-*, all of which have
been unprefixed everywhere for a decade, and -webkit-overflow-scrolling,
which iOS has ignored since version 13.
which mobile WebKit has ignored since version 13.

These are the ones that stay, and why. Do not remove them on the grounds that
an unprefixed property sits beside them; for the first two that is exactly
the point.

-webkit-backdrop-filter Safari needed the prefix well past 16.2, so the
-webkit-backdrop-filter WebKit needed the prefix well past 16.2, so the
glass panels flatten without it.
-webkit-text-size-adjust Safari supports only the prefixed form.
-webkit-text-size-adjust WebKit supports only the prefixed form.
-webkit-line-clamp With display:-webkit-box and -webkit-box-orient
-webkit-box-orient this is still the only portable way to clamp
display:-webkit-box text to a number of lines.
Expand All @@ -25,13 +25,13 @@
-webkit-appearance Required inside ::-webkit-slider-thumb.
::-webkit-slider-thumb Pseudo-elements with no standard equivalent, as
::-moz-range-thumb is the Firefox counterpart.
-webkit-user-select Safari only dropped the prefix at 17.
-webkit-user-select WebKit only dropped the prefix at 17.

ui/test/vendor-prefix-ratchet.test.mjs holds this list, so adding a prefix
outside it fails rather than accumulating quietly. */

/* ── Palette ─────────────────────────────────────────────────────────────────
Apple's system colours, dark values. Both pages render on a dark panel or a
System colours from the reference kit, dark values. Both pages render on a dark panel or a
wallpaper, and no stylesheet declares a prefers-color-scheme rule.

Each hue also has a -hi entry, the increased-contrast value for the same
Expand Down
10 changes: 5 additions & 5 deletions ui/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,21 @@
<link rel="manifest" href="/manifest.json">
<meta name="theme-color" content="#0d1117">
<meta name="apple-mobile-web-app-capable" content="yes">
<!-- Not black-translucent. iOS then places the web view at the top of the
<!-- Not black-translucent. The web view is then placed at the top of the
screen but still sizes it to the screen minus the status bar, leaving a
dead strip below the dock in a home-screen install. -->
<meta name="apple-mobile-web-app-status-bar-style" content="default">
<meta name="apple-mobile-web-app-title" content="Stackyard">
<meta name="mobile-web-app-capable" content="yes">

<!-- No sizes attribute: iOS accepts any size and scales it -->
<!-- No sizes attribute: the platform accepts any size and scales it -->
<link rel="apple-touch-icon" href="/icons/favicon.png">
<link rel="icon" type="image/svg+xml" href="/icons/favicon.svg">
<link rel="icon" type="image/png" href="/icons/favicon.png">

<title>Stackyard</title>
<link rel="stylesheet" href="/css/tokens.css?v=cbcdec0a">
<link rel="stylesheet" href="/css/dashboard.css?v=f5117d06">
<link rel="stylesheet" href="/css/tokens.css?v=f8135c6f">
<link rel="stylesheet" href="/css/dashboard.css?v=352a7817">
</head>
<body>
<h1 class="visually-hidden">Stackyard</h1>
Expand Down Expand Up @@ -53,5 +53,5 @@ <h1 class="visually-hidden">Stackyard</h1>
</div>
</div>
</div>
<script type="module" src="/js/dashboard.js?v=537f459e"></script>
<script type="module" src="/js/dashboard.js?v=603a98ae"></script>
</body></html>
2 changes: 1 addition & 1 deletion ui/js/admin.js
Original file line number Diff line number Diff line change
Expand Up @@ -399,7 +399,7 @@ function parseDragData(raw) {
return null;
}

/* Native HTML5 drag does not fire from touch on iOS Safari. The handle needs
/* Native HTML5 drag does not fire from touch on mobile WebKit. The handle needs
touch-action:none (see admin.css) or starting on it scrolls the list. */
function wireTouchDrag(row, handle, { indent, folderId }) {
handle.addEventListener('pointerdown', e => {
Expand Down
4 changes: 2 additions & 2 deletions ui/js/dashboard.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import { html, setHtml, raw } from '/js/html.js?v=c71f8903';
import { initI18n, t, currentLang } from '/js/i18n.js?v=d056c9c5';
import { pwStrength, passwordMismatch } from '/js/password-strength.js?v=42f45ac7';
import { sanitizeItemLinks } from '/js/link-url.js?v=54adb40f';
import { initUI, mkFolder, openFolderDesktop, openFolderMobile, buildMobile } from '/js/ui.js?v=a8e01084';
import { initUI, mkFolder, openFolderDesktop, openFolderMobile, buildMobile } from '/js/ui.js?v=7522fffa';
import { badgeMinimum, badgeSignature, computeBadgeVisual, readBadgeUpdate } from '/js/badge-logic.js?v=e2857764';
import {
configChanged,
Expand Down Expand Up @@ -769,7 +769,7 @@ async function boot() {
buildLayout();
}, MOB);

/* iOS reports its safe-area insets after the first paint, and again after a
/* The platform reports its safe-area insets after the first paint, and again after a
rotation. The probe is sized by them, so its box changing is the signal
that the space the layout was measured against has moved. */
if (typeof ResizeObserver === 'function') {
Expand Down
2 changes: 1 addition & 1 deletion ui/js/mobile-metrics.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
rendering at one physical size whatever page scale the browser reports.

Safe-area insets are not here. The stylesheet holds them, and the layout
measures the box the stylesheet produced. iOS reports an inset after the
measures the box the stylesheet produced. The platform reports an inset after the
first paint, so a value read into JavaScript is stale from the moment it is
taken. */

Expand Down
4 changes: 2 additions & 2 deletions ui/js/ui.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
} from '/js/utils.js?v=b81f6875';
import { t, currentLang } from '/js/i18n.js?v=d056c9c5';
import { trapFocus } from '/js/dialog.js?v=05935547';
import { mobileMetrics } from '/js/mobile-metrics.js?v=4cf70285';
import { mobileMetrics } from '/js/mobile-metrics.js?v=e48d02a8';

let _state = null;
export function initUI(state) {
Expand Down Expand Up @@ -569,7 +569,7 @@ export function buildMobile() {

/* Every size below comes from this box, so the safe-area insets reach the
layout as the space the stylesheet already reserved. Reading the insets
instead would take a value iOS has not reported yet. */
instead would take a value the platform has not reported yet. */
const firstPage = mkPage();
strip.appendChild(firstPage.page);
const gridBox = firstPage.grid.getBoundingClientRect();
Expand Down
4 changes: 2 additions & 2 deletions ui/test/folder-overlay.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

The second defect: the overlay trapped focus without saying what to focus,
the trap fell back to the first focusable element, and that is the first app
link. iOS Safari paints its own focus ring for a script-driven focus on an
link. Mobile WebKit paints its own focus ring for a script-driven focus on an
anchor, which the stylesheet only styles for :focus-visible. Opening a folder
also should not move focus onto an item nobody asked for.

Expand Down Expand Up @@ -101,7 +101,7 @@ test('the native focus ring cannot render on a folder app link', () => {
}
});

/* Desktop Safari showed the same ring the phone did: same trap, same missing
/* Desktop WebKit showed the same ring the phone did: same trap, same missing
plain-focus rule. */
test('the desktop overlay takes focus itself too', () => {
const desktop = ui.slice(ui.indexOf('export function openFolderDesktop('), ui.indexOf('function mFolder('));
Expand Down
4 changes: 2 additions & 2 deletions ui/test/mobile-metrics.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ test('every metric scales with sc', () => {

/* ── The insets belong to the stylesheet ──────────────────────────────────── */

/* iOS reports an inset after the first paint. A value read into JavaScript at
/* The platform reports an inset after the first paint. A value read into JavaScript at
build time is the zero it reported before, and nothing re-reads it. */
test('no metric is computed from a safe-area inset', () => {
const src = read('js/mobile-metrics.js');
Expand All @@ -56,7 +56,7 @@ test('no metric is computed from a safe-area inset', () => {

test('the layout never reads an inset out of the document', () => {
const ui = read('js/ui.js');
assert.doesNotMatch(ui, /safe-area-inset/, 'reading the inset back returns 0 on iOS until it is reported');
assert.doesNotMatch(ui, /safe-area-inset/, 'reading the inset back returns 0 until the platform reports it');
assert.doesNotMatch(ui, /readSafeInsets/);
});

Expand Down
10 changes: 5 additions & 5 deletions ui/test/vendor-prefix-ratchet.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,18 @@ import { fileURLToPath } from 'node:url';
they accumulate, and nothing said which were still doing anything.

P12-1: there were 177, of which 97 were dead at the support floor written at the top
of tokens.css (Safari and iOS Safari 16.2+, current Chrome, Edge and
of tokens.css (WebKit 16.2+, current Chrome, Edge and
Firefox). The tempting test, "does an unprefixed equivalent sit beside
it", is wrong and would have removed -webkit-backdrop-filter, which Safari
it", is wrong and would have removed -webkit-backdrop-filter, which WebKit
needed well past 16.2 and which is paired with the unprefixed property in all
26 places. Membership of this list is a per-property judgement, so the list is
explicit rather than derived.

Adding a prefix that is not here fails. If it genuinely belongs, add it with
a reason in tokens.css and a line here. */
const ALLOWED = new Set([
'-webkit-backdrop-filter' /* Safari needed it well past 16.2 */,
'-webkit-text-size-adjust' /* Safari supports only the prefixed form */,
'-webkit-backdrop-filter' /* WebKit needed it well past 16.2 */,
'-webkit-text-size-adjust' /* WebKit supports only the prefixed form */,
'-webkit-line-clamp' /* the line-clamp idiom, with the two below */,
'-webkit-box-orient',
'-webkit-box' /* as a display value, not the old flexbox */,
Expand All @@ -29,7 +29,7 @@ const ALLOWED = new Set([
'-webkit-appearance' /* required inside ::-webkit-slider-thumb */,
'-webkit-slider-thumb' /* pseudo-element, no standard equivalent */,
'-moz-range-thumb' /* the Firefox counterpart */,
'-webkit-user-select' /* Safari only dropped the prefix at 17 */,
'-webkit-user-select' /* WebKit only dropped the prefix at 17 */,
]);

/* Removed by P12-1. Named so a reintroduction is reported as a regression
Expand Down
2 changes: 1 addition & 1 deletion ui/widgets/backup/backup.html
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@
+ 'Keep it safe.\nExpect the|unexpected.\nWorries don\'t.|Good backups|do.\nPrepare early.|Panic rarely.|Smile.')
).split('\n').map(line => line.split('|'))

/* Safe rounded rect — avoids ctx.roundRect which has iframe bugs in Safari */
/* Safe rounded rect — avoids ctx.roundRect which has iframe bugs in WebKit */
function rrect(ctx, x, y, w, h, r) {
if (w <= 0 || h <= 0) return;
r = Math.min(r, w/2, h/2);
Expand Down
2 changes: 1 addition & 1 deletion ui/widgets/github/contributions.html
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@
const old = widget.querySelector('.graph');
if (old) old.remove();

/* Wait for layout to settle before measuring — critical inside iframes on iOS */
/* Wait for layout to settle before measuring — critical inside iframes on mobile WebKit */
requestAnimationFrame(() => _renderGraph(data, weeks, widget));
}

Expand Down
2 changes: 1 addition & 1 deletion ui/widgets/stats/system-stats.html
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@
if (!cfg) cfg = { widgetType:'stats', widgetSize:'medium',
widgetConfig:{ slots:[{type:'cpu'},{type:'ram'},{type:'disk',primary:'/'}], network:{enabled:false} }};

/* Detect narrow/iOS */
/* Detect a narrow viewport */
if (document.documentElement.clientHeight >= 180) document.body.classList.add('narrow');

buildShell();
Expand Down
Loading