diff --git a/VERSION b/VERSION index ef6f7031..4db9f0ca 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.60.5 +0.60.6 diff --git a/frontend/eslint.config.js b/frontend/eslint.config.js index a65aafa3..c8f32bc6 100644 --- a/frontend/eslint.config.js +++ b/frontend/eslint.config.js @@ -48,6 +48,23 @@ export default tseslint.config( "error", { allowShortCircuit: true, allowTernary: true }, ], + // R10 S2: keep the rem system from rotting. A px-frozen `text-[Npx]` font size does NOT scale + // with the Settings --font-scale slider while its rem neighbours do; use `text-2xs`/`text-3xs` + // or a `text-[Nrem]` arbitrary instead. Matches both plain strings and template-literal chunks; + // rem arbitraries (text-[0.75rem]) don't match (no "px"). Font-size `text-` only — not w-/h-/p-. + "no-restricted-syntax": [ + "error", + { + selector: "Literal[value=/text-\\[\\d+px\\]/]", + message: + "px-frozen font size doesn't scale with --font-scale: use text-2xs/text-3xs or text-[Nrem], not text-[Npx] (R10 S2).", + }, + { + selector: "TemplateElement[value.raw=/text-\\[\\d+px\\]/]", + message: + "px-frozen font size doesn't scale with --font-scale: use text-2xs/text-3xs or text-[Nrem], not text-[Npx] (R10 S2).", + }, + ], }, }, prettier, diff --git a/frontend/src/components/ActiveFilterChips.tsx b/frontend/src/components/ActiveFilterChips.tsx index a4fa8e61..625f4235 100644 --- a/frontend/src/components/ActiveFilterChips.tsx +++ b/frontend/src/components/ActiveFilterChips.tsx @@ -21,7 +21,7 @@ export default function ActiveFilterChips({ className="flex flex-wrap items-center gap-1.5 pb-3" aria-label={t("feed.chips.label")} > - + {t("feed.chips.label")} {filters.map((f) => ( diff --git a/frontend/src/components/AdminUsers.tsx b/frontend/src/components/AdminUsers.tsx index d96df65c..31e038cc 100644 --- a/frontend/src/components/AdminUsers.tsx +++ b/frontend/src/components/AdminUsers.tsx @@ -61,9 +61,7 @@ function Badge({ ? "border-warn/40 text-warn" : "border-border text-muted"; return ( - - {children} - + {children} ); } @@ -180,7 +178,7 @@ function UsersRoles({ me }: { me: Me }) { {u.display_name ?? u.email.split("@")[0]} {self && · {t("users.roles.you")}} -
{u.email}
+
{u.email}
{u.role === "admin" && {t("users.roles.admin")}} {u.is_demo && {t("users.roles.demo")}} @@ -436,7 +434,7 @@ function AdminDemo() {
{r.email}
{r.added_by && ( -
+
{t("settings.demo.addedBy", { who: r.added_by })}
)} @@ -521,7 +519,7 @@ function InviteRow({
{inv.email}
{inv.requested_at && ( -
+
{t("settings.invites.requested", { time: new Date(inv.requested_at).toLocaleString(), })} diff --git a/frontend/src/components/AuditLog.tsx b/frontend/src/components/AuditLog.tsx index 6f061e0c..f19b0457 100644 --- a/frontend/src/components/AuditLog.tsx +++ b/frontend/src/components/AuditLog.tsx @@ -84,7 +84,7 @@ export default function AuditLog() { emptyText={t("audit.empty")} /> {truncated && ( -

+

{t("audit.truncated", { returned: data!.returned, total: data!.total })}

)} diff --git a/frontend/src/components/ChannelPicker.tsx b/frontend/src/components/ChannelPicker.tsx index 25957150..e2fd5225 100644 --- a/frontend/src/components/ChannelPicker.tsx +++ b/frontend/src/components/ChannelPicker.tsx @@ -71,7 +71,7 @@ export default function ChannelPicker({ random to anyone who hasn't set a priority. Only while unfiltered: a search reorders nothing but does drop the rows that explain it. */} {!q && all.length > 0 && ( -
{t("sidebar.channelOrder")}
+
{t("sidebar.channelOrder")}
)}
{t("channels.tags.noneYet")}

+

{t("channels.tags.noneYet")}

)}
@@ -238,7 +238,7 @@ export function ChannelTagsControl({ }, [hidden, overflowOpen]); const pillClass = - "shrink-0 text-[10px] px-1.5 py-0.5 rounded-full bg-card text-muted border border-border"; + "shrink-0 text-3xs px-1.5 py-0.5 rounded-full bg-card text-muted border border-border"; return ( // When `bounded`, a FIXED width keeps the cell's footprint constant under `table-layout:auto`: a diff --git a/frontend/src/components/Channels.tsx b/frontend/src/components/Channels.tsx index 1c533ce9..d15b889e 100644 --- a/frontend/src/components/Channels.tsx +++ b/frontend/src/components/Channels.tsx @@ -748,7 +748,7 @@ export default function Channels() { ))}
-

{t("channels.blocked.hint")}

+

{t("channels.blocked.hint")}

)}
@@ -814,7 +814,7 @@ function SyncBadge({ ok, label, hint }: { ok: boolean; label: string; hint?: str return ( diff --git a/frontend/src/components/ChatThread.tsx b/frontend/src/components/ChatThread.tsx index 58f96a5d..8f74741d 100644 --- a/frontend/src/components/ChatThread.tsx +++ b/frontend/src/components/ChatThread.tsx @@ -183,7 +183,7 @@ export default function ChatThread({ }`} > {text} -
+
{relativeTime(m.created_at)}
diff --git a/frontend/src/components/ConfigPanel.tsx b/frontend/src/components/ConfigPanel.tsx index 9c1fe3d6..c51dd554 100644 --- a/frontend/src/components/ConfigPanel.tsx +++ b/frontend/src/components/ConfigPanel.tsx @@ -230,9 +230,7 @@ export default function ConfigPanel() { {t("config.plexTest")}
- {dirty && ( -

{t("config.plexTestDirty")}

- )} + {dirty &&

{t("config.plexTestDirty")}

} {plexResult && (

@@ -243,15 +241,15 @@ export default function ConfigPanel() {

{plexResult.media_check?.checked && (plexResult.media_check.ok ? ( -

{t("config.plexMediaOk")}

+

{t("config.plexMediaOk")}

) : ( -

+

{t("config.plexMediaMissing", { path: plexResult.media_check.local_path ?? "", })}

))} -

{t("config.plexPickLibraries")}

+

{t("config.plexPickLibraries")}

{plexResult.sections.map((s) => ( ))}
-

{t("config.plexPickHint")}

+

{t("config.plexPickHint")}

)}
@@ -348,7 +346,7 @@ function Field({

{t(`config.fields.${item.key}.hint`, "")}

-

{status}

+

{status}

diff --git a/frontend/src/components/DownloadCenter.tsx b/frontend/src/components/DownloadCenter.tsx index 1b4975cb..ed0cbefe 100644 --- a/frontend/src/components/DownloadCenter.tsx +++ b/frontend/src/components/DownloadCenter.tsx @@ -227,7 +227,7 @@ function DownloadRow({
{job.job_kind === "edit" && ( - + {t("downloads.clipBadge")} )} @@ -280,7 +280,7 @@ function DownloadRow({ style={{ width: `${job.progress}%` }} />
-
+
{job.progress}% {job.speed_bps ? {formatSpeed(job.speed_bps)} : null} {job.eta_s ? {formatEta(job.eta_s)} : null} diff --git a/frontend/src/components/GlassTuner.tsx b/frontend/src/components/GlassTuner.tsx index 5c0fb624..fed6ad06 100644 --- a/frontend/src/components/GlassTuner.tsx +++ b/frontend/src/components/GlassTuner.tsx @@ -288,7 +288,7 @@ export default function GlassTuner() { >
◐ Glass Tuner - lab + lab ))}
@@ -351,7 +351,7 @@ export default function GlassTuner() { {tab === "palette" && (
-

+

Overrides the live scheme (inline on <html>). Reset clears these back to the scheme.

@@ -364,7 +364,7 @@ export default function GlassTuner() { className="w-8 h-6 rounded-sm border border-border bg-transparent cursor-pointer" /> {p.label} - {swatches[p.k]} + {swatches[p.k]} ))}
@@ -372,7 +372,7 @@ export default function GlassTuner() { {/* Export */}
-
+
Export → bake in
@@ -176,7 +176,7 @@ function ConversationList({ {previewText(c)} {c.unread > 0 && ( - + {c.unread} )} diff --git a/frontend/src/components/NavSidebar.tsx b/frontend/src/components/NavSidebar.tsx index 1daeedfb..92b54547 100644 --- a/frontend/src/components/NavSidebar.tsx +++ b/frontend/src/components/NavSidebar.tsx @@ -198,12 +198,12 @@ export default function NavSidebar({ }`} > - + {/* Collapsed rail: a numeric badge centred on a circle at the icon corner; the ring matches the row background so it reads cleanly whether the row is active or not. */} {show && slim && ( @@ -214,7 +214,7 @@ export default function NavSidebar({ {!slim && {label}} {show && !slim && ( {badge > 99 ? "99+" : badge} @@ -226,14 +226,14 @@ export default function NavSidebar({ return ( // Layout slot: reserves only the slim footprint when unpinned, so the expanded rail (below) // overlays content instead of pushing it. Pinned reserves the full width (in-flow). -
+