feat(mini-apps): live data refresh + install reliability

- watch ~/.rowboat/apps: data.json changes push into open apps (shim
  re-fetches, onData fires); installs/updates re-list the gallery live and
  reload an open app once the write settles
- atomic installs (temp->rename) so an app opened mid-install never reads a
  partial file
- serve app assets by direct disk read instead of net.fetch(file://) —
  Chromium's network service could stall and blank every open app at once
This commit is contained in:
Gagan 2026-07-04 15:59:13 +05:30
parent b548173593
commit 2c038fe518
8 changed files with 150 additions and 16 deletions

View file

@ -1527,8 +1527,15 @@ export const BuiltinTools: z.infer<typeof BuiltinToolsSchema> = {
const dir = path.join(WorkDir, 'apps', m.id);
const dist = path.join(dir, 'dist');
await fs.mkdir(dist, { recursive: true });
await fs.writeFile(path.join(dir, 'manifest.json'), JSON.stringify(m, null, 2));
await fs.writeFile(path.join(dist, 'index.html'), html);
// Atomic writes (temp→rename): an app opened mid-install must never
// see a partially-written manifest or index.html (blank screen).
const writeAtomic = async (p: string, content: string) => {
const tmp = `${p}.tmp`;
await fs.writeFile(tmp, content);
await fs.rename(tmp, p);
};
await writeAtomic(path.join(dist, 'index.html'), html);
await writeAtomic(path.join(dir, 'manifest.json'), JSON.stringify(m, null, 2));
if (data !== undefined) {
let payload: unknown = data;
if (typeof payload === 'string') { try { payload = JSON.parse(payload); } catch { payload = undefined; } }

View file

@ -1033,6 +1033,19 @@ const ipcSchemas = {
req: z.object({ id: z.string() }),
res: z.object({ data: z.unknown().nullable() }),
},
// Mini Apps: event pushed main→renderer when an app's data.json changes on
// disk (agent refresh), so an open app can reload its data live.
'mini-apps:dataChanged': {
req: z.object({ id: z.string() }),
res: z.null(),
},
// Mini Apps: event pushed main→renderer when an app is installed/updated
// (manifest or index.html changed), so the gallery re-lists and an open app
// reloads once the install settles.
'mini-apps:appsChanged': {
req: z.object({ id: z.string() }),
res: z.null(),
},
// Mini Apps: proxy an HTTP request through main (bypasses browser CORS for the
// sandboxed app origin). GET/POST to http(s) only.
'mini-apps:fetch': {