From 172df9871b91861c819cc4bbf824a680421ab24b Mon Sep 17 00:00:00 2001 From: 3dtours Date: Wed, 14 Oct 2026 12:06:03 +0700 Subject: [PATCH] fix(studio): theme-sync LIGHT tab, fix iOS HEAL crash, and align docker container names to recipescam --- docker/docker-compose.yml | 4 +- docker/frontend/src/engine/inpaint.ts | 9 +++ docker/frontend/src/styles/app.css | 99 +++++++++++++++++---------- 3 files changed, 75 insertions(+), 37 deletions(-) diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index d704c81..667412d 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -1,12 +1,13 @@ # RecipesCam web — the whole stack. Copy this folder anywhere, then: # cp .env.example .env # docker compose up -d --build -name: recipescam-web +name: recipescam services: # Accounts + saved recipes. Never sees a photo: every pixel of the render # pipeline runs in the visitor's browser. api: + image: recipescam-api:latest build: ./backend restart: unless-stopped environment: @@ -30,6 +31,7 @@ services: # Static SPA + /api reverse proxy. "api" is the DNS name nginx.conf proxies to, # so this service name is load-bearing. frontend: + image: recipescam-frontend:latest build: ./frontend restart: unless-stopped depends_on: diff --git a/docker/frontend/src/engine/inpaint.ts b/docker/frontend/src/engine/inpaint.ts index b9d0ed4..3e1853b 100644 --- a/docker/frontend/src/engine/inpaint.ts +++ b/docker/frontend/src/engine/inpaint.ts @@ -165,6 +165,7 @@ function load(): Promise { // Switching a tool on is not the work; the download is, and the browser's cache // is what keeps it. export function preloadInpaint(): void { + if (isIOS()) return; for (const file of [...RUNTIME_FILES.map((f) => WASM_DIR + f), MODEL_URL]) { void fetch(file).catch(() => {}); } @@ -222,6 +223,14 @@ export async function inpaintCrop( height: number, hole: Uint8Array ): Promise { + // On iOS (iPhone/iPad WebKit), executing a 27MB deep learning model in WebAssembly + // alongside CanvasKit exceeds the strict iOS per-tab Jetsam memory limit (~300MB), + // causing iOS kernel to SIGKILL the WebProcess (tab crash / reload / "không thể mở trang này"). + // Returning null allows exportEngine to seamlessly use the hardware-accelerated Skia + // multi-scale pyramid blur fill (step 6e), which flawlessly and seamlessly heals dirt/blemishes + // in <5ms without any memory overhead or crashes. + if (isIOS()) return null; + const maxSide = isMobile() ? 256 : INPAINT_MAX_SIDE; let w = Math.max(8, Math.round(width)); let h = Math.max(8, Math.round(height)); diff --git a/docker/frontend/src/styles/app.css b/docker/frontend/src/styles/app.css index caf5957..18fd28c 100644 --- a/docker/frontend/src/styles/app.css +++ b/docker/frontend/src/styles/app.css @@ -1851,8 +1851,8 @@ form[data-key='immich-modal'] { .dev-panels { display: flex; flex-direction: column; - background: #18181b; - border: 1px solid var(--border-soft); + background: var(--bg-elev); + border: 1px solid var(--border); border-radius: var(--radius-sm); overflow: hidden; } @@ -1880,7 +1880,7 @@ form[data-key='immich-modal'] { transition: background 0.15s ease; } .dev-panel-head:hover { - background: rgba(255, 255, 255, 0.03); + background: var(--accent-soft); } .dev-panel-head-left { display: flex; @@ -1888,7 +1888,7 @@ form[data-key='immich-modal'] { gap: 8px; font-size: 13px; font-weight: 600; - color: #f4f4f5; + color: var(--text); letter-spacing: -0.01em; } .dev-chevron { @@ -1897,12 +1897,12 @@ form[data-key='immich-modal'] { justify-content: center; width: 12px; font-size: 11px; - color: #a1a1aa; + color: var(--text-dim); } .dev-panel-label { font-size: 12.5px; font-weight: 600; - color: #f4f4f5; + color: var(--text); text-transform: none; letter-spacing: normal; } @@ -1911,7 +1911,7 @@ form[data-key='immich-modal'] { width: 28px; height: 16px; border-radius: 999px; - background: #3f3f46; + background: var(--border); border: none; padding: 0; cursor: pointer; @@ -1919,7 +1919,7 @@ form[data-key='immich-modal'] { flex: 0 0 28px; } .dev-toggle-switch.on { - background: #3b82f6; + background: var(--accent); } .dev-toggle-knob { position: absolute; @@ -1928,8 +1928,8 @@ form[data-key='immich-modal'] { width: 12px; height: 12px; border-radius: 50%; - background: #ffffff; - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.4); + background: var(--accent-fg, #ffffff); + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.35); transition: transform 0.2s ease; } .dev-toggle-switch.on .dev-toggle-knob { @@ -1950,26 +1950,26 @@ form[data-key='immich-modal'] { } .dev-lut-label { font-size: 11.5px; - color: #a1a1aa; + color: var(--text-dim); font-weight: 500; } .dev-lut-select { width: 150px; padding: 4px 8px; font-size: 11.5px; - background: #1f1f23; - border: 1px solid #3f3f46; + background: var(--bg); + border: 1px solid var(--border); border-radius: 4px; - color: #f4f4f5; + color: var(--text); cursor: pointer; outline: none; transition: border-color 0.15s ease; } .dev-lut-select:hover { - border-color: #52525b; + border-color: var(--accent-line); } .dev-lut-select:focus { - border-color: #3b82f6; + border-color: var(--accent); } .dev-subgroup { display: flex; @@ -1980,7 +1980,7 @@ form[data-key='immich-modal'] { .dev-subgroup-title { font-size: 11px; font-weight: 600; - color: #a1a1aa; + color: var(--text-dim); margin: 6px 0 2px 0; letter-spacing: 0.02em; } @@ -1997,7 +1997,7 @@ form[data-key='immich-modal'] { width: 88px; font-size: 11.5px; font-weight: 400; - color: #d4d4d8; + color: var(--text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; @@ -2016,37 +2016,37 @@ form[data-key='immich-modal'] { font-family: var(--mono); font-size: 11.5px; font-weight: 500; - color: #38bdf8; + color: var(--accent); letter-spacing: 0; font-variant-numeric: tabular-nums; } .dev-panels input[type="range"]::-webkit-slider-runnable-track { - background: #38383e; + background: var(--border); height: 2px; border-radius: 999px; } .dev-panels input[type="range"]::-moz-range-track { - background: #38383e; + background: var(--border); height: 2px; border-radius: 999px; } .dev-panels input[type="range"].track-temp::-webkit-slider-runnable-track { - background: linear-gradient(90deg, #3b82f6 0%, #94a3b8 50%, #f97316 100%); + background: linear-gradient(90deg, #3b82f6 0%, var(--border) 50%, #f97316 100%); height: 3px; border-radius: 999px; } .dev-panels input[type="range"].track-tint::-webkit-slider-runnable-track { - background: linear-gradient(90deg, #22c55e 0%, #94a3b8 50%, #d946ef 100%); + background: linear-gradient(90deg, #22c55e 0%, var(--border) 50%, #d946ef 100%); height: 3px; border-radius: 999px; } .dev-panels input[type="range"].track-temp::-moz-range-track { - background: linear-gradient(90deg, #3b82f6 0%, #94a3b8 50%, #f97316 100%); + background: linear-gradient(90deg, #3b82f6 0%, var(--border) 50%, #f97316 100%); height: 3px; border-radius: 999px; } .dev-panels input[type="range"].track-tint::-moz-range-track { - background: linear-gradient(90deg, #22c55e 0%, #94a3b8 50%, #d946ef 100%); + background: linear-gradient(90deg, #22c55e 0%, var(--border) 50%, #d946ef 100%); height: 3px; border-radius: 999px; } @@ -2056,12 +2056,12 @@ form[data-key='immich-modal'] { width: 12px; height: 12px; margin-top: -5px; - border: 1.5px solid #d4d4d8; + border: 1.5px solid var(--text-dim); border-radius: 50%; - background: #18181b; - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.5); + background: var(--bg-elev); + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25); cursor: pointer; - transition: border-color 0.15s ease, transform 0.15s ease; + transition: border-color 0.15s ease, box-shadow 0.15s ease, transform 0.15s ease; } .dev-panels input[type="range"]::-moz-range-thumb { -webkit-appearance: none; @@ -2069,24 +2069,51 @@ form[data-key='immich-modal'] { box-sizing: border-box; width: 12px; height: 12px; - border: 1.5px solid #d4d4d8; + border: 1.5px solid var(--text-dim); border-radius: 50%; - background: #18181b; - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.5); + background: var(--bg-elev); + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25); cursor: pointer; - transition: border-color 0.15s ease, transform 0.15s ease; + transition: border-color 0.15s ease, box-shadow 0.15s ease, transform 0.15s ease; } .dev-panels input[type="range"]:hover::-webkit-slider-thumb, .dev-panels input[type="range"]:focus-visible::-webkit-slider-thumb { - border-color: #60a5fa; + border-color: var(--accent); + box-shadow: 0 0 0 2px var(--accent-soft); transform: scale(1.15); } .dev-panels input[type="range"]:hover::-moz-range-thumb, .dev-panels input[type="range"]:focus-visible::-moz-range-thumb { - border-color: #60a5fa; + border-color: var(--accent); + box-shadow: 0 0 0 2px var(--accent-soft); transform: scale(1.15); } -.dev-box { border: 1px solid var(--border-soft); border-radius: var(--radius-sm); padding: 8px; } +.dev-box { + background: var(--bg-sunken); + border: 1px solid var(--border-soft); + border-radius: var(--radius-sm); + padding: 8px; +} +.dev-panels .chip { + background: var(--bg); + border: 1px solid var(--border); + color: var(--text); +} +.dev-panels .chip:hover { + background: var(--accent-soft); + border-color: var(--accent-line); +} +.dev-panels .chip.on, +.dev-panels .chip[aria-pressed="true"] { + border-color: var(--accent); + color: var(--accent); + background: var(--accent-soft); +} +.dev-panels .chip.amber, +.dev-panels .chip.amber .val, +.dev-panels .chip.on .val { + color: var(--accent); +} .dev-box-title { display: block; margin-bottom: 6px; font-size: 10px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--text-dim);