Compare commits
1 Commits
main
...
claude/mob
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6f2eda521f |
41
apps/web/e2e/mobile-font-size.spec.ts
Normal file
41
apps/web/e2e/mobile-font-size.spec.ts
Normal file
@ -0,0 +1,41 @@
|
||||
import { expect, type Locator, test } from "@playwright/test";
|
||||
import { captureScreenshot, completeOnboarding, signup } from "./helpers";
|
||||
|
||||
async function fontSize(locator: Locator) {
|
||||
return locator.evaluate((element) => Number.parseFloat(getComputedStyle(element).fontSize));
|
||||
}
|
||||
|
||||
test("phone widths read text at 16px and up while desktop keeps its sizes", async ({
|
||||
page,
|
||||
}, testInfo) => {
|
||||
const stamp = Date.now();
|
||||
await signup(page, `mobile-font-${stamp}@rakazo.test`, "password12", "Font QA");
|
||||
await completeOnboarding(page);
|
||||
|
||||
const composer = page.getByRole("combobox", { name: /^Message/ });
|
||||
await composer.fill("Please review `shared/PROJECT_CHECKPOINT_WRAPUP.md`.");
|
||||
await composer.press("Enter");
|
||||
const reply = page
|
||||
.getByTestId("transcript")
|
||||
.locator("[data-message-id]")
|
||||
.filter({ hasText: "done. i handled:" });
|
||||
await expect(reply).toBeVisible({ timeout: 30_000 });
|
||||
|
||||
const smallText = page.locator(".text-ui-body-sm").first();
|
||||
const desktopSmall = await fontSize(smallText);
|
||||
expect(desktopSmall).toBe(14);
|
||||
|
||||
await page.setViewportSize({ width: 390, height: 844 });
|
||||
await expect.poll(() => fontSize(smallText)).toBeCloseTo(16, 1);
|
||||
expect(await fontSize(composer)).toBeGreaterThanOrEqual(16);
|
||||
|
||||
await page.emulateMedia({ colorScheme: "dark" });
|
||||
await expect(page.locator("html")).toHaveAttribute("data-theme", "dark");
|
||||
await captureScreenshot(page, testInfo, "mobile-font-size-dark");
|
||||
await page.emulateMedia({ colorScheme: "light" });
|
||||
await expect(page.locator("html")).toHaveAttribute("data-theme", "light");
|
||||
await captureScreenshot(page, testInfo, "mobile-font-size-light");
|
||||
|
||||
await page.setViewportSize({ width: 1280, height: 720 });
|
||||
await expect.poll(() => fontSize(smallText)).toBe(desktopSmall);
|
||||
});
|
||||
@ -251,7 +251,7 @@ export function OoStage({
|
||||
) : null}
|
||||
</>
|
||||
) : (
|
||||
<p className="mt-1 max-w-xl truncate text-center text-ui-caption text-muted-foreground">
|
||||
<p className="mt-1 w-full max-w-xl truncate text-center text-ui-caption text-muted-foreground">
|
||||
{subtitle}
|
||||
</p>
|
||||
)}
|
||||
@ -548,7 +548,7 @@ export function OoGroupStage({
|
||||
{subtitle}
|
||||
</p>
|
||||
) : (
|
||||
<p className="mt-1 max-w-xl truncate text-center text-ui-caption text-muted-foreground">
|
||||
<p className="mt-1 w-full max-w-xl truncate text-center text-ui-caption text-muted-foreground">
|
||||
{subtitle}
|
||||
</p>
|
||||
)}
|
||||
|
||||
@ -80,7 +80,10 @@ export function ExternalConversationSettings({
|
||||
return (
|
||||
<div data-testid="external-conversation-settings">
|
||||
<div className="mb-6">
|
||||
<h2 className="truncate text-[17px] font-medium text-foreground" dir="auto">
|
||||
<h2
|
||||
className="truncate text-ui-reading leading-normal font-medium text-foreground"
|
||||
dir="auto"
|
||||
>
|
||||
{conversation.displayName || t`External conversation`}
|
||||
</h2>
|
||||
<p className="mt-1 truncate text-ui-body-sm text-muted-foreground">
|
||||
|
||||
@ -134,7 +134,7 @@ export function PeerMessagesOverlay({
|
||||
<div className="mb-1 text-ui-body-sm text-muted-foreground/70" dir="auto">
|
||||
{sent ? botName : peerBotName}
|
||||
</div>
|
||||
<div className="text-[14.5px] leading-[1.5] text-foreground/90" dir="auto">
|
||||
<div className="text-ui-body-sm leading-[1.5] text-foreground/90" dir="auto">
|
||||
<ChatMarkdown>{peerMessage.text}</ChatMarkdown>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -558,7 +558,9 @@ export function PluginsOverlay({
|
||||
{item.name[0]}
|
||||
</div>
|
||||
)}
|
||||
<div className="truncate text-[17px] font-medium text-foreground">{item.name}</div>
|
||||
<div className="truncate text-ui-reading leading-normal font-medium text-foreground">
|
||||
{item.name}
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
type="button"
|
||||
|
||||
@ -156,7 +156,7 @@ export function RoutineListRow({
|
||||
)}
|
||||
</span>
|
||||
<span className="min-w-0 flex-1">
|
||||
<span className="block truncate text-[14.5px] font-medium text-foreground" dir="auto">
|
||||
<span className="block truncate text-ui-body-sm font-medium text-foreground" dir="auto">
|
||||
{routineName(routine)}
|
||||
</span>
|
||||
<span className="block truncate text-ui-body-sm text-muted-foreground/80">
|
||||
@ -522,7 +522,7 @@ function MessageTriggerCard({ provider, onRemove }: { provider: string; onRemove
|
||||
className="text-muted-foreground size-icon-md"
|
||||
aria-hidden
|
||||
/>
|
||||
<span className="flex-1 text-[14.5px] text-foreground">{label}</span>
|
||||
<span className="flex-1 text-ui-body-sm text-foreground">{label}</span>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon-xs"
|
||||
@ -587,7 +587,7 @@ function InboundTriggerCard({
|
||||
) : (
|
||||
<Globe strokeWidth={1.6} className="text-muted-foreground size-icon-md" aria-hidden />
|
||||
)}
|
||||
<span className="flex-1 text-[14.5px] text-foreground">
|
||||
<span className="flex-1 text-ui-body-sm text-foreground">
|
||||
{kind === "github" ? <Trans>Git event</Trans> : <Trans>When a webhook fires</Trans>}
|
||||
</span>
|
||||
<Button
|
||||
|
||||
@ -229,9 +229,9 @@ export function RoutineSchedule({
|
||||
<div className="mt-2 rounded-xl border border-border p-3">
|
||||
<div className="flex items-center gap-2.5 px-0.5">
|
||||
<Clock size={17} strokeWidth={1.6} className="shrink-0 text-muted-foreground" aria-hidden />
|
||||
<span className="text-[14.5px] text-foreground">{lead}</span>
|
||||
<span className="text-ui-body-sm text-foreground">{lead}</span>
|
||||
{detail ? (
|
||||
<span className="flex-1 text-[14.5px] text-muted-foreground">{detail}</span>
|
||||
<span className="flex-1 text-ui-body-sm text-muted-foreground">{detail}</span>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="mt-2.5 flex flex-wrap items-center gap-2 text-sm text-muted-foreground">
|
||||
|
||||
@ -124,7 +124,7 @@ export function ScratchpadSection({ botId }: { botId: string }) {
|
||||
/>
|
||||
<div className="min-w-0 flex-1">
|
||||
<div
|
||||
className={`text-start text-[14.5px] ${item.status === "done" ? "text-muted-foreground/80 line-through" : "text-foreground"}`}
|
||||
className={`text-start text-ui-body-sm ${item.status === "done" ? "text-muted-foreground/80 line-through" : "text-foreground"}`}
|
||||
dir="auto"
|
||||
>
|
||||
{item.title}
|
||||
|
||||
@ -21,7 +21,7 @@ export function WelcomePage() {
|
||||
<p className="text-[27px] leading-[1.35] text-foreground/90 font-medium">
|
||||
<Trans>Your specialists. One calm place to talk.</Trans>
|
||||
</p>
|
||||
<p className="text-[18px] leading-[1.5] text-foreground/60">
|
||||
<p className="text-ui-title-sm leading-[1.5] text-foreground/60">
|
||||
<Trans>Simple to start. Expert when you need it.</Trans>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@ -383,7 +383,7 @@ function ChartCanvas({
|
||||
return (
|
||||
<div className="text-foreground/75">
|
||||
{meta.title ? (
|
||||
<div className="mb-1 text-[14.5px] font-semibold text-foreground">{meta.title}</div>
|
||||
<div className="mb-1 text-ui-body-sm font-semibold text-foreground">{meta.title}</div>
|
||||
) : null}
|
||||
{meta.swatches.length > 0 ? (
|
||||
<div className="mb-2 flex flex-wrap gap-x-3 gap-y-1">
|
||||
@ -460,7 +460,7 @@ export function McpApprovalCard({
|
||||
<span className="grid size-control-sm place-items-center rounded-lg bg-muted text-ui-label text-foreground">
|
||||
M
|
||||
</span>
|
||||
<span className="text-[14.5px] font-medium text-foreground">
|
||||
<span className="text-ui-body-sm font-medium text-foreground">
|
||||
<Trans>Connect MCP server “{name}”</Trans>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@ -811,7 +811,7 @@ const MessageView = memo(function MessageView({
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<span
|
||||
className="text-[14.5px] font-semibold text-foreground tracking-tight"
|
||||
className="text-ui-body-sm font-semibold text-foreground tracking-tight"
|
||||
dir="auto"
|
||||
>
|
||||
{effectiveSpeakerName}
|
||||
@ -1048,7 +1048,7 @@ const MessageView = memo(function MessageView({
|
||||
</div>
|
||||
<div className="mt-2 text-ui-body text-muted-foreground">{block.task}</div>
|
||||
{block.progress || block.result ? (
|
||||
<div className="mt-2.5 text-[14.5px] leading-[1.5] text-foreground/75">
|
||||
<div className="mt-2.5 text-ui-body-sm leading-[1.5] text-foreground/75">
|
||||
<ChatMarkdown streaming={running}>
|
||||
{block.result || block.progress || ""}
|
||||
</ChatMarkdown>
|
||||
@ -1095,7 +1095,7 @@ const MessageView = memo(function MessageView({
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="mt-2 text-[14.5px] leading-[1.5] text-foreground/75" dir="auto">
|
||||
<div className="mt-2 text-ui-body-sm leading-[1.5] text-foreground/75" dir="auto">
|
||||
{removed
|
||||
? block.status === "archived"
|
||||
? t`Archived. Chat, memory, and files kept.`
|
||||
@ -1201,7 +1201,7 @@ const MessageView = memo(function MessageView({
|
||||
className={
|
||||
encounterMode
|
||||
? "max-w-full whitespace-pre-wrap wrap-anywhere rounded-2xl bg-chat-user px-3.5 py-2 text-ui-body-sm leading-relaxed text-chat-user-foreground"
|
||||
: "chat-bubble-user max-w-full whitespace-pre-wrap wrap-anywhere rounded-[24px] px-6 py-4 text-[17px] leading-[1.62] text-chat-user-foreground shadow-md"
|
||||
: "chat-bubble-user max-w-full whitespace-pre-wrap wrap-anywhere rounded-[24px] px-6 py-4 text-ui-reading leading-[1.62] text-chat-user-foreground shadow-md"
|
||||
}
|
||||
dir="auto"
|
||||
>
|
||||
@ -1218,7 +1218,7 @@ const MessageView = memo(function MessageView({
|
||||
className={
|
||||
encounterMode
|
||||
? "max-w-full space-y-2 border-s-2 border-emerald-500/35 ps-3 text-ui-body-sm leading-relaxed text-foreground/90"
|
||||
: "chat-bubble-bot max-w-full space-y-3 rounded-[24px] px-6 py-4 text-[17.5px] leading-[1.85] text-foreground/95"
|
||||
: "chat-bubble-bot max-w-full space-y-3 rounded-[24px] px-6 py-4 text-ui-reading leading-[1.85] text-foreground/95"
|
||||
}
|
||||
dir="auto"
|
||||
>
|
||||
@ -1330,7 +1330,7 @@ const MessageView = memo(function MessageView({
|
||||
{block.state}
|
||||
</span>
|
||||
</div>
|
||||
<div className="my-2.5 text-[14.5px] leading-[1.5] text-foreground/75">
|
||||
<div className="my-2.5 text-ui-body-sm leading-[1.5] text-foreground/75">
|
||||
<ChatMarkdown>{block.text}</ChatMarkdown>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -30,7 +30,7 @@ export function UserMenu({
|
||||
<span className="grid size-control-lg place-items-center rounded-full bg-accent text-ui-body-sm text-foreground/75">
|
||||
{initials}
|
||||
</span>
|
||||
<span className="text-[14.5px] text-foreground/90">{userName}</span>
|
||||
<span className="text-ui-body-sm text-foreground/90">{userName}</span>
|
||||
</PopoverTrigger>
|
||||
{open ? (
|
||||
<PopoverContent
|
||||
|
||||
@ -98,7 +98,7 @@ body,
|
||||
/* Gemini-Grade Accessible Typography (Apple-like with Inter Variable).
|
||||
Heading, code and quote colors come from the shared @rakazo/chat-ui markdown styles. */
|
||||
.rk-chat-markdown {
|
||||
font-size: var(--font-size-reading);
|
||||
font-size: calc(var(--font-size-reading) * var(--font-scale));
|
||||
line-height: var(--line-height-reading);
|
||||
color: var(--foreground);
|
||||
letter-spacing: -0.011em;
|
||||
@ -116,7 +116,7 @@ body,
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.09),
|
||||
0 12px 36px rgba(0, 0, 0, 0.55);
|
||||
padding: 1.25em 1.5em;
|
||||
font-size: 14.5px;
|
||||
font-size: calc(14.5px * var(--font-scale));
|
||||
line-height: 1.65;
|
||||
font-family: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
||||
}
|
||||
@ -130,7 +130,7 @@ body,
|
||||
font-weight: 500;
|
||||
border-radius: 6px;
|
||||
padding: 0.15em 0.4em;
|
||||
font-size: 14.5px;
|
||||
font-size: calc(14.5px * var(--font-scale));
|
||||
font-family: "JetBrains Mono", ui-monospace, SFMono-Regular, monospace;
|
||||
}
|
||||
|
||||
|
||||
@ -45,20 +45,26 @@
|
||||
--radius-xl: calc(var(--radius) + 4px);
|
||||
--radius-2xl: calc(var(--radius) + 8px);
|
||||
|
||||
/* Typography scale */
|
||||
--text-ui-caption: var(--font-size-caption);
|
||||
/* Typography scale. Every text size follows --font-scale, which grows on phones. */
|
||||
--text-xs: calc(0.75rem * var(--font-scale));
|
||||
--text-sm: calc(0.875rem * var(--font-scale));
|
||||
--text-base: calc(1rem * var(--font-scale));
|
||||
--text-lg: calc(1.125rem * var(--font-scale));
|
||||
--text-xl: calc(1.25rem * var(--font-scale));
|
||||
--text-2xl: calc(1.5rem * var(--font-scale));
|
||||
--text-ui-caption: calc(var(--font-size-caption) * var(--font-scale));
|
||||
--text-ui-caption--line-height: var(--line-height-tight);
|
||||
--text-ui-label: var(--font-size-label);
|
||||
--text-ui-label: calc(var(--font-size-label) * var(--font-scale));
|
||||
--text-ui-label--line-height: var(--line-height-tight);
|
||||
--text-ui-body-sm: var(--font-size-body-sm);
|
||||
--text-ui-body-sm: calc(var(--font-size-body-sm) * var(--font-scale));
|
||||
--text-ui-body-sm--line-height: var(--line-height-body);
|
||||
--text-ui-body: var(--font-size-body);
|
||||
--text-ui-body: calc(var(--font-size-body) * var(--font-scale));
|
||||
--text-ui-body--line-height: var(--line-height-body);
|
||||
--text-ui-reading: var(--font-size-reading);
|
||||
--text-ui-reading: calc(var(--font-size-reading) * var(--font-scale));
|
||||
--text-ui-reading--line-height: var(--line-height-reading);
|
||||
--text-ui-title-sm: var(--font-size-title-sm);
|
||||
--text-ui-title-sm: calc(var(--font-size-title-sm) * var(--font-scale));
|
||||
--text-ui-title-sm--line-height: var(--line-height-tight);
|
||||
--text-ui-title: var(--font-size-title);
|
||||
--text-ui-title: calc(var(--font-size-title) * var(--font-scale));
|
||||
--text-ui-title--line-height: var(--line-height-tight);
|
||||
|
||||
/* Control + icon sizes (also drive h-*, size-*, gap-*, p-* utilities) */
|
||||
@ -74,6 +80,18 @@
|
||||
--spacing-rk-sidebar-y: var(--spacing-sidebar-row-y);
|
||||
}
|
||||
|
||||
/* Phones read the 14px base size at 16px (Safari also stops zooming into fields at 16px);
|
||||
all other sizes grow by the same ratio. */
|
||||
:root {
|
||||
--font-scale: 1;
|
||||
}
|
||||
|
||||
@media (max-width: 767.98px) {
|
||||
:root {
|
||||
--font-scale: 1.142857;
|
||||
}
|
||||
}
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
@apply border-border outline-ring/50;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user