Skip to content

Commit 5b6f869

Browse files
authored
feat: tighten public front door and dashboard UX (#159)
* docs: tighten front door and fix mobile shell * feat: sharpen dashboard first-screen triage * docs: compress public front door * feat: tighten dashboard home narrative * feat: make command tower recovery-first * test: cover command tower recovery mode
1 parent b032e87 commit 5b6f869

7 files changed

Lines changed: 184 additions & 247 deletions

File tree

apps/dashboard/app/command-tower/page.tsx

Lines changed: 130 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,14 @@ export const metadata: Metadata = {
2020
"Monitor live operator visibility, linked Workflow Cases, blockers, and next operator actions from the OpenVibeCoding command tower cockpit.",
2121
};
2222

23-
export async function CommandTowerHomeSection({ locale }: { locale: UiLocale }) {
23+
type CommandTowerHomeState = {
24+
overview: CommandTowerOverviewPayload;
25+
sessions: PmSessionSummary[];
26+
warning: string;
27+
hasLiveData: boolean;
28+
};
29+
30+
async function loadCommandTowerHomeState(locale: UiLocale): Promise<CommandTowerHomeState> {
2431
const commandTowerCopy = getUiCopy(locale).dashboard.commandTowerPage;
2532
const fallbackOverview: CommandTowerOverviewPayload = {
2633
generated_at: new Date().toISOString(),
@@ -71,11 +78,25 @@ export async function CommandTowerHomeSection({ locale }: { locale: UiLocale })
7178
(overview.active_sessions || 0) > 0 ||
7279
(sessions?.length || 0) > 0;
7380

81+
return {
82+
overview,
83+
sessions,
84+
warning,
85+
hasLiveData,
86+
};
87+
}
88+
89+
export async function CommandTowerHomeSection({ locale }: { locale: UiLocale }) {
90+
const commandTowerCopy = getUiCopy(locale).dashboard.commandTowerPage;
91+
const { overview, sessions, warning, hasLiveData } = await loadCommandTowerHomeState(locale);
92+
7493
return (
7594
<>
76-
<section aria-label="Command Tower live overview" aria-describedby="command-tower-page-subtitle">
77-
<CommandTowerHomeLiveClient initialOverview={overview} initialSessions={sessions} locale={locale} />
78-
</section>
95+
{warning && !hasLiveData ? null : (
96+
<section aria-label="Command Tower live overview" aria-describedby="command-tower-page-subtitle">
97+
<CommandTowerHomeLiveClient initialOverview={overview} initialSessions={sessions} locale={locale} />
98+
</section>
99+
)}
79100
{warning && !hasLiveData ? (
80101
<ControlPlaneStatusCallout
81102
title={commandTowerCopy.unavailableTitle}
@@ -116,41 +137,69 @@ export function CommandTowerHomeSectionFallback({ locale }: { locale: UiLocale }
116137
);
117138
}
118139

119-
export function CommandTowerPageIntro({ locale }: { locale: UiLocale }) {
140+
export function CommandTowerPageIntro({
141+
locale,
142+
recoverySummary,
143+
}: {
144+
locale: UiLocale;
145+
recoverySummary?: string;
146+
}) {
120147
const commandTowerCopy = getUiCopy(locale).dashboard.commandTowerPage;
121-
const towerActions =
122-
locale === "zh-CN"
148+
const recoveryMode = Boolean(recoverySummary);
149+
const towerActions = recoveryMode
150+
? locale === "zh-CN"
151+
? [
152+
{ href: "/command-tower", label: "重载指挥塔", variant: "default" as const },
153+
{ href: "/pm", label: "回到 PM 入口", variant: "secondary" as const },
154+
]
155+
: [
156+
{ href: "/command-tower", label: "Reload Command Tower", variant: "default" as const },
157+
{ href: "/pm", label: "Start from PM", variant: "secondary" as const },
158+
]
159+
: locale === "zh-CN"
123160
? [
124-
{ href: "/events", label: "打开风险事件" },
125-
{ href: "/workflows", label: "打开工作流案例" },
126-
{ href: "/runs", label: "打开证明室" },
161+
{ href: "/events", label: "打开风险事件", variant: "warning" as const },
162+
{ href: "/workflows", label: "打开工作流案例", variant: "secondary" as const },
163+
{ href: "/runs", label: "打开证明室", variant: "secondary" as const },
127164
]
128165
: [
129-
{ href: "/events", label: "Open risk events" },
130-
{ href: "/workflows", label: "Open Workflow Cases" },
131-
{ href: "/runs", label: "Open proof room" },
166+
{ href: "/events", label: "Open risk events", variant: "warning" as const },
167+
{ href: "/workflows", label: "Open Workflow Cases", variant: "secondary" as const },
168+
{ href: "/runs", label: "Open proof room", variant: "secondary" as const },
132169
];
133170
return (
134171
<header className="app-section">
135172
<div className="home-briefing-shell">
136173
<div className="home-briefing-copy">
137174
<p className="cell-sub mono muted">
138-
{locale === "zh-CN" ? "L0 驾驶舱 / 实时控制桌" : "L0 cockpit / live control desk"}
175+
{recoveryMode
176+
? locale === "zh-CN"
177+
? "恢复模式 / 当前主面不可用"
178+
: "Recovery mode / live surface unavailable"
179+
: locale === "zh-CN"
180+
? "L0 驾驶舱 / 实时控制桌"
181+
: "L0 cockpit / live control desk"}
139182
</p>
140183
<h1 id="command-tower-page-title" className="page-title">
141184
{commandTowerCopy.srTitle}
142185
</h1>
143186
<p id="command-tower-page-subtitle" className="page-subtitle">
144-
{commandTowerCopy.srSubtitle}
187+
{recoveryMode
188+
? locale === "zh-CN"
189+
? "指挥塔当前拿不到 live 总览。先确认只读真相,再走一条恢复路径。"
190+
: "Command Tower cannot read the live overview right now. Verify the read-only truth first, then take one recovery path."
191+
: commandTowerCopy.srSubtitle}
145192
</p>
146193
<p className="cell-sub mono muted">
147-
{locale === "zh-CN"
148-
? "这一页应该先告诉你:现在发生什么、哪条线危险、下一步该去哪个真相入口。"
149-
: "This page should answer three questions first: what is happening now, which lane is risky, and which truth surface to open next."}
194+
{recoveryMode
195+
? recoverySummary
196+
: locale === "zh-CN"
197+
? "这一页应该先告诉你:现在发生什么、哪条线危险、下一步该去哪个真相入口。"
198+
: "This page should answer three questions first: what is happening now, which lane is risky, and which truth surface to open next."}
150199
</p>
151200
<nav className="home-briefing-actions" aria-label={locale === "zh-CN" ? "指挥塔首屏操作" : "Command Tower first-screen actions"}>
152-
{towerActions.map((action, index) => (
153-
<Button asChild key={action.href} variant={index === 0 ? "warning" : "secondary"}>
201+
{towerActions.map((action) => (
202+
<Button asChild key={action.href} variant={action.variant}>
154203
<Link href={action.href}>{action.label}</Link>
155204
</Button>
156205
))}
@@ -159,28 +208,62 @@ export function CommandTowerPageIntro({ locale }: { locale: UiLocale }) {
159208
<Card className="home-briefing-panel">
160209
<div className="home-briefing-panel-head">
161210
<span className="cell-sub mono muted">
162-
{locale === "zh-CN" ? "值班判断" : "Operator judgment"}
211+
{recoveryMode
212+
? locale === "zh-CN"
213+
? "恢复判断"
214+
: "Recovery judgment"
215+
: locale === "zh-CN"
216+
? "值班判断"
217+
: "Operator judgment"}
163218
</span>
164-
<Badge variant="running">
165-
{locale === "zh-CN" ? "先看 live" : "Live first"}
219+
<Badge variant={recoveryMode ? "warning" : "running"}>
220+
{recoveryMode
221+
? locale === "zh-CN"
222+
? "先恢复主面"
223+
: "Restore the surface first"
224+
: locale === "zh-CN"
225+
? "先看 live"
226+
: "Live first"}
166227
</Badge>
167228
</div>
168229
<div className="home-briefing-signal-list">
169-
<div className="home-briefing-signal">
170-
<span className="cell-sub mono muted">{locale === "zh-CN" ? "现在发生什么" : "What is happening now"}</span>
171-
<strong>{locale === "zh-CN" ? "先看 live session board" : "Scan the live session board first"}</strong>
172-
<p>{locale === "zh-CN" ? "不要先钻细节页。先确定 board 上最重要的 run 和 session。" : "Do not drill into detail pages first. Identify the most important session and run on the board."}</p>
173-
</div>
174-
<div className="home-briefing-signal">
175-
<span className="cell-sub mono muted">{locale === "zh-CN" ? "风险在哪" : "Where is the risk"}</span>
176-
<strong>{locale === "zh-CN" ? "先读 risk lane 和 degraded alert" : "Read the risk lane and degraded alert first"}</strong>
177-
<p>{locale === "zh-CN" ? "这页的主任务是分诊,不是浏览所有模块。" : "The main job here is triage, not browsing every module."}</p>
178-
</div>
179-
<div className="home-briefing-signal">
180-
<span className="cell-sub mono muted">{locale === "zh-CN" ? "下一步" : "What to do next"}</span>
181-
<strong>{locale === "zh-CN" ? "先用 tower 再跳去 Workflow 或 Proof" : "Use the tower before jumping to Workflow or Proof"}</strong>
182-
<p>{locale === "zh-CN" ? "让 tower 成为主驾驶舱,而不是另一个数据列表页。" : "Treat the tower as the cockpit, not another reporting page."}</p>
183-
</div>
230+
{recoveryMode ? (
231+
<>
232+
<div className="home-briefing-signal">
233+
<span className="cell-sub mono muted">{locale === "zh-CN" ? "当前状态" : "Current state"}</span>
234+
<strong>{locale === "zh-CN" ? "live 总览暂时不可读" : "The live overview is temporarily unavailable"}</strong>
235+
<p>{locale === "zh-CN" ? "这不是正常驾驶舱读面。先恢复主面,再继续值班。" : "This is not a normal cockpit read. Restore the surface first, then resume operator work."}</p>
236+
</div>
237+
<div className="home-briefing-signal">
238+
<span className="cell-sub mono muted">{locale === "zh-CN" ? "仍然成立的真相" : "What still holds"}</span>
239+
<strong>{locale === "zh-CN" ? "只读入口与证明室仍可用" : "The read-only rooms still work"}</strong>
240+
<p>{locale === "zh-CN" ? "你仍然可以回 PM、Runs 和 Workflow Cases,但不要把当前页面当成 live cockpit。 " : "You can still use PM, Runs, and Workflow Cases, but do not treat this page as a live cockpit right now."}</p>
241+
</div>
242+
<div className="home-briefing-signal">
243+
<span className="cell-sub mono muted">{locale === "zh-CN" ? "恢复动作" : "Recovery move"}</span>
244+
<strong>{locale === "zh-CN" ? "先重载,再决定是否回 PM" : "Reload first, then decide whether to return to PM"}</strong>
245+
<p>{locale === "zh-CN" ? "把恢复动作收成一条主路径,而不是继续分散到多个正常驾驶舱动作。 " : "Keep recovery on one main path instead of splitting attention across normal cockpit actions."}</p>
246+
</div>
247+
</>
248+
) : (
249+
<>
250+
<div className="home-briefing-signal">
251+
<span className="cell-sub mono muted">{locale === "zh-CN" ? "现在发生什么" : "What is happening now"}</span>
252+
<strong>{locale === "zh-CN" ? "先看 live session board" : "Scan the live session board first"}</strong>
253+
<p>{locale === "zh-CN" ? "不要先钻细节页。先确定 board 上最重要的 run 和 session。" : "Do not drill into detail pages first. Identify the most important session and run on the board."}</p>
254+
</div>
255+
<div className="home-briefing-signal">
256+
<span className="cell-sub mono muted">{locale === "zh-CN" ? "风险在哪" : "Where is the risk"}</span>
257+
<strong>{locale === "zh-CN" ? "先读 risk lane 和 degraded alert" : "Read the risk lane and degraded alert first"}</strong>
258+
<p>{locale === "zh-CN" ? "这页的主任务是分诊,不是浏览所有模块。" : "The main job here is triage, not browsing every module."}</p>
259+
</div>
260+
<div className="home-briefing-signal">
261+
<span className="cell-sub mono muted">{locale === "zh-CN" ? "下一步" : "What to do next"}</span>
262+
<strong>{locale === "zh-CN" ? "先用 tower 再跳去 Workflow 或 Proof" : "Use the tower before jumping to Workflow or Proof"}</strong>
263+
<p>{locale === "zh-CN" ? "让 tower 成为主驾驶舱,而不是另一个数据列表页。" : "Treat the tower as the cockpit, not another reporting page."}</p>
264+
</div>
265+
</>
266+
)}
184267
</div>
185268
</Card>
186269
</div>
@@ -191,12 +274,17 @@ export function CommandTowerPageIntro({ locale }: { locale: UiLocale }) {
191274
export default async function CommandTowerPage() {
192275
const cookieStore = await cookies();
193276
const locale = normalizeUiLocale(cookieStore.get(UI_LOCALE_STORAGE_KEY)?.value);
277+
const { warning, hasLiveData } = await loadCommandTowerHomeState(locale);
278+
const recoverySummary =
279+
warning && !hasLiveData ? warning : undefined;
194280
return (
195281
<main className="grid" aria-labelledby="command-tower-page-title" aria-describedby="command-tower-page-subtitle">
196-
<CommandTowerPageIntro locale={locale} />
197-
<Suspense fallback={<CommandTowerHomeSectionFallback locale={locale} />}>
198-
<CommandTowerHomeSection locale={locale} />
199-
</Suspense>
282+
<CommandTowerPageIntro locale={locale} recoverySummary={recoverySummary} />
283+
{recoverySummary ? null : (
284+
<Suspense fallback={<CommandTowerHomeSectionFallback locale={locale} />}>
285+
<CommandTowerHomeSection locale={locale} />
286+
</Suspense>
287+
)}
200288
</main>
201289
);
202290
}

apps/dashboard/app/globals.css

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
@import "./globals.tokens.css";
22
@import "./globals.layout.css";
33
@import "./globals.feature.css";
4+
@import "./globals.responsive.css";

apps/dashboard/app/globals.tokens.css

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
@import "./globals.run-detail.css";
2-
@import "./globals.responsive.css";
32

43
/* =========================================
54
OpenVibeCoding Command Tower — Design Tokens

0 commit comments

Comments
 (0)