From 5f251923f03ccbcfdb8180deacc8aa1cb09a4c20 Mon Sep 17 00:00:00 2001 From: Ralksta <16941050+ralksta@users.noreply.github.com> Date: Sun, 27 Sep 2026 16:39:34 +0200 Subject: [PATCH] release: v0.18.1 (#734) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Thanks - [@RichKidsDev](https://github.com/RichKidsDev) for reporting the admin layout on iPhone (#732). ### Fixed - **The admin fits a phone** ([#733](https://github.com/ralksta/immich-folio/pull/733), reported in [#732](https://github.com/ralksta/immich-folio/issues/732) by [@RichKidsDev](https://github.com/RichKidsDev)). Below 860px the sidebar unfolded above the content — navigation, status and four actions — and filled most of an iPhone screen. It is now a header row with the brand and a menu button; the menu opens as one column of full-width rows and closes again on navigation, and an unread message shows as a dot on the button. Desktop is unchanged. - **The settings editor refuses prototype keys in a setting's path** ([#731](https://github.com/ralksta/immich-folio/pull/731)). All paths are written in the code, so this was not reachable; CodeQL flagged the setter after the v0.18.0 release. --------- Signed-off-by: dependabot[bot] Co-authored-by: ralksta Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Claude Opus 5 Co-authored-by: Ralf Co-authored-by: Paul Thies <110185070+ImScheinox@users.noreply.github.com> Co-authored-by: lancetm714 <108419425+lancetm714@users.noreply.github.com> Co-authored-by: Lance Martin --- CHANGELOG.md | 19 ++ README.md | 2 +- app/admin/components/AdminDashboard.tsx | 355 +++++++++++++----------- app/admin/components/SettingsEditor.tsx | 6 + app/admin/styles/redesign.css | 77 +++++ package-lock.json | 4 +- package.json | 2 +- 7 files changed, 301 insertions(+), 164 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 50ba7c6..7212aa2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 Releases up to and including v0.9.2 are documented in the [GitHub releases](https://github.com/ralksta/immich-folio/releases). +## [0.18.1] — 2026-09-27 + +### Fixed + +- **The admin fits a phone** + ([#733](https://github.com/ralksta/immich-folio/pull/733), reported in + [#732](https://github.com/ralksta/immich-folio/issues/732) by + [@RichKidsDev](https://github.com/RichKidsDev)). Below 860px the sidebar + unfolded above the content — navigation, status and four actions — and + filled most of an iPhone screen. It is now a header row with the brand and + a menu button; the menu opens as one column of full-width rows and closes + again on navigation, and an unread message shows as a dot on the button. + Desktop is unchanged. + +- **The settings editor refuses prototype keys in a setting's path** + ([#731](https://github.com/ralksta/immich-folio/pull/731)). All paths are + written in the code, so this was not reachable; CodeQL flagged the setter + after the v0.18.0 release. + ## [0.18.0] — 2026-09-27 ### Security diff --git a/README.md b/README.md index e4c8ba5..528bfd3 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ A self-hosted portfolio powered by [Immich](https://immich.app). It acts as a **secure reverse proxy** between your visitors and your private Immich instance: your Immich server stays on your local network, completely invisible to the outside world, while your albums are published as a gallery you control. -**Latest: v0.18.0** — built-in contact form, privacy page and a reworked admin panel. → [What's New](#whats-new) +**Latest: v0.18.1** — built-in contact form, privacy page and a reworked admin panel. → [What's New](#whats-new) ## Contents diff --git a/app/admin/components/AdminDashboard.tsx b/app/admin/components/AdminDashboard.tsx index 1f51912..71a12e6 100644 --- a/app/admin/components/AdminDashboard.tsx +++ b/app/admin/components/AdminDashboard.tsx @@ -112,6 +112,11 @@ const SIDEBAR_KEY = 'folio_admin_sidebar'; export default function AdminDashboard({ onLogout, children }: Props) { const notify = useNotify(); const pathname = usePathname(); + // Phones: the sidebar folds into a header row, and the nav opens from a + // menu button (#732) — spread out above the content, it took most of an + // iPhone screen before anything could be edited. + const [menuOpen, setMenuOpen] = useState(false); + useEffect(() => setMenuOpen(false), [pathname]); const [saving, setSaving] = useState(false); // Diagnostics & Backup state @@ -267,7 +272,11 @@ export default function AdminDashboard({ onLogout, children }: Props) { const missingCredentials = status?.setup?.credentials === 'missing'; return ( -
+
diff --git a/app/admin/components/SettingsEditor.tsx b/app/admin/components/SettingsEditor.tsx index c28d063..8ed63d8 100644 --- a/app/admin/components/SettingsEditor.tsx +++ b/app/admin/components/SettingsEditor.tsx @@ -188,6 +188,12 @@ export default function SettingsEditor() { setSettings((s) => { const copy = JSON.parse(JSON.stringify(s)); const parts = path.split('.'); + // Paths are literals in the section components, but a key like + // `__proto__` would write through to Object.prototype (CodeQL + // js/prototype-pollution-utility). + if (parts.some((p) => p === '__proto__' || p === 'constructor' || p === 'prototype')) { + return s; + } let obj = copy; for (let i = 0; i < parts.length - 1; i++) { if (!obj[parts[i]]) obj[parts[i]] = {}; diff --git a/app/admin/styles/redesign.css b/app/admin/styles/redesign.css index 2664d82..6902036 100644 --- a/app/admin/styles/redesign.css +++ b/app/admin/styles/redesign.css @@ -1247,3 +1247,80 @@ display: none; } } + +/* ── Phone header (#732) ───────────────────────────────────── */ + +/* On a phone the sidebar used to unfold above the content — nav, status, + and four actions — and filled most of an iPhone screen. It is now a + header row with the brand and a menu button; the rest opens on demand. */ +.admin-menu-toggle { + display: none; +} + +@media (max-width: 860px) { + .admin-sidebar { + display: grid; + grid-template-columns: 1fr auto; + align-items: center; + gap: 0; + padding: 0.6rem 1rem; + } + + .admin-sidebar .admin-brand { + margin: 0; + } + + .admin-menu-toggle { + position: relative; + display: inline-grid; + place-items: center; + width: 40px; + height: 40px; + border: 1px solid var(--admin-border); + border-radius: var(--admin-radius); + background: transparent; + color: var(--admin-text); + cursor: pointer; + } + + .admin-menu-toggle:focus-visible { + outline: var(--admin-focus-ring); + outline-offset: 2px; + } + + .admin-menu-dot { + position: absolute; + top: 7px; + right: 7px; + width: 7px; + height: 7px; + border-radius: 50%; + background: var(--admin-accent); + } + + .admin-sidebar-menu { + display: none; + grid-column: 1 / -1; + padding-top: 0.75rem; + } + + .admin-app.menu-open .admin-sidebar-menu { + display: block; + } + + .admin-app.menu-open .admin-sidebar { + max-height: 100dvh; + overflow-y: auto; + } + + /* One column: tap targets of a full row, not a wrapped cloud of links. */ + .admin-nav { + flex-direction: column; + flex-wrap: nowrap; + gap: 0; + } + + .admin-nav .admin-nav-item { + min-height: 44px; + } +} diff --git a/package-lock.json b/package-lock.json index dda3413..285867a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "immich-folio", - "version": "0.18.0", + "version": "0.18.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "immich-folio", - "version": "0.18.0", + "version": "0.18.1", "license": "MIT", "dependencies": { "@dnd-kit/core": "^6.3.1", diff --git a/package.json b/package.json index 9d52bae..fc8c5de 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "immich-folio", - "version": "0.18.0", + "version": "0.18.1", "license": "MIT", "scripts": { "dev": "next dev",