Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
a79c889
docs(cockpit): design for making Run discoverable from Code
blove Sep 1, 2026
0939ea4
docs(cockpit): implementation plan for Run discoverability
blove Sep 1, 2026
e17f4ac
fix(cockpit): stop persisting the active mode so every capability ope…
blove Sep 1, 2026
12321da
fix(cockpit): restore docs-surface coverage, key shell on route, tigh…
blove Sep 1, 2026
d5b12b6
docs(cockpit): correct the plan's verification commands
blove Sep 1, 2026
8b39d2f
test(cockpit): add real regression coverage for the per-route Cockpit…
blove Sep 1, 2026
4fc1f3e
feat(cockpit): make the mode rail read as a switch
blove Sep 1, 2026
d9d5cfc
fix(cockpit): make rail separator visible and group label accessible
blove Sep 1, 2026
917b6c4
feat(ui-react): add a status dot slot to control plane rail items
blove Sep 1, 2026
25665aa
fix(ui-react): fold rail item status into one object prop
blove Sep 1, 2026
9e704de
docs(cockpit): align Task 4 with the folded rail status prop
blove Sep 1, 2026
fad9577
feat(cockpit): show runtime phase on the Run rail item
blove Sep 1, 2026
ecc3d1b
fix(cockpit): separate the rail tooltip from the status accessible name
blove Sep 1, 2026
e6a0bae
feat(cockpit): re-scope the Activity indicator to unseen problems
blove Sep 1, 2026
cb44eb4
docs(cockpit): correct the activity log ordering claim
blove Sep 1, 2026
180d3f6
docs(cockpit): runtimeNeedsAttention is dead, not live
blove Sep 1, 2026
7c6d117
refactor(cockpit): drop dead runtimeNeedsAttention and cover the clea…
blove Sep 1, 2026
e87eb94
docs(cockpit): record Task 6 browser verification results
blove Sep 1, 2026
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
72 changes: 72 additions & 0 deletions apps/cockpit/src/app/[...slug]/page.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/** @vitest-environment jsdom */
import { describe, expect, it, vi } from 'vitest';

vi.mock('next/navigation', () => ({
redirect: vi.fn(() => {
throw new Error('redirect() should not be called for a canonical slug');
}),
}));

vi.mock('../../lib/content-bundle', () => ({
getContentBundle: vi.fn().mockResolvedValue({
codeFiles: {},
promptFiles: {},
runtimeUrl: null,
docSections: [],
narrativeDocs: [],
}),
}));

import CockpitRoutePage from './page';
import { getCockpitPageModel } from '../../lib/cockpit-page';

describe('CockpitRoutePage', () => {
it('keys the rendered CockpitShell on the canonical path', async () => {
const slug = [
'langgraph',
'core-capabilities',
'streaming',
'overview',
'python',
];
const { canonicalPath } = getCockpitPageModel(slug);

const element = await CockpitRoutePage({
params: Promise.resolve({ slug }),
});

expect(element.key).toBe(canonicalPath);
});

it('gives two different capabilities two different keys', async () => {
const streamingSlug = [
'langgraph',
'core-capabilities',
'streaming',
'overview',
'python',
];
const persistenceSlug = [
'langgraph',
'core-capabilities',
'persistence',
'overview',
'python',
];

const streamingElement = await CockpitRoutePage({
params: Promise.resolve({ slug: streamingSlug }),
});
const persistenceElement = await CockpitRoutePage({
params: Promise.resolve({ slug: persistenceSlug }),
});

expect(streamingElement.key).not.toBe(persistenceElement.key);
expect(streamingElement.key).toBe(
getCockpitPageModel(streamingSlug).canonicalPath
);
expect(persistenceElement.key).toBe(
getCockpitPageModel(persistenceSlug).canonicalPath
);
});
});
2 changes: 2 additions & 0 deletions apps/cockpit/src/app/[...slug]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import React from 'react';
import { redirect } from 'next/navigation';
import { CockpitShell } from '../../components/cockpit-shell';
import { getContentBundle } from '../../lib/content-bundle';
Expand Down Expand Up @@ -27,6 +28,7 @@ export default async function CockpitRoutePage({

return (
<CockpitShell
key={canonicalPath}
navigationTree={navigationTree}
presentation={presentation}
entryTitle={entry.title}
Expand Down
48 changes: 46 additions & 2 deletions apps/cockpit/src/app/cockpit.css
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,7 @@ pre.shiki {
.cockpit-control-plane {
--cockpit-state-error: #b42318;
--cockpit-state-success: #1a7a40;
--cockpit-state-working: #9a6700;
display: grid;
grid-template-columns: 56px minmax(0, 272px);
height: 100%;
Expand All @@ -373,6 +374,7 @@ pre.shiki {
[data-theme="dark"] .cockpit-control-plane {
--cockpit-state-error: #ff6369;
--cockpit-state-success: #4cc38a;
--cockpit-state-working: #e0a02f;
}
.cockpit-control-plane [data-control-plane-rail] {
min-width: 0;
Expand All @@ -387,9 +389,28 @@ pre.shiki {
flex-direction: column;
gap: 4px;
}
.cockpit-control-plane [data-control-plane-rail-group="utilities"] { margin-top: auto; }
.cockpit-control-plane [data-control-plane-rail-group="utilities"] {
margin-top: auto;
padding-top: 8px;
/* --ds-border is a 1-value difference from --ds-surface-tinted (the rail
background) in dark mode -- rgb(45,45,45) on rgb(44,44,44), effectively
invisible. --ds-border-strong is the token the pane divider already
uses for the same reason (cockpit.css ~L460). */
border-top: 1px solid var(--ds-border-strong);
}
.cockpit-control-plane [data-control-plane-rail-group-label] {
display: block;
padding-bottom: 4px;
color: var(--ds-text-secondary);
font-size: 10px;
font-weight: 600;
letter-spacing: 0.09em;
text-transform: uppercase;
text-align: center;
}
.cockpit-control-plane-utility-anchor { display: contents; }
.cockpit-control-plane [data-control-plane-rail-item] {
--cockpit-rail-status-ring: var(--ds-surface-tinted);
position: relative;
min-height: 48px;
padding: 6px 2px;
Expand All @@ -400,13 +421,14 @@ pre.shiki {
align-items: center;
justify-content: center;
gap: 4px;
color: var(--ds-text-muted);
color: var(--ds-text-secondary);
background: transparent;
text-decoration: none;
cursor: pointer;
transition: background 120ms ease, color 120ms ease;
}
.cockpit-control-plane [data-control-plane-rail-item]:hover {
--cockpit-rail-status-ring: var(--ds-surface);
color: var(--ds-text-primary);
background: var(--ds-surface);
}
Expand All @@ -419,6 +441,24 @@ pre.shiki {
line-height: 1;
font-weight: 600;
}
.cockpit-control-plane [data-control-plane-rail-status] {
position: absolute;
top: 7px;
right: 11px;
width: 7px;
height: 7px;
border: 2px solid var(--cockpit-rail-status-ring);
border-radius: 999px;
}
.cockpit-control-plane [data-control-plane-rail-status="success"] {
background: var(--cockpit-state-success);
}
.cockpit-control-plane [data-control-plane-rail-status="working"] {
background: var(--cockpit-state-working);
}
.cockpit-control-plane [data-control-plane-rail-status="error"] {
background: var(--cockpit-state-error);
}
.cockpit-control-plane [data-control-plane-rail-icon],
[data-cockpit-activity-icon] {
display: inline-flex;
Expand Down Expand Up @@ -950,6 +990,10 @@ pre.shiki {
color: CanvasText;
background: Canvas;
}
.cockpit-control-plane [data-control-plane-rail-status] {
border: 1px solid CanvasText;
background: Canvas;
}
.cockpit-control-plane [data-control-plane-overflow-menu] {
border: 1px solid CanvasText;
color: CanvasText;
Expand Down
Loading
Loading