desktop reads: a card that joins a read or gets the 'read just now' answer applies it (no bus event for those: a tab opened again stayed on its skeleton); invalidate before a write's after-refresh, editor save re-reads with force

This commit is contained in:
CRTkafa 2026-10-23 11:21:51 +08:00
parent fa081666ec
commit f3e7c98994
2 changed files with 10 additions and 6 deletions

View file

@ -23,7 +23,7 @@ export function TabEditor({ tab, project }: { tab: AppTab; project: Project }) {
const r = useEditable(project);
if (!EDIT_TABS[tab]) return null;
if (!r.data) return r.error ? <ErrorNotice error={t('ed.loadFailed', { error: r.error })} tone="bad" /> : <Section><Skeleton lines={4} block /></Section>;
const p = { project, data: r.data, onSaved: () => { r.refresh(); } };
const p = { project, data: r.data, onSaved: () => { r.refresh(true); } };
switch (tab) {
case 'listings': return <ListingEditor {...p} />;
case 'appinfo': return <AppInfoEditor {...p} />;

View file

@ -608,7 +608,8 @@ export interface ReadState<T> {
justNow?: boolean;
/** Its store account waits on a quota pause or 429 backoff until then (epoch ms). */
pausedUntil?: number | null;
refresh: () => Promise<void>;
/** Reads again (joins / memo); `true` skips the "read just now" memo. */
refresh: (force?: unknown) => Promise<void>;
}
/** preview: false when the screen already showed this dry run (the editors' Review step); the native dialog still follows. */
@ -638,7 +639,7 @@ export function useWrite(projectId: string | null) {
const o = await api.writeWithConfirm(w.method, projectId, w.args, job);
if (o.stage === 'dry-run') d.updateToast(tid, { kind: 'bad', title: t('write.dryFailed'), detail: withHint(failureText(o.dryRun)), progress: undefined, job: undefined });
else if (o.stage === 'dialog') d.updateToast(tid, { kind: 'info', title: t('write.notApplied'), detail: t('write.notAppliedDetail'), progress: undefined, job: undefined });
else if (o.result?.ok) { d.updateToast(tid, { kind: 'ok', title: w.done, detail: lastLine(o.result.output) || undefined, progress: undefined, job: undefined }); w.after?.(); d.invalidate(projectId, w.method); }
else if (o.result?.ok) { d.updateToast(tid, { kind: 'ok', title: w.done, detail: lastLine(o.result.output) || undefined, progress: undefined, job: undefined }); d.invalidate(projectId, w.method); w.after?.(); }
// exit 3: the command stopped for an owner decision it did not get (a price change of a live product: the
// Catalog tab's owner action applies it); everything else it could do was done
else if (o.result?.exitCode === 3) { d.updateToast(tid, { kind: 'info', title: t('write.declined'), detail: lastLine(o.result.output) || undefined, progress: undefined, job: undefined }); w.after?.(); }
@ -702,11 +703,14 @@ export function useRead<T>(target: string, method: string, o: { network: boolean
const skipRef = useRef(o.skip);
skipRef.current = o.skip;
const refresh = useCallback(async () => {
const refresh = useCallback(async (force?: unknown) => {
if (skipRef.current) return;
set(x => ({ ...x, loading: false }));
// a click: highest priority, joins a read in flight, answered from the memo when it finished just now
await dRef.current.schedule(target, method, JSON.parse(argsKey) as Record<string, unknown>, 0).catch(() => {});
// a click: highest priority, joins a read in flight, answered from the memo when it finished just now. The answer
// is applied here too: a join or a memo answer sends no bus event, and a card that mounted after the read (a tab
// opened again) would otherwise wait forever. `force === true` (after a save of this app's files) skips the memo.
const r = await dRef.current.schedule(target, method, JSON.parse(argsKey) as Record<string, unknown>, 0, force === true).catch(() => null);
if (r && usable(method, r)) set(x => (x.data === r.data ? x : { data: r.data as T, at: new Date().toISOString(), fromCache: false, loading: false, stale: false, error: null, command: r.command }));
}, [target, method, argsKey]);
// results of this key, whoever asked for them (this card, the background reader, a write's invalidation)