Make the lock-screen web-server warning dismissible

- The security banner (shown when opened over http(s), not file://) now has a
  close button.
- Dismissal persists via the settings store (dismissedLocalWarning), so it
  stays hidden across reloads on this browser, while still appearing fresh by
  default.
- Rephrased text kept; banner made flex with a hoverable, accessible close
  button.
This commit is contained in:
hermes-explorigin 2026-08-27 01:18:28 +00:00
parent c9a42c4670
commit 59d903fbd7
3 changed files with 100 additions and 13 deletions

65
dist/index.html vendored
View File

@ -5810,11 +5810,20 @@ var SettingsStore = class {
set lockOnTabSwitch(value) { set lockOnTabSwitch(value) {
set(this.#lockOnTabSwitch, value, true); set(this.#lockOnTabSwitch, value, true);
} }
#dismissedLocalWarning = /* @__PURE__ */ state(false);
get dismissedLocalWarning() {
return get(this.#dismissedLocalWarning);
}
set dismissedLocalWarning(value) {
set(this.#dismissedLocalWarning, value, true);
}
async load() { async load() {
const minutes = await getSetting("autoLockMinutes"); const minutes = await getSetting("autoLockMinutes");
const tabSwitch = await getSetting("lockOnTabSwitch"); const tabSwitch = await getSetting("lockOnTabSwitch");
const dismissedWarning = await getSetting("dismissedLocalWarning");
this.autoLockMinutes = minutes != null ? Number(minutes) : 5; this.autoLockMinutes = minutes != null ? Number(minutes) : 5;
this.lockOnTabSwitch = tabSwitch != null ? Boolean(tabSwitch) : true; this.lockOnTabSwitch = tabSwitch != null ? Boolean(tabSwitch) : true;
this.dismissedLocalWarning = dismissedWarning != null ? Boolean(dismissedWarning) : false;
} }
/** /**
* Persist current settings to IndexedDB. * Persist current settings to IndexedDB.
@ -5822,6 +5831,7 @@ var SettingsStore = class {
async save() { async save() {
await saveSetting("autoLockMinutes", this.autoLockMinutes); await saveSetting("autoLockMinutes", this.autoLockMinutes);
await saveSetting("lockOnTabSwitch", this.lockOnTabSwitch); await saveSetting("lockOnTabSwitch", this.lockOnTabSwitch);
await saveSetting("dismissedLocalWarning", this.dismissedLocalWarning);
} }
}; };
var settings = new SettingsStore(); var settings = new SettingsStore();
@ -5927,7 +5937,7 @@ function autofocus(node, condition = true) {
} }
//#endregion //#endregion
//#region src/components/LockScreen.svelte //#region src/components/LockScreen.svelte
var root_1$7 = /* @__PURE__ */ from_html(`<div class="warning-banner svelte-7sq1ct" role="alert">You're viewing this from a web server. For best security, download this HTML file and open it locally on your computer instead.</div>`); var root_1$7 = /* @__PURE__ */ from_html(`<div class="warning-banner svelte-7sq1ct" role="alert"><span class="warning-text svelte-7sq1ct">You're viewing this from a web server. For best security, download this HTML file and open it locally on your computer instead.</span> <button class="warning-dismiss svelte-7sq1ct" aria-label="Dismiss warning"></button></div>`);
var root_2$6 = /* @__PURE__ */ from_html(`<div class="error-banner svelte-7sq1ct" role="alert"> </div>`); var root_2$6 = /* @__PURE__ */ from_html(`<div class="error-banner svelte-7sq1ct" role="alert"> </div>`);
var root_3$6 = /* @__PURE__ */ from_html(`<div class="form-group"><label for="confirm-password">Confirm Password</label> <input id="confirm-password" type="password" placeholder="Confirm master password" autocomplete="new-password"/></div>`); var root_3$6 = /* @__PURE__ */ from_html(`<div class="form-group"><label for="confirm-password">Confirm Password</label> <input id="confirm-password" type="password" placeholder="Confirm master password" autocomplete="new-password"/></div>`);
var root$7 = /* @__PURE__ */ from_html(`<div class="lock-screen svelte-7sq1ct"><div class="lock-card svelte-7sq1ct"><div class="lock-icon svelte-7sq1ct">🔐</div> <h1 class="svelte-7sq1ct">Password Vault</h1> <p class="subtitle svelte-7sq1ct"> </p> <!> <!> <form class="lock-form svelte-7sq1ct"><div class="form-group"><label for="master-password">Master Password</label> <input id="master-password" type="password" placeholder="Enter master password" autocomplete="current-password"/></div> <!> <button type="submit" class="btn btn-primary w-full"> </button></form> <p class="hint svelte-7sq1ct"> </p></div></div>`); var root$7 = /* @__PURE__ */ from_html(`<div class="lock-screen svelte-7sq1ct"><div class="lock-card svelte-7sq1ct"><div class="lock-icon svelte-7sq1ct">🔐</div> <h1 class="svelte-7sq1ct">Password Vault</h1> <p class="subtitle svelte-7sq1ct"> </p> <!> <!> <form class="lock-form svelte-7sq1ct"><div class="form-group"><label for="master-password">Master Password</label> <input id="master-password" type="password" placeholder="Enter master password" autocomplete="current-password"/></div> <!> <button type="submit" class="btn btn-primary w-full"> </button></form> <p class="hint svelte-7sq1ct"> </p></div></div>`);
@ -5943,6 +5953,16 @@ function LockScreen($$anchor, $$props) {
set(isSetup, !await isVaultInitialized()); set(isSetup, !await isVaultInitialized());
} }
checkVault(); checkVault();
let showLocalWarning = /* @__PURE__ */ state(true);
async function dismissLocalWarning() {
settings.dismissedLocalWarning = true;
set(showLocalWarning, false);
try {
await settings.save();
} catch (e) {
console.warn("Failed to persist warning dismissal:", e);
}
}
async function handleSubmit() { async function handleSubmit() {
set(error, ""); set(error, "");
set(loading, true); set(loading, true);
@ -6000,10 +6020,14 @@ function LockScreen($$anchor, $$props) {
reset(p); reset(p);
var node = sibling(p, 2); var node = sibling(p, 2);
var consequent = ($$anchor) => { var consequent = ($$anchor) => {
append($$anchor, root_1$7()); var div_2 = root_1$7();
var button = sibling(child(div_2), 2);
reset(div_2);
delegated("click", button, dismissLocalWarning);
append($$anchor, div_2);
}; };
if_block(node, ($$render) => { if_block(node, ($$render) => {
if (get(notLocal)) $$render(consequent); if (get(notLocal) && get(showLocalWarning) && !settings.dismissedLocalWarning) $$render(consequent);
}); });
var node_1 = sibling(node, 2); var node_1 = sibling(node, 2);
var consequent_1 = ($$anchor) => { var consequent_1 = ($$anchor) => {
@ -6036,9 +6060,9 @@ function LockScreen($$anchor, $$props) {
if_block(node_2, ($$render) => { if_block(node_2, ($$render) => {
if (get(isSetup)) $$render(consequent_2); if (get(isSetup)) $$render(consequent_2);
}); });
var button = sibling(node_2, 2); var button_1 = sibling(node_2, 2);
var text_2 = child(button, true); var text_2 = child(button_1, true);
reset(button); reset(button_1);
reset(form); reset(form);
var p_1 = sibling(form, 2); var p_1 = sibling(form, 2);
var text_3 = child(p_1, true); var text_3 = child(p_1, true);
@ -6048,7 +6072,7 @@ function LockScreen($$anchor, $$props) {
template_effect(() => { template_effect(() => {
set_text(text, get(isSetup) ? "Create your vault" : "Unlock your vault"); set_text(text, get(isSetup) ? "Create your vault" : "Unlock your vault");
input.disabled = get(loading); input.disabled = get(loading);
button.disabled = get(loading); button_1.disabled = get(loading);
set_text(text_2, get(loading) ? "Processing..." : get(isSetup) ? "Create Vault" : "Unlock"); set_text(text_2, get(loading) ? "Processing..." : get(isSetup) ? "Create Vault" : "Unlock");
set_text(text_3, get(isSetup) ? "Your master password encrypts all data locally. It cannot be recovered if lost." : "Your data is encrypted with AES-256-GCM. Key is stored only in memory."); set_text(text_3, get(isSetup) ? "Your master password encrypts all data locally. It cannot be recovered if lost." : "Your data is encrypted with AES-256-GCM. Key is stored only in memory.");
}); });
@ -6059,6 +6083,7 @@ function LockScreen($$anchor, $$props) {
append($$anchor, div); append($$anchor, div);
pop(); pop();
} }
delegate(["click"]);
//#endregion //#endregion
//#region src/lib/stores/search.svelte.js //#region src/lib/stores/search.svelte.js
var DEBOUNCE_MS = 300; var DEBOUNCE_MS = 300;
@ -8204,13 +8229,35 @@ label {
.warning-banner.svelte-7sq1ct { .warning-banner.svelte-7sq1ct {
width: 100%; width: 100%;
padding: 10px 14px; display: flex;
align-items: flex-start;
gap: 8px;
padding: 10px 10px 10px 14px;
background: rgba(255, 193, 7, 0.15); background: rgba(255, 193, 7, 0.15);
border: 1px solid rgba(230, 168, 0, 0.5); border: 1px solid rgba(230, 168, 0, 0.5);
border-radius: var(--radius-md); border-radius: var(--radius-md);
color: #b8860b; color: #b8860b;
font-size: 0.85rem; font-size: 0.85rem;
text-align: center; }
.warning-text.svelte-7sq1ct {
flex: 1;
text-align: left;
}
.warning-dismiss.svelte-7sq1ct {
flex-shrink: 0;
border: none;
background: transparent;
color: inherit;
font-size: 0.9rem;
line-height: 1;
cursor: pointer;
padding: 0 2px;
opacity: 0.7;
}
.warning-dismiss.svelte-7sq1ct:hover {
opacity: 1;
} }
.hint.svelte-7sq1ct { .hint.svelte-7sq1ct {

View File

@ -19,6 +19,16 @@
} }
checkVault() checkVault()
// Dismissed warning state loads from the settings store (which loads on
// unlock/setup). Track locally so the banner hides immediately on click.
let showLocalWarning = $state(true)
async function dismissLocalWarning() {
settings.dismissedLocalWarning = true
showLocalWarning = false
try { await settings.save() } catch (e) { console.warn('Failed to persist warning dismissal:', e) }
}
async function handleSubmit() { async function handleSubmit() {
error = '' error = ''
loading = true loading = true
@ -88,8 +98,11 @@
<h1>Password Vault</h1> <h1>Password Vault</h1>
<p class="subtitle">{isSetup ? 'Create your vault' : 'Unlock your vault'}</p> <p class="subtitle">{isSetup ? 'Create your vault' : 'Unlock your vault'}</p>
{#if notLocal} {#if notLocal && showLocalWarning && !settings.dismissedLocalWarning}
<div class="warning-banner" role="alert">This HTML file is intended for offline use.</div> <div class="warning-banner" role="alert">
<span class="warning-text">You're viewing this from a web server. For best security, download this HTML file and open it locally on your computer instead.</span>
<button class="warning-dismiss" onclick={dismissLocalWarning} aria-label="Dismiss warning"></button>
</div>
{/if} {/if}
{#if error} {#if error}
@ -196,13 +209,35 @@
.warning-banner { .warning-banner {
width: 100%; width: 100%;
padding: 10px 14px; display: flex;
align-items: flex-start;
gap: 8px;
padding: 10px 10px 10px 14px;
background: rgba(255, 193, 7, 0.15); background: rgba(255, 193, 7, 0.15);
border: 1px solid rgba(230, 168, 0, 0.5); border: 1px solid rgba(230, 168, 0, 0.5);
border-radius: var(--radius-md); border-radius: var(--radius-md);
color: #b8860b; color: #b8860b;
font-size: 0.85rem; font-size: 0.85rem;
text-align: center; }
.warning-text {
flex: 1;
text-align: left;
}
.warning-dismiss {
flex-shrink: 0;
border: none;
background: transparent;
color: inherit;
font-size: 0.9rem;
line-height: 1;
cursor: pointer;
padding: 0 2px;
opacity: 0.7;
}
.warning-dismiss:hover {
opacity: 1;
} }
.hint { .hint {

View File

@ -10,6 +10,8 @@ import { getSetting, saveSetting } from '../storage/db.js'
export class SettingsStore { export class SettingsStore {
autoLockMinutes = $state(5) autoLockMinutes = $state(5)
lockOnTabSwitch = $state(true) lockOnTabSwitch = $state(true)
// User dismissed the "running from a web server" warning banner on the lock screen.
dismissedLocalWarning = $state(false)
/** /**
* Load persisted settings from IndexedDB. * Load persisted settings from IndexedDB.
@ -18,9 +20,11 @@ export class SettingsStore {
async load() { async load() {
const minutes = await getSetting('autoLockMinutes') const minutes = await getSetting('autoLockMinutes')
const tabSwitch = await getSetting('lockOnTabSwitch') const tabSwitch = await getSetting('lockOnTabSwitch')
const dismissedWarning = await getSetting('dismissedLocalWarning')
this.autoLockMinutes = minutes != null ? Number(minutes) : 5 this.autoLockMinutes = minutes != null ? Number(minutes) : 5
this.lockOnTabSwitch = tabSwitch != null ? Boolean(tabSwitch) : true this.lockOnTabSwitch = tabSwitch != null ? Boolean(tabSwitch) : true
this.dismissedLocalWarning = dismissedWarning != null ? Boolean(dismissedWarning) : false
} }
/** /**
@ -29,6 +33,7 @@ export class SettingsStore {
async save() { async save() {
await saveSetting('autoLockMinutes', this.autoLockMinutes) await saveSetting('autoLockMinutes', this.autoLockMinutes)
await saveSetting('lockOnTabSwitch', this.lockOnTabSwitch) await saveSetting('lockOnTabSwitch', this.lockOnTabSwitch)
await saveSetting('dismissedLocalWarning', this.dismissedLocalWarning)
} }
} }