From a22c26eb9da9627323d6f627aef9ca25b63bd554 Mon Sep 17 00:00:00 2001 From: Oliver Kieselbach Date: Fri, 11 Sep 2026 17:28:41 +0200 Subject: [PATCH] =?UTF-8?q?feat(web):=20session=20detail=20=E2=80=94=20the?= =?UTF-8?q?=20Download,=20Install=20and=20Script=20panels=20mark=20where?= =?UTF-8?q?=20the=20user=20phase=20begins:=20a=20quiet=20"Device=20phase"?= =?UTF-8?q?=20caption=20over=20the=20first=20row=20and=20a=20"User=20phase?= =?UTF-8?q?"=20divider=20before=20the=20first=20row=20that=20started=20aft?= =?UTF-8?q?er=20the=20device=20entered=20Account=20Setup,=20so=20user-targ?= =?UTF-8?q?eted=20apps=20and=20user-context=20scripts=20stand=20out;=20ses?= =?UTF-8?q?sions=20without=20a=20user=20phase=20render=20unchanged?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5 (1M context) --- internal | 2 +- .../app/sessions/page.tsx | 6 ++ .../components/DownloadProgress.tsx | 20 ++++-- .../components/InstallProgress.tsx | 18 +++-- .../components/PhaseDivider.tsx | 22 ++++++ .../components/ScriptExecutions.tsx | 29 +++++--- .../lib/__tests__/userPhaseBoundary.test.ts | 68 +++++++++++++++++++ .../lib/installProgress.ts | 4 +- .../lib/userPhaseBoundary.ts | 36 ++++++++++ 9 files changed, 185 insertions(+), 20 deletions(-) create mode 100644 src/Web/autopilot-monitor-web/components/PhaseDivider.tsx create mode 100644 src/Web/autopilot-monitor-web/lib/__tests__/userPhaseBoundary.test.ts create mode 100644 src/Web/autopilot-monitor-web/lib/userPhaseBoundary.ts diff --git a/internal b/internal index ee5b6604..66ebb051 160000 --- a/internal +++ b/internal @@ -1 +1 @@ -Subproject commit ee5b6604af686ef6cc1a18cbe591f9ff3a319a24 +Subproject commit 66ebb0511e50832fd7f0735fd60a3ab77061850b diff --git a/src/Web/autopilot-monitor-web/app/sessions/page.tsx b/src/Web/autopilot-monitor-web/app/sessions/page.tsx index 95f45938..6dbf500e 100644 --- a/src/Web/autopilot-monitor-web/app/sessions/page.tsx +++ b/src/Web/autopilot-monitor-web/app/sessions/page.tsx @@ -13,6 +13,7 @@ import PerformanceChart from '../../components/PerformanceChart'; import DownloadProgress from '../../components/DownloadProgress'; import InstallProgress from '../../components/InstallProgress'; import ScriptExecutions from '../../components/ScriptExecutions'; +import { findUserPhaseStartMs } from '@/lib/userPhaseBoundary'; import { useLatestVersions } from '@/lib/useLatestVersions'; import { useScriptDisplayNames } from '@/lib/scriptDisplayNames'; import { api } from "@/lib/api"; @@ -215,6 +216,8 @@ function SessionDetailContent() { // without a terminal event stop being "still running" — it becomes incomplete, its timer // bounded by the agent's last report. Live sessions pass null and keep ticking. const observedUntilMs = isTerminalStatus(displayStatus) ? lastObservedAtMs : null; + // One boundary for the Download / Install / Script panels, so all three split at the same moment. + const userPhaseStartMs = findUserPhaseStartMs(events); // All phase keys currently present. For WhiteGlove sessions we use prefixed keys // (pre-X, user-X) to avoid collisions. @@ -675,6 +678,7 @@ function SessionDetailContent() { latestBootstrapVersion={latestBootstrapVersion} displayNamesByRefKey={scriptDisplayNamesByRefKey} observedUntilMs={observedUntilMs} + userPhaseStartMs={userPhaseStartMs} /> )} @@ -688,6 +692,7 @@ function SessionDetailContent() { )} summaryStats={appSummaryStats} observedUntilMs={observedUntilMs} + userPhaseStartMs={userPhaseStartMs} /> )} @@ -707,6 +712,7 @@ function SessionDetailContent() { )} summaryStats={appSummaryStats} observedUntilMs={observedUntilMs} + userPhaseStartMs={userPhaseStartMs} /> )} diff --git a/src/Web/autopilot-monitor-web/components/DownloadProgress.tsx b/src/Web/autopilot-monitor-web/components/DownloadProgress.tsx index ba1c9678..c6ef73ed 100644 --- a/src/Web/autopilot-monitor-web/components/DownloadProgress.tsx +++ b/src/Web/autopilot-monitor-web/components/DownloadProgress.tsx @@ -1,9 +1,11 @@ "use client"; -import { useMemo, useState } from "react"; +import { Fragment, useMemo, useState } from "react"; import { partitionHistoricReplayEvents, type ReplayInputEvent } from "@/lib/historicReplay"; import TruncatedLabel from "@/components/TruncatedLabel"; import PendingAppRow from "@/components/PendingAppRow"; +import PhaseDivider from "@/components/PhaseDivider"; +import { userPhaseSplitIndex } from "@/lib/userPhaseBoundary"; import { shouldSkipLowBytesTotal, shouldSkipNoActivity, hasByteActivity } from "@/lib/downloadProgressFilters"; import { formatBytes, formatThroughput, formatDuration } from "@/lib/formatting"; import DoBreakdownBar from "./DoBreakdownBar"; @@ -64,6 +66,8 @@ interface DownloadProgressProps { // Epoch ms of the agent's last report, passed only once the session is terminal. A download // that never finished by then is incomplete, not active. observedUntilMs?: number | null; + // Epoch ms the device entered Account Setup; null when the session has no user phase. + userPhaseStartMs?: number | null; } interface DoStats { @@ -147,7 +151,7 @@ function effectiveDurationMs(dl: DownloadItem): number { return 0; } -export default function DownloadProgress({ events, summaryStats, observedUntilMs = null }: DownloadProgressProps) { +export default function DownloadProgress({ events, summaryStats, observedUntilMs = null, userPhaseStartMs = null }: DownloadProgressProps) { // Legacy-agent guard: drop download events replayed from a previous enrollment's IME log. // Silent (empty finals set) — the InstallProgress panel already reports the hidden count // for the same apps; a second note here would double-report them. @@ -352,6 +356,8 @@ export default function DownloadProgress({ events, summaryStats, observedUntilMs ? summaryStats.installed + summaryStats.installing + summaryStats.failed : null; + const userSplit = userPhaseSplitIndex(filteredDownloads, d => d.startedMs, userPhaseStartMs); + return (