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
1 change: 1 addition & 0 deletions frontend/src/v2/V2App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,7 @@ const V2App: React.FC = () => {
false,
/* V2PodBoard owns its own header (pod name + back). */
false,
'v2-feature--board',
)}
/>
<Route
Expand Down
7 changes: 6 additions & 1 deletion frontend/src/v2/__tests__/V2AgentBYOMemory.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,13 @@ import { render, screen, fireEvent, waitFor, act } from '@testing-library/react'
import { MemoryRouter } from 'react-router-dom';
import V2AgentBYO from '../components/V2AgentBYO';
import { AuthContext } from '../../context/AuthContext';
// This suite owns the BYO request sequence, not the rail's attention query.
// This suite owns the BYO request sequence, not the attention queries the chrome
// makes. Each one would otherwise consume a queued pod-list response.
jest.mock('../components/V2NavRail', () => () => null);
jest.mock('../hooks/useV2PodAttention', () => ({
...jest.requireActual('../hooks/useV2PodAttention'),
useV2PodAttention: () => ({ items: [], count: null, countByPod: {}, refresh: async () => {} }),
}));

jest.mock('axios', () => {
const mock = {
Expand Down
7 changes: 6 additions & 1 deletion frontend/src/v2/__tests__/V2AgentBYOPodPicker.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,13 @@ import { MemoryRouter } from 'react-router-dom';
import V2AgentBYO from '../components/V2AgentBYO';
import { AuthContext } from '../../context/AuthContext';
import { POD_VISITS_KEY } from '../lib/podRecency';
// This suite owns the BYO request sequence, not the rail's attention query.
// This suite owns the BYO request sequence, not the attention queries the chrome
// makes. Each one would otherwise consume a queued pod-list response.
jest.mock('../components/V2NavRail', () => () => null);
jest.mock('../hooks/useV2PodAttention', () => ({
...jest.requireActual('../hooks/useV2PodAttention'),
useV2PodAttention: () => ({ items: [], count: null, countByPod: {}, refresh: async () => {} }),
}));

jest.mock('axios', () => {
const mock = {
Expand Down
38 changes: 38 additions & 0 deletions frontend/src/v2/__tests__/v2-layout-invariants.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5066,3 +5066,41 @@ test('OpenUI cards keep the approved phone controls and table records', () => {
expect(css).toContain('.v2-root .v2-msg.v2-ui-card { display:flex; flex-direction:column;');
expect(css).toContain('.ui.gone{border-style:dashed');
});

describe('v2 board on a phone (TASK-576)', () => {
test('the board head wraps so the title keeps a full-width row above the actions', () => {
const css = read('../v2.css');
const phone = mediaBlockContaining(css, '.v2-board__head');
expect(ruleBody(phone, '.v2-board__head')).toContain('flex-wrap: wrap');
expect(ruleBody(phone, '.v2-board__title-wrap')).toContain('flex: 1 1 100%');
expect(ruleBody(phone, '.v2-board__title-wrap')).toContain('order: 1');
expect(ruleBody(phone, '.v2-board__new')).toContain('margin-left: auto');
});

test('the board drops the feature body gutter on a phone', () => {
const css = read('../v2.css');
const phone = mediaBlockContaining(css, '.v2-feature--board > .v2-feature__body');
expect(ruleBody(phone, '.v2-feature--board > .v2-feature__body')).toMatch(/padding:\s*0;/);
});

test('the board route passes the class that opts it into that override', () => {
expect(read('../V2App.tsx')).toMatch(
/<V2PodBoard \/>,\s*false,\s*\/\*[^*]*\*\/\s*false,\s*'v2-feature--board'/,
);
});

test('feature panes reserve the tab bar height so the body stops above the fixed nav', () => {
const css = read('../v2.css');
const phone = mediaBlockContaining(css, 'padding-bottom: 56px');
expect(ruleBody(phone, '.v2-feature')).toContain('padding-bottom: 56px');
expect(ruleBody(phone, '.v2-mobile-tabs')).toContain('height: 56px');
});

test('feature pages mount the phone tab bar and share one attention count with the rail', () => {
const page = read('../components/V2FeaturePage.tsx');
expect(page).toContain("import V2MobileTabs from './V2MobileTabs';");
expect(page).toContain('const attention = useV2PodAttention();');
expect(page).toContain('<V2NavRail needsYouCount={attention.count} />');
expect(page).toContain('<V2MobileTabs needsYouCount={attention.count ?? 0} />');
});
});
6 changes: 5 additions & 1 deletion frontend/src/v2/components/V2FeaturePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { useTranslation } from 'react-i18next';
import V2NavRail from './V2NavRail';
import V2PodsSidebar from './V2PodsSidebar';
import { V2EmbeddedProvider } from '../hooks/useV2Embedded';
import { useV2PodAttention } from '../hooks/useV2PodAttention';
import V2MobileTabs from './V2MobileTabs';
import V2PageHead from './V2PageHead';

interface V2FeaturePageProps {
Expand Down Expand Up @@ -298,6 +300,7 @@ const V2FeaturePage: React.FC<V2FeaturePageProps> = ({
}) => {
const navigate = useNavigate();
const { t } = useTranslation();
const attention = useV2PodAttention();

useEffect(() => {
try {
Expand Down Expand Up @@ -325,7 +328,7 @@ const V2FeaturePage: React.FC<V2FeaturePageProps> = ({

return (
<div className={`v2-shell${showPodsSidebar ? ' v2-shell--feature' : ' v2-shell--feature-wide'}`}>
<V2NavRail />
<V2NavRail needsYouCount={attention.count} />
{showPodsSidebar && <V2PodsSidebar selectedPodId={null} />}
<main className={`v2-pane v2-pane--main v2-feature${className ? ` ${className}` : ''}`} onClickCapture={handleClickCapture} aria-label={title}>
{showHeader && <V2PageHead className="v2-feature__header" titleClassName="v2-feature__title" title={title} hint={hint} hintLabel={t('common.aboutPage', { title })} />}
Expand All @@ -339,6 +342,7 @@ const V2FeaturePage: React.FC<V2FeaturePageProps> = ({
</ThemeProvider>
</section>
</main>
<V2MobileTabs needsYouCount={attention.count ?? 0} />
</div>
);
};
Expand Down
26 changes: 26 additions & 0 deletions frontend/src/v2/v2.css
Original file line number Diff line number Diff line change
Expand Up @@ -9003,11 +9003,31 @@ body.modern-ui.v2-canvas {
}

@media (max-width: 760px) {
/* At 390 the head was one nowrap row (back, subtitle, New task), which squeezed
the title to under 1px. On a phone the title takes its own full-width row;
back and New task share the row above it. */
.v2-board__head {
flex-wrap: wrap;
row-gap: 8px;
padding: 12px 14px;
gap: 10px;
}

.v2-board__title-wrap {
order: 1;
flex: 1 1 100%;
}

.v2-board__new {
margin-left: auto;
}

/* The board is the phone's full-width surface. Its own head and columns carry
the 14px inset, so the feature body does not add its 34px desktop gutter. */
.v2-feature--board > .v2-feature__body {
padding: 0;
}

.v2-board__columns {
padding: 12px 14px 20px;
}
Expand Down Expand Up @@ -12215,6 +12235,12 @@ body.modern-ui.v2-canvas {
padding: 6px 8px 4px;
}

/* Feature pages mount this same bar (V2FeaturePage). Reserve its height on
the pane so a scrolling body stops above the fixed bar, not under it. */
.v2-feature {
padding-bottom: 56px;
}

.v2-root button.v2-mobile-tabs__item {
display: flex;
width: auto;
Expand Down
Loading