fix(web): drop indexer debug log, harden settings dialog, escape search-placeholder quotes

Three small cleanups bundled:

- Remove the `console.debug('[indexer]', ...)` line in the indexer
  store. The PhotoPrism WS protocol is now verified; the log was a
  development aid that no longer earns its console noise.

- GeneralSettingsDialog: normalize cloned PpSettings so `ui` / `search`
  / `maps` are always real objects (some deployments return them
  unset), and re-clone the draft on each open instead of nulling it on
  close. The previous lifecycle let Dialog's exit animation keep the
  form mounted while `draft` was already null, which threw at runtime
  via the `bind:value={draft.ui!.theme}` getters.

- Search-input placeholder string: rewrite as a JS expression so the
  embedded `"vacation"` quotes inside the example don't terminate the
  HTML attribute early. The previous form was a Svelte parse error
  that stopped the dev-server module from loading.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-18 08:13:42 +02:00
parent cfd85a1fe8
commit ccbc1050de
3 changed files with 964 additions and 916 deletions

View File

@@ -90,21 +90,43 @@
enabled: open
}));
/**
* Some PhotoPrism deployments return `/settings` without the
* `ui` / `search` / `maps` keys (older versions, custom edits to
* settings.yml). The form's `bind:value={draft.ui!.theme}` etc.
* non-null-asserts those sub-objects — when they're missing the
* assertion lies and the bind getter throws on the next tick. Force
* the shape on every clone so every binding has a real object to
* write into, and so `draft.ui` is never null while `draft` is non-
* null (template gates only check `draft`).
*/
function normalize(s: PpSettings): PpSettings {
return {
...s,
ui: s.ui ?? {},
search: s.search ?? {},
maps: s.maps ?? {}
};
}
let draft = $state<PpSettings | null>(null);
// Re-clone on each open so reopening the dialog shows the freshest
// server state. Eagerly nulling on close used to introduce a window
// where Dialog's exit animation kept the form mounted while draft
// was already null — and bind:value getters read null, triggering
// "$.get(...) is null" / can't access .ui at runtime. Resetting on
// open instead avoids that race entirely.
$effect(() => {
if (settingsQuery.data && draft === null) {
draft = structuredClone(settingsQuery.data);
if (open && settingsQuery.data) {
draft = normalize(structuredClone(settingsQuery.data));
}
});
$effect(() => {
if (!open) draft = null;
});
const saveMut = createMutation(() => ({
mutationFn: (patch: PpSettings) => saveSettings(patch),
onSuccess: (next) => {
qc.setQueryData(['settings'], next);
draft = structuredClone(next);
draft = normalize(structuredClone(next));
toast.success('Settings saved');
},
onError: (err) =>
@@ -112,7 +134,7 @@
}));
function resetDraft() {
if (settingsQuery.data) draft = structuredClone(settingsQuery.data);
if (settingsQuery.data) draft = normalize(structuredClone(settingsQuery.data));
}
const selectClass =

View File

@@ -126,11 +126,6 @@ function handleMessage(raw: string): void {
const eventName = inner.event as string | undefined;
const data = (inner.data ?? {}) as Record<string, unknown>;
if (!eventName) return;
// PhotoPrism's WS protocol isn't a stable contract; log the live shape
// at `debug` (hidden by default in DevTools — toggle "Verbose" to see)
// so future-us can spot new indexer event names without instrumenting
// the entire app.
console.debug('[indexer]', eventName, data);
switch (eventName) {
case 'index.indexing': {
// Per-file event during the scan pass. PhotoPrism emits one

File diff suppressed because it is too large Load Diff