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(this.#lockOnTabSwitch, value, true);
}
#dismissedLocalWarning = /* @__PURE__ */ state(false);
get dismissedLocalWarning() {
return get(this.#dismissedLocalWarning);
}
set dismissedLocalWarning(value) {
set(this.#dismissedLocalWarning, value, true);
}
async load() {
const minutes = await getSetting("autoLockMinutes");
const tabSwitch = await getSetting("lockOnTabSwitch");
const dismissedWarning = await getSetting("dismissedLocalWarning");
this.autoLockMinutes = minutes != null ? Number(minutes) : 5;
this.lockOnTabSwitch = tabSwitch != null ? Boolean(tabSwitch) : true;
this.dismissedLocalWarning = dismissedWarning != null ? Boolean(dismissedWarning) : false;
}
/**
* Persist current settings to IndexedDB.
@ -5822,6 +5831,7 @@ var SettingsStore = class {
async save() {
await saveSetting("autoLockMinutes", this.autoLockMinutes);
await saveSetting("lockOnTabSwitch", this.lockOnTabSwitch);
await saveSetting("dismissedLocalWarning", this.dismissedLocalWarning);
}
};
var settings = new SettingsStore();
@ -5927,7 +5937,7 @@ function autofocus(node, condition = true) {
}
//#endregion
//#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_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>`);
@ -5943,6 +5953,16 @@ function LockScreen($$anchor, $$props) {
set(isSetup, !await isVaultInitialized());
}
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() {
set(error, "");
set(loading, true);
@ -6000,10 +6020,14 @@ function LockScreen($$anchor, $$props) {
reset(p);
var node = sibling(p, 2);
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 (get(notLocal)) $$render(consequent);
if (get(notLocal) && get(showLocalWarning) && !settings.dismissedLocalWarning) $$render(consequent);
});
var node_1 = sibling(node, 2);
var consequent_1 = ($$anchor) => {
@ -6036,9 +6060,9 @@ function LockScreen($$anchor, $$props) {
if_block(node_2, ($$render) => {
if (get(isSetup)) $$render(consequent_2);
});
var button = sibling(node_2, 2);
var text_2 = child(button, true);
reset(button);
var button_1 = sibling(node_2, 2);
var text_2 = child(button_1, true);
reset(button_1);
reset(form);
var p_1 = sibling(form, 2);
var text_3 = child(p_1, true);
@ -6048,7 +6072,7 @@ function LockScreen($$anchor, $$props) {
template_effect(() => {
set_text(text, get(isSetup) ? "Create your vault" : "Unlock your vault");
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_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);
pop();
}
delegate(["click"]);
//#endregion
//#region src/lib/stores/search.svelte.js
var DEBOUNCE_MS = 300;
@ -8204,13 +8229,35 @@ label {
.warning-banner.svelte-7sq1ct {
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);
border: 1px solid rgba(230, 168, 0, 0.5);
border-radius: var(--radius-md);
color: #b8860b;
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 {

View File

@ -19,6 +19,16 @@
}
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() {
error = ''
loading = true
@ -88,8 +98,11 @@
<h1>Password Vault</h1>
<p class="subtitle">{isSetup ? 'Create your vault' : 'Unlock your vault'}</p>
{#if notLocal}
<div class="warning-banner" role="alert">This HTML file is intended for offline use.</div>
{#if notLocal && showLocalWarning && !settings.dismissedLocalWarning}
<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 error}
@ -196,13 +209,35 @@
.warning-banner {
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);
border: 1px solid rgba(230, 168, 0, 0.5);
border-radius: var(--radius-md);
color: #b8860b;
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 {

View File

@ -10,6 +10,8 @@ import { getSetting, saveSetting } from '../storage/db.js'
export class SettingsStore {
autoLockMinutes = $state(5)
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.
@ -18,9 +20,11 @@ export class SettingsStore {
async load() {
const minutes = await getSetting('autoLockMinutes')
const tabSwitch = await getSetting('lockOnTabSwitch')
const dismissedWarning = await getSetting('dismissedLocalWarning')
this.autoLockMinutes = minutes != null ? Number(minutes) : 5
this.lockOnTabSwitch = tabSwitch != null ? Boolean(tabSwitch) : true
this.dismissedLocalWarning = dismissedWarning != null ? Boolean(dismissedWarning) : false
}
/**
@ -29,6 +33,7 @@ export class SettingsStore {
async save() {
await saveSetting('autoLockMinutes', this.autoLockMinutes)
await saveSetting('lockOnTabSwitch', this.lockOnTabSwitch)
await saveSetting('dismissedLocalWarning', this.dismissedLocalWarning)
}
}