The signed release showed only its background colour and never started the sidecar. Tauri's freezePrototype freezes Object.prototype before the bundle runs; d3-color and d3-format (under the visx charts) assign `constructor` / `toString` on plain objects while loading, which throws on a frozen prototype, so the bundle stopped before React drew anything. The saved state was not the cause. - build/freeze-safe.ts: Vite plugin that rewrites those assignments in dependencies into a helper that defines the property when the frozen prototype would refuse it - error boundaries for the window, each screen and each app tab (Copy details, Reload, Reset saved state with a backup); public/boot.js shows the same panel when the bundle fails or the window stays empty - logs/host.log (start, saved state, sidecar start/stop, panics) and logs/webview.log (log_webview), rotated, home as ~, keys, tokens and e-mail addresses replaced - state.json `version` 2 with an explicit 1 -> 2 migration (original kept as state.json.v1.bak), per-entry tolerant parsing, partial settings keep their fields, unreadable files kept aside - folders that are gone: "Folder not found" with Locate... (project_locate) / Remove; portfolio reads skip them - tools/desktop-boot-check.ts: the built window with the release CSP and the frozen prototype, a mocked host from the state fixtures (empty, D1 with folders gone, D3) and the real sidecar, through every screen; in bun run ci, the Forgejo CI workflow and the macOS release
42 lines
2.1 KiB
TypeScript
42 lines
2.1 KiB
TypeScript
// An app folder that is gone (moved, renamed, deleted, or an old worktree): say so, and offer Locate… (keeps the app's
|
|
// id, accounts and saved reads) or Remove from the list (no files touched). Shown in the app view and the folder list.
|
|
import { useState } from 'react';
|
|
import { api, errorText, type Project } from '../api';
|
|
import { t } from '../i18n';
|
|
import { useNav } from '../nav';
|
|
import { shortPath } from '../paths';
|
|
import { useData } from '../store';
|
|
import { Icon } from './icons';
|
|
import { Notice } from './kit';
|
|
|
|
export function useFolderActions(p: Project) {
|
|
const d = useData();
|
|
const [busy, setBusy] = useState(false);
|
|
const locate = async () => {
|
|
setBusy(true);
|
|
try {
|
|
const found = await api.locateProject(p.id);
|
|
if (found) { await d.reload(); d.toast({ kind: 'ok', title: t('folder.located', { name: found.name }) }); }
|
|
} catch (e) { d.toast({ kind: 'bad', title: t('folder.locateFailed'), detail: errorText(e) }); } finally { setBusy(false); }
|
|
};
|
|
const remove = async (after?: () => void) => {
|
|
setBusy(true);
|
|
try { await api.removeProject(p.id); await d.reload(); d.toast({ kind: 'ok', title: t('folder.removed', { name: p.name }) }); after?.(); }
|
|
catch (e) { d.toast({ kind: 'bad', title: t('toast.saveFailed'), detail: errorText(e) }); } finally { setBusy(false); }
|
|
};
|
|
return { busy, locate, remove };
|
|
}
|
|
|
|
export function FolderMissing({ project }: { project: Project }) {
|
|
const d = useData();
|
|
const nav = useNav();
|
|
const { busy, locate, remove } = useFolderActions(project);
|
|
return (
|
|
<Notice tone="warn" icon="folder" action={<div className="actions">
|
|
<button className="btn btn-sm btn-primary" onClick={locate} disabled={busy} title={t('folder.locateHint')}><Icon name="search" />{t('folder.locate')}</button>
|
|
<button className="btn btn-sm" onClick={() => remove(() => nav.go({ screen: 'portfolio' }))} disabled={busy}><Icon name="trash" />{t('folder.remove')}</button>
|
|
</div>}>
|
|
<b>{t('folder.missing')}</b> {t('folder.missingText', { path: shortPath(project.path, d.info?.home) })}
|
|
</Notice>
|
|
);
|
|
}
|