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
5 changes: 5 additions & 0 deletions .changeset/dashboard-platform-label.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@tapflowio/relay": patch
---

Dashboard: platform badges in App Center's build rows and in Settings → Apps, and the QA session's "No agents available for …" line, read `iOS`, `Android` and `Both`. They read `Ios`, or `ios` in the QA session line, because the lowercase API value was shown as it came, with only CSS capitalization on the badges.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Fixed

- **Platform badges say iOS, not Ios.** App Center's build rows, Settings → Apps and the QA session's "No agents available" line now name the platform `iOS`, `Android` or `Both`.

- **Long dashboard pages no longer scroll the whole window.** On Settings → Default and other long forms the window grew a second scrollbar and the header could scroll out of view, because a screen-reader-only status line near the bottom of a form was stretching the page. Only the page area scrolls now.

- **The dashboard no longer flashes light before switching to dark mode.** With dark mode set, or the system theme dark, each page load first painted light while the app loaded. The theme is now applied before the first paint.
Expand Down
6 changes: 3 additions & 3 deletions packages/dashboard/components/app-center/BuildRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import { Separator } from '@/components/ui/separator';
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
import { Play, SquarePlay, Trash2, TimerOff } from 'lucide-react';
import type { Build } from '@/lib/types';
import { STATUS_TONE, buildRowName, formatDeletionCountdown } from '@/lib/build-format';
import { PLATFORM_LABEL, STATUS_TONE, buildRowName, formatDeletionCountdown } from '@/lib/build-format';

interface Props {
build: Build;
Expand Down Expand Up @@ -105,9 +105,9 @@ export function BuildRow({
</span>
<Badge
tone={build.platform === 'ios' ? 'ios' : 'android'}
className="text-xs capitalize"
className="text-xs"
>
{build.platform}
{PLATFORM_LABEL[build.platform]}
</Badge>
{build.status_label && (
<Badge tone={STATUS_TONE[build.status_label]} className="text-xs">
Expand Down
9 changes: 8 additions & 1 deletion packages/dashboard/lib/build-format.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Build } from '@/lib/types'
import type { App, Build } from '@/lib/types'

export const STATUS_TONE = {
Backlog: 'backlog',
Expand All @@ -7,6 +7,13 @@ export const STATUS_TONE = {
Rejected: 'rejected',
} as const satisfies Record<string, 'backlog' | 'progress' | 'done' | 'rejected'>

/** How a platform is named on screen. The API value is lowercase, and CSS `capitalize` would make it "Ios". */
export const PLATFORM_LABEL = {
ios: 'iOS',
android: 'Android',
both: 'Both',
} as const satisfies Record<App['platform'], string>

export function buildLabel(build: Build): string {
if (build.version_name && build.build_number) return `${build.version_name} · build ${build.build_number}`
return build.version_name ?? build.version_label ?? (build.build_number ? `build ${build.build_number}` : 'Build')
Expand Down
9 changes: 9 additions & 0 deletions packages/dashboard/src/__tests__/BuildRow.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -141,3 +141,12 @@ describe('BuildRow — Viewer (canWrite false)', () => {
expect(screen.getByRole('button', { name: 'Cancel scheduled deletion of ios build 1, 1.0.0' })).toBeTruthy()
})
})

// The platform comes from the API in lowercase. CSS `capitalize` used to dress it up, which drew "Ios".
describe('BuildRow — platform badge', () => {
it.each([['ios', 'iOS'], ['android', 'Android']] as const)('%s reads %s', (platform, label) => {
renderRow(makeBuild({ platform }))
expect(screen.getByText(label, { exact: true })).toBeTruthy()
cleanup()
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,13 @@ describe('Default settings — what it loads', () => {
expect(await screen.findByDisplayValue('Coffee')).toBeInTheDocument()
})

// `platform` arrives lowercase; the badge names it the way the platform does ("Ios" was CSS `capitalize`).
it('names the platform of each app', async () => {
auth.user = user('Developer')
renderPage()
expect(await screen.findByText('iOS', { exact: true })).toBeInTheDocument()
})

// QA manages apps like Developer; Viewer is the one role that does not.
// Mutation: drop 'QA' from `canEditApps` → this fails; the Viewer twin below holds the other side.
it('lists the apps for QA', async () => {
Expand Down
14 changes: 14 additions & 0 deletions packages/dashboard/src/__tests__/QASession.aiHolder.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,17 @@ describe('QASession — a device an AI agent is driving', () => {
expect(screen.getByRole('button', { name: /iPhone SE/ }).textContent).toContain('In use')
})
})

describe('QASession — no agent for the build', () => {
// The platform arrives lowercase; the sentence names it the way the platform does.
it('names the platform as iOS', async () => {
render(withQuery(
<MemoryRouter initialEntries={['/qa?id=7']}>
<BreadcrumbProvider><QASession /></BreadcrumbProvider>
</MemoryRouter>,
))
await vi.waitFor(() => expect(deliver).not.toBeNull())
await act(async () => { deliver!({ type: 'agents:listed', sessions: [] }) })
expect(await screen.findByText('No agents available for iOS.', { exact: true })).toBeTruthy()
})
})
4 changes: 2 additions & 2 deletions packages/dashboard/src/pages/QASession.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import {
BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator,
} from '@/components/ui/breadcrumb';
import { cn } from '@/lib/utils';
import { STATUS_TONE, buildLabel } from '@/lib/build-format';
import { PLATFORM_LABEL, STATUS_TONE, buildLabel } from '@/lib/build-format';
import { Switch } from '@/components/ui/switch';
import { Label } from '@/components/ui/label';
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
Expand Down Expand Up @@ -319,7 +319,7 @@ export function QASession() {

{agentGroups.length === 0 ? (
<p className="text-sm text-muted-foreground">
{connected ? `No agents available for ${os}.` : 'Connecting to relay…'}
{connected ? `No agents available for ${PLATFORM_LABEL[os]}.` : 'Connecting to relay…'}
</p>
) : (
<div className="grid grid-cols-1 lg:grid-cols-2 2xl:grid-cols-3 gap-2">
Expand Down
4 changes: 2 additions & 2 deletions packages/dashboard/src/pages/settings/Default.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { FieldHint } from '@/components/ui/field-hint'
import { PASSWORD_HINT, PASSWORD_MIN_LENGTH, PASSWORD_TOO_SHORT, describedBy } from '@/lib/password'
import { Label } from '@/components/ui/label'
import { Badge } from '@/components/ui/badge'
import { PLATFORM_LABEL } from '@/lib/build-format'
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
import { Separator } from '@/components/ui/separator'
import {
Expand Down Expand Up @@ -361,9 +362,8 @@ export function DefaultSettings() {
<Badge
tone={app.platform === 'ios' ? 'ios' : app.platform === 'android' ? 'android' : undefined}
variant={app.platform === 'both' ? 'secondary' : undefined}
className="capitalize"
>
{app.platform}
{PLATFORM_LABEL[app.platform]}
</Badge>
</div>
<div className="flex items-center justify-end gap-2">
Expand Down
Loading