Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions docs/specs/2026-09-05-logged-in-ui-shell-design.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,9 +132,9 @@ URLs are unchanged: `/chat`, `/chat/$threadId`, `/dashboards`, `/dashboards/$das
|---|---|---|
| Embedded views render in the system font, eight console errors per view | `ui/host/src/mcp-app-frame.tsx` `mcpAppCSP` | always emit `font-src 'self' data:` |
| "994.6 Ms" | `ui/host/src/dashboard.tsx` `Metric` | drop `tt="capitalize"`, format with `duration()` |
| 404 on every new chat | `ui/host/src/App.tsx` thread load effect | draft pane, fetch only existing threads |
| Header clips at 390px | `ui/host/src/App.tsx` header | header reduced to four controls, rail in drawer |
| Hard-coded "Live" dot | `ui/host/src/App.tsx` header | removed |
| 404 on every new chat | `ui/host/src/app.tsx` thread load effect | draft pane, fetch only existing threads |
| Header clips at 390px | `ui/host/src/app.tsx` header | header reduced to four controls, rail in drawer |
| Hard-coded "Live" dot | `ui/host/src/app.tsx` header | removed |
| Dashboard polls every 3s | `ui/host/src/dashboard.tsx` | 30s |

## 6. Testing and gates
Expand Down
2 changes: 1 addition & 1 deletion ui/host/build/apps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export function appBuildBoundary(): Plugin {
const walk = (id: string, chain: string[]) => {
if (seen.has(id)) return; seen.add(id);
const path = id.replaceAll("\\", "/").split("?")[0];
if (/\/src\/(?:auth[\w-]*(?:\.[jt]sx?$|\/)|router(?:\.[jt]sx?|\/)|routes\/|(?:App|main)\.[jt]sx?$|(?:app-context|provider-context)(?:\.[jt]sx?|\/)|(?:app|providers?)\/|(?:api|observability|mcp-app-frame)\.[jt]sx?$|dashboards\/api\.[jt]sx?$)/.test(path)) this.error(`Forbidden MCP app import: ${[...chain, id].join(" -> ")}`);
if (/\/src\/(?:auth[\w-]*(?:\.[jt]sx?$|\/)|router(?:\.[jt]sx?|\/)|routes\/|(?:app|main)\.[jt]sx?$|(?:app-context|provider-context)(?:\.[jt]sx?|\/)|(?:app|providers?)\/|(?:api|observability|mcp-app-frame)\.[jt]sx?$|dashboards\/api\.[jt]sx?$)/.test(path)) this.error(`Forbidden MCP app import: ${[...chain, id].join(" -> ")}`);
const info = this.getModuleInfo(id);
for (const next of [...(info?.importedIds ?? []), ...(info?.dynamicallyImportedIds ?? [])]) walk(next, [...chain, id]);
};
Expand Down
2 changes: 1 addition & 1 deletion ui/host/src/answer-only.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { createMemoryHistory, createRootRoute, createRoute, createRouter, Router
import { act } from "react";
import { createRoot } from "react-dom/client";
import { afterEach, expect, it, vi } from "vitest";
import App from "./App";
import App from "./app";
import { useFanoutApp } from "./app-context";

vi.mock("./auth", () => ({
Expand Down
2 changes: 1 addition & 1 deletion ui/host/src/app.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { createMemoryHistory, createRootRoute, createRoute, createRouter, Router
import { act } from "react";
import { createRoot } from "react-dom/client";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import App from "./App";
import App from "./app";
import { ChatPage } from "./chat";
import { createDashboardPrompt, useFanoutApp } from "./app-context";
import { parseSearch, toSearchParams } from "./dashboards/search";
Expand Down
File renamed without changes.
2 changes: 1 addition & 1 deletion ui/host/src/chat.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { createRoot } from "react-dom/client";
import { afterEach, describe, expect, it, vi } from "vitest";
import { FanoutAppContext, useFanoutApp, type FanoutAppContextValue } from "./app-context";
import { createRootRoute, createRoute, createRouter, RouterProvider } from "@tanstack/react-router";
import App from "./App";
import App from "./app";
import { ChatPage } from "./chat";
import { saved as receiptFixture, build as receiptBuild, user, call, result } from "../tests/dashboard-receipts";
import { fixture } from "../tests/fixtures";
Expand Down
2 changes: 1 addition & 1 deletion ui/host/src/dashboard-change-chips.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Badge, Group } from "@mantine/core";

export function EditChips({ labels }: { labels: string[] }) {
export function DashboardChangeChips({ labels }: { labels: string[] }) {
if (!labels.length) return null;
return <Group gap={4} data-edit-chips>{labels.map((label, index) => <Badge key={index} color="gray" variant="light" size="sm" tt="none" maw="100%" title={label} style={{ color: "var(--mantine-color-text)", height: "auto", whiteSpace: "normal", overflowWrap: "anywhere" }}>{label}</Badge>)}</Group>;
}
4 changes: 2 additions & 2 deletions ui/host/src/dashboard-receipt-view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Button, Collapse, Group, Loader, Paper, Stack, Text } from "@mantine/co
import { Link } from "@tanstack/react-router";
import { useId, useState } from "react";
import { changeChips, stageNames, type BuildReceipt } from "./dashboard-receipt";
import { EditChips } from "./dashboard-change-chips";
import { DashboardChangeChips } from "./dashboard-change-chips";

const seconds = (ms: number) => `${(ms / 1000).toFixed(1)} s`;
export function DashboardReceiptView({ receipt, running = false, activity = "", awaitingAnswer = false }: { receipt: BuildReceipt; running?: boolean; activity?: string; awaitingAnswer?: boolean }) {
Expand All @@ -27,7 +27,7 @@ export function DashboardReceiptView({ receipt, running = false, activity = "",
<Button component="button" variant="subtle" color="gray" size="compact-xs" aria-expanded={expanded} aria-controls={detailsID} onClick={() => setExpanded(v => !v)}>Details{chips.length > 4 ? ` (+${chips.length - 4})` : ""}</Button>
</Group>
{running && awaitingAnswer && <Group gap="xs" data-build-running><Loader type="dots" size="sm" /><Text c="dimmed" size="sm">{activity || "Analyzing your system"}</Text></Group>}
<EditChips labels={chips.slice(0,4)} />
<DashboardChangeChips labels={chips.slice(0,4)} />
{receipt.explanations.map(text => running ? text.split(" · ").filter(part => !part.endsWith(": incomplete")).join(" · ") : text).filter(Boolean).map((text,i) => <Text key={i} size="xs" c="dimmed" data-receipt-attention>{text}</Text>)}
{saved && <Button renderRoot={(props) => <Link {...props} to="/dashboards/$dashboardId" params={{dashboardId:saved.id}} search={{}} />} variant="subtle" size="compact-sm" color="gray" style={{alignSelf:"flex-start"}}>Open dashboard</Button>}
<Collapse expanded={expanded} id={detailsID}>
Expand Down
2 changes: 1 addition & 1 deletion ui/host/src/dashboards/chart-keyboard.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { FragmentView } from './fragment-view';
import { fixture } from '../../tests/fixtures';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { TableViz } from './viz/table';
import { Waterfall } from './trace/trace-components';
import { Waterfall } from './trace/waterfall';
import { traceFixture } from '../../tests/fixtures';

const chart=vi.hoisted(()=>({setOption:vi.fn(),dispatchAction:vi.fn(),dispose:vi.fn(),on:vi.fn(),resize:vi.fn()}));
Expand Down
4 changes: 2 additions & 2 deletions ui/host/src/dashboards/history.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { useEffect, useRef, useState } from "react";
import { exactTimestamp } from "../../../format";
import { changeChips } from "../dashboard-receipt";
import { EditChips } from "../dashboard-change-chips";
import { DashboardChangeChips } from "../dashboard-change-chips";
import { dashboardDataPredicate } from "./cache";
import { ApiError, dashboardsKey, getVersion, listVersions, restoreVersion, type DashboardRecord, type VersionInfo, type VersionRecord } from "./api";

Expand Down Expand Up @@ -136,7 +136,7 @@ function VersionDetails({ record, currentVersion, pending, onRestore }: { record
</Group>
{dashboard.description && <Text size="sm">{dashboard.description}</Text>}
{record.changes_available ? <>
<EditChips labels={labels} />
<DashboardChangeChips labels={labels} />
{!labels.length && !record.layout_changed && <Text size="sm" c="dimmed">No authored changes.</Text>}
</> : <Alert color="warn" title="Changes unavailable">The preceding version is no longer retained. This saved spec is still available.</Alert>}
<Text size="sm" c="dimmed">Read-only · {dashboard.spec.panels.length} panels. Historical data is not queried.</Text>
Expand Down
2 changes: 1 addition & 1 deletion ui/host/src/dashboards/trace/detail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { filledTextOn } from "../../../../theme";
import { duration } from "../../../../format";
import { EmptyState } from "./components";
import { ListBullets } from "@phosphor-icons/react";
import { Waterfall } from "./trace-components";
import { Waterfall } from "./waterfall";
import { LogsViz } from "../viz/logs";
import type { PanelResult } from "../../../../panels/types";

Expand Down
2 changes: 1 addition & 1 deletion ui/host/src/routes/__root.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { createRootRoute, Navigate } from "@tanstack/react-router";
import App from "../App";
import App from "../app";

export const Route = createRootRoute({
component: App,
Expand Down
2 changes: 1 addition & 1 deletion ui/host/tests/build-graph.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { build } from "vite";
import { expect, it } from "vitest";
import { appBuildBoundary, blobWorkerOnly } from "../build/apps";

it.each(["auth.ts", "auth-session.ts", "api.ts", "observability.ts", "mcp-app-frame.tsx", "router.ts", "routes/index.ts", "App.tsx", "main.tsx", "app-context.tsx", "provider-context.tsx", "dashboards/api.ts"].flatMap(file => [false, true].map(transitive => ({ file, transitive }))))("rejects forbidden runtime imports: $file transitive=$transitive", async ({ file, transitive }) => {
it.each(["auth.ts", "auth-session.ts", "api.ts", "observability.ts", "mcp-app-frame.tsx", "router.ts", "routes/index.ts", "app.tsx", "main.tsx", "app-context.tsx", "provider-context.tsx", "dashboards/api.ts"].flatMap(file => [false, true].map(transitive => ({ file, transitive }))))("rejects forbidden runtime imports: $file transitive=$transitive", async ({ file, transitive }) => {
const root = mkdtempSync(resolve(tmpdir(), "fanout-graph-"));
try {
mkdirSync(resolve(root, "src/mcp-apps"), { recursive: true }); mkdirSync(resolve(root, "src/dashboards"));
Expand Down
Loading