feat(FN-1219): add mission observability and autopilot dashboard UX

- Add mission observability types and API helpers, and forward mission events through SSE with mission filtering support
- Expand MissionManager with health indicators, activity/event log views, improved autopilot controls, and activity auto-scroll behavior
- Refine mission detail mobile controls and styling updates for responsive observability surfaces
- Add regression coverage for MissionManager behavior/mobile CSS and SSE mission filtering
- Include a changeset documenting the mission dashboard observability update
This commit is contained in:
gsxdsm
2026-04-08 18:08:06 -07:00
parent 61117c8a2b
commit b2f0df5455
9 changed files with 1786 additions and 86 deletions

View File

@@ -158,6 +158,41 @@ describe("createSSE", () => {
expect(store.listenerCount("task:created")).toBe(0);
});
it("relays mission:event events as SSE messages when missionStore is provided", () => {
const missionStore = createMockStore();
const req = createMockRequest();
const { res, chunks } = createMockResponse();
createSSE(store, missionStore)(req, res);
const missionEvent = {
id: "ME-001",
missionId: "M-001",
eventType: "mission_started",
description: "Mission started",
metadata: null,
timestamp: new Date().toISOString(),
};
missionStore.emit("mission:event", missionEvent);
const sseMsg = chunks.find((c) => c.includes("mission:event"));
expect(sseMsg).toBeDefined();
expect(sseMsg).toContain(JSON.stringify(missionEvent));
});
it("cleans up mission:event listener when client disconnects", () => {
const missionStore = createMockStore();
const req = createMockRequest();
const { res } = createMockResponse();
createSSE(store, missionStore)(req, res);
expect(missionStore.listenerCount("mission:event")).toBe(1);
req.emit("close");
expect(missionStore.listenerCount("mission:event")).toBe(0);
});
it("tracks active connection count", () => {
const req1 = createMockRequest();
const { res: res1 } = createMockResponse();

View File

@@ -102,6 +102,9 @@ export function createSSE(store: TaskStore, missionStore?: MissionStore, aiSessi
const onFeatureLinked = (data: any) => {
send(`event: feature:linked\ndata: ${JSON.stringify(data)}\n\n`);
};
const onMissionEvent = (data: any) => {
send(`event: mission:event\ndata: ${JSON.stringify(data)}\n\n`);
};
const onAiSessionUpdated = (data: any) => {
send(`event: ai_session:updated\ndata: ${JSON.stringify(data)}\n\n`);
@@ -138,6 +141,7 @@ export function createSSE(store: TaskStore, missionStore?: MissionStore, aiSessi
missionStore.off("feature:updated", onFeatureUpdated);
missionStore.off("feature:deleted", onFeatureDeleted);
missionStore.off("feature:linked", onFeatureLinked);
missionStore.off("mission:event", onMissionEvent);
}
if (aiSessionStore) {
aiSessionStore.off("ai_session:updated", onAiSessionUpdated);
@@ -168,6 +172,7 @@ export function createSSE(store: TaskStore, missionStore?: MissionStore, aiSessi
missionStore.on("feature:updated", onFeatureUpdated);
missionStore.on("feature:deleted", onFeatureDeleted);
missionStore.on("feature:linked", onFeatureLinked);
missionStore.on("mission:event", onMissionEvent);
}
if (aiSessionStore) {