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
Binary file added docs/audits/task-322/page-heads-1200.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/audits/task-322/page-heads-390.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 2 additions & 1 deletion frontend/design-system/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -179,10 +179,11 @@ entrance and scroll-reveal animation is allowed within these limits:

### Layout rules

- **Page heads** use `V2PageHead`: one title, an optional live count in mono, and actions. Put explanation in `V2Hint` beside the title. The (i) opens on hover, keyboard focus, and tap; at 760px and below its text expands inline. Do not add eyebrow or sentence subtitle rows. Errors and requests for action remain visible.
- **Fixed sidebar widths** (rail 76, pods 272, inspector 336) — not fluid. The main pane absorbs all flex. The content pane is the card: 8px gutter top/right/bottom on desktop, flush on phones; when the inspector is open the card meets the inspector gutter.
- **Rail collapses labels** at all widths — labels show as tooltips on hover (CSS `::after` with `data-label`).
- **Inspector closes** to free width for the main pane; default state is collapsed.
- Header heights cluster at **42px (tabs)**, **34px (chips/buttons)**, **78px (chat header w/ subtitle)**.
- Header heights cluster at **42px (tabs)**, **34px (chips/buttons)**, **78px (chat header)**.
- Mobile breakpoints at `1200px / 992px / 900px / 768px`. Below 900px the rail and pods sidebar collapse off-canvas.
- **App-shell scroll containment.** `.v2-root` is a fixed-height app shell —
`height: 100vh; overflow: hidden` — so the shell chrome never scrolls and each
Expand Down
3 changes: 2 additions & 1 deletion frontend/src/i18n/__tests__/zhNumberUnitSpacing.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ const OPAQUE_PLACEHOLDERS = [
'name', 'second', 'first', 'handle', 'author', 'affiliations', 'username', 'code',
'label', 'taskId', 'agent', 'machine', 'time', 'section', 'platform', 'projects',
'connector', 'user', 'workspace', 'channel', 'tool', 'tools', 'member', 'agents', 'os',
'adapter',
// common.aboutPage receives a page title, currently Latin, not a count.
'adapter', 'title',
];

// The exception runs the safe way: a name that is usually opaque and is a number
Expand Down
20 changes: 19 additions & 1 deletion frontend/src/i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"close": "Close",
"copy": "Copy",
"copied": "Copied!",
"aboutPage": "About {{title}}",
"language": {
"label": "Language"
},
Expand Down Expand Up @@ -56,6 +57,13 @@
"activity": {
"title": "Activity",
"subtitle": "what needs you, then what moved",
"about": "About activity",
"head": {
"needsYou_one": "{{count}} needs you",
"needsYou_other": "{{count}} need you",
"moved_one": "{{count}} moved",
"moved_other": "{{count}} moved"
},
"controlsAriaLabel": "Activity controls",
"windowAriaLabel": "Activity window",
"windows": {
Expand Down Expand Up @@ -94,6 +102,7 @@
"updatesCount_one": "{{count}} update",
"updatesCount_other": "{{count}} updates",
"needsYou": {
"about": "About needs you",
"eyebrow": "Decision queue",
"title": "Needs you",
"description": "Direct mentions, approvals, and active decisions that need your call.",
Expand Down Expand Up @@ -166,6 +175,7 @@
}
},
"movedForward": {
"about": "About moved forward",
"title": "Moved forward",
"description": "grouped by pod · newest first",
"empty": "Nothing yet today.",
Expand Down Expand Up @@ -515,6 +525,7 @@
"none": "No recent heartbeats"
},
"header": {
"aboutPod": "About this pod",
"agentsWorking_one": "{{count}} agent working",
"agentsWorking_other": "{{count}} agents working",
"members_one": "{{count}} member",
Expand Down Expand Up @@ -1343,7 +1354,6 @@
}
},
"agentByo": {
"eyebrow": "Your own agent",
"title": "Bring your own agent",
"description": "Connect the Claude Code or Codex you already use, so it answers you in your pods. One command in Terminal, about 2 minutes.",
"form": {
Expand Down Expand Up @@ -1949,6 +1959,13 @@
}
},
"connectors": {
"about": "About connectors",
"counts": {
"connected_one": "{{count}} connected",
"connected_other": "{{count}} connected",
"needsYou_one": "{{count}} needs you",
"needsYou_other": "{{count}} need you"
},
"loading": "Loading connectors…",
"loadError": "Could not load connectors.",
"createError": "Could not create the connector.",
Expand Down Expand Up @@ -2096,6 +2113,7 @@
},
"artifacts": {
"title": "Artifacts",
"about": "About artifacts",
"meta": "every file and page shared in your pods",
"kindLabel": "Kind",
"podLabel": "Pod",
Expand Down
20 changes: 19 additions & 1 deletion frontend/src/i18n/locales/zh-CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"close": "关闭",
"copy": "复制",
"copied": "已复制",
"aboutPage": "关于 {{title}}",
"language": {
"label": "语言"
},
Expand Down Expand Up @@ -56,6 +57,13 @@
"activity": {
"title": "动态",
"subtitle": "需要你处理的事项,以及刚刚推进的工作",
"about": "关于动态",
"head": {
"needsYou_one": "{{count}}项需要你处理",
"needsYou_other": "{{count}}项需要你处理",
"moved_one": "{{count}}项已推进",
"moved_other": "{{count}}项已推进"
},
"controlsAriaLabel": "动态控制项",
"windowAriaLabel": "动态时间范围",
"windows": {
Expand Down Expand Up @@ -94,6 +102,7 @@
"updatesCount_one": "{{count}}条更新",
"updatesCount_other": "{{count}}条更新",
"needsYou": {
"about": "关于需要你处理的事项",
"eyebrow": "决策队列",
"title": "需要你处理",
"description": "这里只显示需要你回应的直接提及、审批和待决事项。",
Expand Down Expand Up @@ -166,6 +175,7 @@
}
},
"movedForward": {
"about": "关于推进的工作",
"title": "推进的工作",
"description": "按 Pod 分组 · 最新在前",
"empty": "今天还没有更新。",
Expand Down Expand Up @@ -515,6 +525,7 @@
"recent_other": "最近收到{{formattedCount}}次心跳"
},
"header": {
"aboutPod": "关于这个 Pod",
"agentsWorking_other": "{{count}}个智能体正在工作",
"members_one": "{{count}}名成员",
"members_other": "{{count}}名成员",
Expand Down Expand Up @@ -1403,7 +1414,6 @@
}
},
"agentByo": {
"eyebrow": "你自己的智能体",
"title": "接入你自己的智能体",
"description": "接入你已在用的 Claude Code 或 Codex,让它在你的 Pod 里回复你。只需在终端里运行一条命令,约2分钟完成。",
"form": {
Expand Down Expand Up @@ -1941,6 +1951,13 @@
}
},
"connectors": {
"about": "关于连接器",
"counts": {
"connected_one": "已连接{{count}}个频道",
"connected_other": "已连接{{count}}个频道",
"needsYou_one": "{{count}}项需要你处理",
"needsYou_other": "{{count}}项需要你处理"
},
"loading": "正在加载连接器…",
"loadError": "无法加载连接器。",
"createError": "无法创建连接器。",
Expand Down Expand Up @@ -2088,6 +2105,7 @@
},
"artifacts": {
"title": "产物",
"about": "关于产物",
"meta": "你所有 Pod 中共享的文件与页面",
"kindLabel": "类型",
"podLabel": "Pod",
Expand Down
28 changes: 6 additions & 22 deletions frontend/src/v2/V2App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -154,15 +154,13 @@ const V2UseCaseRedirect: React.FC = () => {

const feature = (
title: string,
description: string,
children: React.ReactNode,
showPodsSidebar = false,
showHeader = true,
className = '',
) => (
<V2FeaturePage
title={title}
description={description}
showPodsSidebar={showPodsSidebar}
showHeader={showHeader}
className={className}
Expand Down Expand Up @@ -243,7 +241,6 @@ const V2App: React.FC = () => {
path="pods/:podId/board"
element={feature(
'Board',
'Pod task board inside the v2 shell.',
<V2PodBoard />,
false,
/* V2PodBoard owns its own header (pod name + back). */
Expand All @@ -254,7 +251,6 @@ const V2App: React.FC = () => {
path="pods/:podType/:roomId"
element={feature(
'Pod Tools',
'Full pod chat, files, member, and agent tools without leaving v2.',
<ChatRoom />,
false,
/* ChatRoom owns its own sticky AppBar with title/tabs;
Expand All @@ -271,7 +267,6 @@ const V2App: React.FC = () => {
path="agents"
element={feature(
'Your Team',
'Agents you have hired across your projects.',
<V2YourTeamPage />,
false,
/* V2YourTeamPage owns its own header — suppress the
Expand All @@ -281,26 +276,20 @@ const V2App: React.FC = () => {
/>
<Route
path="agents/manage"
element={feature('Manage agents', 'Installed agents and registry operations.', <AgentsHub />)}
element={feature('Manage agents', <AgentsHub />)}
/>
<Route
path="agents/byo"
element={<V2AgentBYO />}
/>
<Route
path="connect"
element={feature(
'Connect',
'Set up the CLI, MCP, and the agents your team runs.',
<V2ConnectPage />,
false,
)}
element={<V2FeaturePage title="Connect" hint="Set up the CLI, MCP, and the agents your team runs."><V2ConnectPage /></V2FeaturePage>}
/>
<Route
path="connectors"
element={feature(
'Connectors',
'Bridge pods to the channels your team already uses.',
<V2ConnectorsPage />,
false,
// The Signal artboard owns this page's display-sized head.
Expand All @@ -309,15 +298,15 @@ const V2App: React.FC = () => {
/>
<Route
path="artifacts"
element={feature('Artifacts', undefined, <V2ArtifactsPage />, false, false, 'v2-feature--artifacts')}
element={feature('Artifacts', <V2ArtifactsPage />, false, false, 'v2-feature--artifacts')}
/>
<Route
path="activity"
element={feature('Activity', 'Review updates, mentions, approvals, pod activity, and unread items.', <V2ActivityPage />, false, false, 'v2-feature--activity')}
element={feature('Activity', <V2ActivityPage />, false, false, 'v2-feature--activity')}
/>
<Route
path="settings"
element={feature('Settings', undefined, <V2SettingsPage />, false, false)}
element={feature('Settings', <V2SettingsPage />, false, false)}
/>
<Route
path="settings/devices"
Expand All @@ -329,13 +318,12 @@ const V2App: React.FC = () => {
/>
<Route
path="profile/:id"
element={feature('Profile', 'Public profile, activity, and pod membership.', <UserProfile />)}
element={feature('Profile', <UserProfile />)}
/>
<Route
path="admin/integrations/global"
element={feature(
'Global Integrations',
'Global integration administration inside v2.',
<ProtectedRoute requireAdmin><GlobalIntegrations /></ProtectedRoute>,
false,
)}
Expand All @@ -344,7 +332,6 @@ const V2App: React.FC = () => {
path="admin/users"
element={feature(
'User Admin',
'Review waitlist requests and manage invitation codes.',
<ProtectedRoute requireAdmin><V2AdminUsers /></ProtectedRoute>,
false,
)}
Expand All @@ -353,7 +340,6 @@ const V2App: React.FC = () => {
path="admin/analytics"
element={feature(
'Usage Analytics',
'Activation funnel, signups, messages, and active-user counts for this instance.',
<ProtectedRoute requireAdmin><V2AdminAnalytics /></ProtectedRoute>,
false,
)}
Expand All @@ -362,7 +348,6 @@ const V2App: React.FC = () => {
path="dev/api"
element={feature(
'API Dev',
'Developer API inspection tools inside v2.',
<ProtectedRoute requireAdmin><ApiDevPage /></ProtectedRoute>,
false,
)}
Expand All @@ -371,7 +356,6 @@ const V2App: React.FC = () => {
path="dev/pod-context"
element={feature(
'Pod Context Dev',
'Pod context inspection tools inside v2.',
<ProtectedRoute requireAdmin><PodContextDevPage /></ProtectedRoute>,
false,
)}
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/v2/__tests__/V2Composer.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ describe('V2Composer send button', () => {
const header = view.container.querySelector('.v2-pod-header');
expect(header).toBeTruthy();
expect(header.querySelector('h1')).toHaveTextContent('My Workspace');
expect(header.querySelector('.v2-thread__title p')).toHaveTextContent('Team discussion');
expect(header.querySelector('.v2-hint__content')).toHaveTextContent('Team discussion');
const meta = header.querySelector('.v2-pod-header__meta:not(.v2-pod-header__meta--compact)');
expect(meta).toHaveTextContent('2 members');
expect(meta).toHaveTextContent('1 agent');
Expand Down
22 changes: 22 additions & 0 deletions frontend/src/v2/__tests__/V2ConnectorsPage.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -986,6 +986,28 @@ describe('V2ConnectorsPage', () => {
});
};

it('counts connected channels in the page head, not catalogue options', async () => {
mockCatalog([
entry(),
entry({
installableId: 'slack', label: 'Slack',
installation: { status: 'active', boundPodId: 'p1' },
integration: liveIntegration(),
}),
]);
const { container } = renderPage();

await waitFor(() => expect(container.querySelector('.v2-connectors__header .v2-page-head__meta')?.textContent).toBe('1 connected'));
});

it('omits the connected count when the catalogue has no live channels', async () => {
mockCatalog([entry(), entry({ installableId: 'discord', label: 'Discord' })]);
const { container } = renderPage();

await screen.findByRole('button', { name: 'View Telegram' });
expect(container.querySelector('.v2-connectors__header .v2-page-head__meta')).toBeNull();
});

it('TASK-131: a catalog failure prints the generic sentence, not the raw exception that wrote the row', async () => {
// The catalogue half reads `InstallableInstallation.errorMessage`, whose
// projection-failure writer stores `error.message` verbatim. Same rule as
Expand Down
Loading
Loading