mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-07 15:09:30 +00:00
fix: guard dismissal honesty, one threshold source, EXIF proven (#39 r1)
r1-M1: dismissal (Escape/scrim/Cancel) while the reduce or keep-original flow is executing no longer races the decision — hp-close is ignored while busy, and every flow re-checks it still owns the guard before applying, so a force-cleared dialog can never silently install its stale result. The smoke now drives both: hp-close during a hanging decode leaves the busy dialog up, and a force-cleared guard ends with clean staging, no toast, no planFile. A new registry mutant removes the busy gate and is killed. r1-M2: the hard-dialog text takes its limit from the imported HARD_DIMENSION instead of a literal — recalibration stays a one-file change, as the spec promises. r1-M3: AC8 is now proven end to end, not plausible: the smoke splices a real EXIF APP1 (orientation 6) into a canvas-encoded 8200×4100 JPEG, asserts the header probe reads the unrotated SOF, that the decode call carries imageOrientation:'from-image' (captured on the hook), and that the reduced copy comes out portrait 2048×4096. TESTING.md names the scenario. Issue: #39 User-Visible: no
This commit is contained in:
+15
-4
@@ -7,7 +7,7 @@
|
||||
import { html, type TemplateResult } from 'lit';
|
||||
import {
|
||||
DOWNSCALE_JPEG_QUALITY, DOWNSCALE_TARGET_PX, DOWNSCALE_TIMEOUT_MS,
|
||||
downscaleDimensions, probeBackdrop, type BackdropProbe,
|
||||
HARD_DIMENSION, downscaleDimensions, probeBackdrop, type BackdropProbe,
|
||||
} from './backdrop-probe';
|
||||
import type { I18nKey } from './i18n';
|
||||
|
||||
@@ -166,7 +166,7 @@ export function renderBackdropGuard(
|
||||
const hard = probe.kind === 'hard';
|
||||
const body = hard
|
||||
? host._t('backdrop.too_large_body', {
|
||||
w: probe.width ?? 0, h: probe.height ?? 0, limit: 16384,
|
||||
w: probe.width ?? 0, h: probe.height ?? 0, limit: HARD_DIMENSION,
|
||||
})
|
||||
: probe.kind === 'unknown'
|
||||
? host._t('backdrop.unknown_body')
|
||||
@@ -176,9 +176,18 @@ export function renderBackdropGuard(
|
||||
fileMb: megabytes(guard.file.size),
|
||||
decodedMb: megabytes(probe.decodedBytes ?? 0),
|
||||
});
|
||||
// r1-M1: while a decision is executing, dismissal must not race it — the
|
||||
// dialog stays up (buttons are disabled), and even if the guard somehow
|
||||
// vanished mid-flight, a stale flow must not apply its result silently.
|
||||
const dismiss = (): void => {
|
||||
if (host._backdropGuard?.busy) return;
|
||||
close();
|
||||
};
|
||||
const stillCurrent = (): boolean => host._backdropGuard?.file === guard.file;
|
||||
const original = async (): Promise<void> => {
|
||||
if (host._backdropGuard?.busy) return;
|
||||
const payload = await encodePlanFile(guard.file, guard.ext, guard.file.name);
|
||||
if (!stillCurrent()) return;
|
||||
apply(payload);
|
||||
close();
|
||||
};
|
||||
@@ -189,21 +198,23 @@ export function renderBackdropGuard(
|
||||
try {
|
||||
const out = await downscaleBackdrop(guard);
|
||||
const payload = await encodePlanFile(out.blob, out.ext, out.name);
|
||||
if (!stillCurrent()) return;
|
||||
apply(payload);
|
||||
close();
|
||||
} catch {
|
||||
// Honest phase 2 (spec §UX): no silent fallback to the original the
|
||||
// user just declined — staging stays clean, the toast says what happened.
|
||||
if (!stillCurrent()) return;
|
||||
close();
|
||||
host._showToast(host._t('backdrop.downscale_failed'));
|
||||
}
|
||||
};
|
||||
return html`<hp-dialog .hass=${hass}
|
||||
.title=${host._t(hard ? 'backdrop.too_large_title' : 'backdrop.large_title')}
|
||||
icon="mdi:image-size-select-large" dismiss-on-scrim @hp-close=${() => close()}>
|
||||
icon="mdi:image-size-select-large" dismiss-on-scrim @hp-close=${() => dismiss()}>
|
||||
<div class="body"><p>${body}</p></div>
|
||||
<div class="row" slot="footer">
|
||||
<button class="btn ghost" ?disabled=${guard.busy} @click=${() => close()}>
|
||||
<button class="btn ghost" ?disabled=${guard.busy} @click=${() => dismiss()}>
|
||||
${host._t('btn.cancel')}</button>
|
||||
<span class="spacer"></span>
|
||||
${hard ? null : html`
|
||||
|
||||
Reference in New Issue
Block a user