feat(app): UI parity with Pencil mockup — top bar, panel cards, sidebar/event-center polish

Top bar (breadcrumb + actions + account), rounded panel cards with active
accent + rich headers, sidebar count pills/collapsible groups/daemon footer,
preset chips + scrollback pill, Event Center tabs + external-notify footer,
JetBrains Mono + Inter via @fontsource, shared theme tokens. Backend-absent
pieces are mocked (search, zoom, uptime, channels) pending SP1–SP5.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-10 06:47:38 +07:00
parent 807eab3f6c
commit 36964c9f21
13 changed files with 458 additions and 89 deletions
+22 -13
View File
@@ -1,11 +1,14 @@
import { useEffect, useState, useCallback, useRef } from "react";
import { LayoutEngine } from "./LayoutEngine";
import { Sidebar } from "./Sidebar";
import { PresetPicker } from "./PresetPicker";
import { TopBar } from "./TopBar";
import { CenterToolbar } from "./CenterToolbar";
import { Wizard } from "./Wizard";
import { EventCenter, type FeedEntry } from "./EventCenter";
import { maybeNotify } from "./notify";
import { COLORS } from "./theme";
import { getStatusFull, applyPreset, onDaemonEvent, onDaemonRawEvent, setWorkspaceMeta, focusSurface } from "./socketBridge";
import { leafIds } from "./layoutTypes";
import type { Group, WorkspaceView, SurfaceState } from "./layoutTypes";
export function App() {
@@ -16,6 +19,8 @@ export function App() {
const [states, setStates] = useState<Record<string, SurfaceState>>({});
const [feed, setFeed] = useState<FeedEntry[]>([]);
const [wizard, setWizard] = useState(false);
const [eventsOpen, setEventsOpen] = useState(true);
const [focusedId, setFocusedId] = useState<string | null>(null);
const feedId = useRef(0);
const activeRef = useRef<string | null>(null);
const wsRef = useRef<WorkspaceView[]>([]);
@@ -66,28 +71,32 @@ export function App() {
}, [refresh]);
const active = workspaces.find((w) => w.id === activeId) ?? null;
const leaves = active ? leafIds(active.layout) : [];
const effectiveFocus = focusedId && leaves.includes(focusedId) ? focusedId : leaves[0] ?? null;
function selectWorkspace(id: string) {
setActiveId(id);
setFocusedId(null);
void setWorkspaceMeta(id, { unread: false });
}
return (
<div style={{ display: "flex", height: "100vh", background: "#0E1116" }}>
<Sidebar groups={groups} workspaces={workspaces} activeId={activeId} onSelect={selectWorkspace} onNew={() => setWizard(true)} />
<div style={{ flex: 1, display: "flex", flexDirection: "column", minWidth: 0 }}>
{active && (
<div style={{ padding: 8, borderBottom: "1px solid #232A33" }}>
<PresetPicker selected="" onSelect={(p) => { if (active) void applyPreset(active.id, p, []); }} />
<div style={{ display: "flex", flexDirection: "column", height: "100vh", background: COLORS.bgApp }}>
<TopBar active={active} eventsOpen={eventsOpen} onToggleEvents={() => setEventsOpen((v) => !v)} />
<div style={{ flex: 1, display: "flex", minHeight: 0 }}>
<Sidebar groups={groups} workspaces={workspaces} activeId={activeId} onSelect={selectWorkspace} onNew={() => setWizard(true)} />
<div style={{ flex: 1, display: "flex", flexDirection: "column", minWidth: 0 }}>
{active && (
<CenterToolbar selected="" onSelect={(p) => { if (active) void applyPreset(active.id, p, []); }} />
)}
<div style={{ flex: 1, minHeight: 0 }}>
{active
? <LayoutEngine workspaceId={active.id} layout={active.layout} running={running} states={states} surfaces={active.surfaces} focusedId={effectiveFocus} onFocus={setFocusedId} />
: <div style={{ color: COLORS.textMuted, padding: 24 }}>No workspace create one to begin.</div>}
</div>
)}
<div style={{ flex: 1, minHeight: 0 }}>
{active
? <LayoutEngine workspaceId={active.id} layout={active.layout} running={running} states={states} />
: <div style={{ color: "#666", padding: 24 }}>No workspace create one to begin.</div>}
</div>
{eventsOpen && <EventCenter feed={feed} onMarkRead={() => setFeed([])} onSelect={(sid) => { void focusSurface(sid); }} />}
</div>
<EventCenter feed={feed} onMarkRead={() => setFeed([])} onSelect={(sid) => { void focusSurface(sid); }} />
{wizard && <Wizard onDone={(id) => { setWizard(false); setActiveId(id); void refresh(); }} onCancel={() => setWizard(false)} />}
</div>
);