The activity dock: every background process, every view, bottom-right
One reserved corner of the shell, one blob per running BACKGROUND
process — session sub-agents (research/demo/visual) and global
extraction runs. Teaching turns are deliberately NOT here: the dock
exists to surface what the learner can't otherwise see; a running
teaching turn is already fully visible where it happens (user decision).
- Data: ONE poll of GET /api/activity (jobs across ALL chats,
title-joined, oldest-first + extraction runs), folded client-side
with core/prices.ts (jobCost). Adaptive interval — 2.5s while
anything runs, 10s idle, 4× slower on hidden tabs; a failed fetch
keeps the last feed (no flicker to empty on a blip).
- Per blob: kind icon, elapsed (ticking), cost, activity line, and an
explicit END on the blob's face. Click → detail popover (context
line, model, steps, open-session link). The context line is the LAST
model call's tokens vs the model's limit (JobUsage.lastStep — pending
agents-stream contract; "—" until it populates), NEVER a cumulative
sum. Absent data renders "—", never zero (the doctrine).
- Header: total running cost (sum of priced blobs, "—" when nothing
is priced). Narrow viewports collapse to a count badge → drawer.
- END posts to /api/activity/abort {kind, id} — the backend is being
composed by master over the streams' mechanisms; until it exists a
click locks the button and the popover says "Abort not available
yet" (never a dead control).
- Idle renders NOTHING. Mounted once in the root layout; every view
gets it for free.
toBlobs / formatElapsed / formatUsd / formatTokens are pure + tested;
SSR render tests pin blobs, the "—" doctrine, the face END, and the
priced total.
This commit is contained in:
+7
-1
@@ -3,6 +3,7 @@ import "katex/dist/katex.min.css";
|
||||
import "./globals.css";
|
||||
import { AppShell, SidebarProvider } from "@/components/sidebar";
|
||||
import { TreePanelModeProvider } from "@/components/tree-panel";
|
||||
import { ActivityDock, ActivityProvider } from "@/components/activity-dock";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Learning Harness",
|
||||
@@ -15,7 +16,12 @@ export default function RootLayout({ children }: { children: React.ReactNode })
|
||||
<body className="min-h-dvh antialiased">
|
||||
<SidebarProvider>
|
||||
<TreePanelModeProvider>
|
||||
<AppShell>{children}</AppShell>
|
||||
<ActivityProvider>
|
||||
<AppShell>{children}</AppShell>
|
||||
{/* The reserved bottom-right corner of every view — renders
|
||||
nothing while no background process runs. */}
|
||||
<ActivityDock />
|
||||
</ActivityProvider>
|
||||
</TreePanelModeProvider>
|
||||
</SidebarProvider>
|
||||
</body>
|
||||
|
||||
Reference in New Issue
Block a user