refactor(maestro): flatten web UI to a single host

Session tree nests by working directory only; the daemon chips in the
header become a session tally, and the spawn form loses its host picker.
API calls move from /api/daemons/:id/* to /api/dirs, /api/recent-dirs,
/api/gitroot, and /api/info.
This commit is contained in:
Miguel Palhas
2026-08-22 10:14:10 +01:00
parent 6790f41652
commit 1bb464be4b
14 changed files with 155 additions and 467 deletions
+7 -19
View File
@@ -1,15 +1,14 @@
import { eventsWsUrl, listDaemons, listSessions } from "./api.js";
import type { DaemonInfo, RegistryEvent, Session, Uuid } from "./types.js";
import { eventsWsUrl, listSessions } from "./api.js";
import type { CatalogEvent, Session, Uuid } from "./types.js";
export interface CatalogState {
daemons: Map<string, DaemonInfo>;
sessions: Map<Uuid, Session>;
}
export type CatalogListener = (state: CatalogState) => void;
export class CatalogStore {
private state: CatalogState = { daemons: new Map(), sessions: new Map() };
private state: CatalogState = { sessions: new Map() };
private listeners = new Set<CatalogListener>();
private ws: WebSocket | null = null;
private reconnectTimer: number | null = null;
@@ -46,9 +45,9 @@ export class CatalogStore {
this.ws = ws;
ws.onmessage = (ev) => {
let event: RegistryEvent;
let event: CatalogEvent;
try {
event = JSON.parse(ev.data) as RegistryEvent;
event = JSON.parse(ev.data) as CatalogEvent;
} catch {
return;
}
@@ -83,23 +82,13 @@ export class CatalogStore {
}, delay);
}
private apply(event: RegistryEvent): void {
private apply(event: CatalogEvent): void {
switch (event.type) {
case "Snapshot":
this.state = {
daemons: new Map(event.daemons.map((d) => [d.daemon_id, d])),
sessions: new Map(event.sessions.map((s) => [s.id, s])),
};
break;
case "DaemonAdded":
this.state.daemons.set(event.daemon.daemon_id, event.daemon);
break;
case "DaemonRemoved":
this.state.daemons.delete(event.daemon_id);
for (const [id, sess] of this.state.sessions) {
if (sess.daemon_id === event.daemon_id) this.state.sessions.delete(id);
}
break;
case "SessionAdded":
this.state.sessions.set(event.session.id, event.session);
break;
@@ -119,9 +108,8 @@ export class CatalogStore {
private async resync(): Promise<void> {
try {
const [daemons, sessions] = await Promise.all([listDaemons(), listSessions()]);
const sessions = await listSessions();
this.state = {
daemons: new Map(daemons.map((d) => [d.daemon_id, d])),
sessions: new Map(sessions.map((s) => [s.id, s])),
};
this.emit();