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
7 changes: 7 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,13 @@ jobs:
- name: Type check
run: pnpm type-check

# The script existed and already failed — CSS said `sm` was 430px while
# src/config/breakpoints.ts said 428px — and it was wired into no workflow
# and no hook (#373 B2). Landing it green here is what keeps the two in
# step; without this step the mirror silently drifts again.
- name: Validate breakpoint configuration
run: pnpm validate:breakpoints

- name: Run tests
run: pnpm test --run

Expand Down
5 changes: 5 additions & 0 deletions scripts/validate-ci.sh
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,11 @@ run_check "ESLint" "pnpm lint"
# 2. Type check
run_check "TypeScript type check" "pnpm type-check"

# 2b. Breakpoint drift. The script existed and already failed — CSS said the `sm`
# breakpoint was 430px while src/config/breakpoints.ts said 428px — and it was
# wired into no workflow and no hook, so nothing ever ran it (#373 B2).
run_check "Breakpoint config" "pnpm validate:breakpoints"

# 3. Unit tests
run_check "Unit tests" "pnpm test --run"

Expand Down
2 changes: 1 addition & 1 deletion src/app/accessibility/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export default function AccessibilityPage() {
const { fontSize, lineHeight, fontFamily } = settings;

return (
<main className="bg-base-100 min-h-screen">
<main className="bg-base-100 min-h-full">
<div className="mx-auto w-full px-4 py-6 sm:py-8 md:py-12">
<div className="mx-auto max-w-4xl">
<h1 className="mb-6 !text-2xl font-bold sm:mb-8 sm:!text-4xl md:!text-5xl">
Expand Down
2 changes: 1 addition & 1 deletion src/app/contact/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export const metadata: Metadata = {

export default function ContactPage() {
return (
<main className="mx-auto min-h-screen w-full px-4 py-6 sm:py-8 md:py-12">
<main className="mx-auto min-h-full w-full px-4 py-6 sm:py-8 md:py-12">
<div className="mx-auto max-w-4xl">
<div className="mb-8 text-center">
<h1 className="mb-4 !text-2xl font-bold sm:!text-4xl md:!text-5xl">
Expand Down
10 changes: 5 additions & 5 deletions src/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -1190,7 +1190,7 @@ p:not([class*='text-']) {
line-height: 1 !important;
}

@media (min-width: 26.75rem) {
@media (width >= theme(--breakpoint-sm)) {
.btn-mobile-compact {
height: 2rem !important;
min-height: 2rem !important;
Expand All @@ -1211,7 +1211,7 @@ p:not([class*='text-']) {
margin-bottom: 0.25rem !important;
}

@media (min-width: 26.75rem) {
@media (width >= theme(--breakpoint-sm)) {
/* sm breakpoint */
.page-title {
font-size: var(--text-2xl) !important;
Expand Down Expand Up @@ -1252,7 +1252,7 @@ p:not([class*='text-']) {
font-weight: 600 !important;
}

@media (min-width: 26.75rem) {
@media (width >= theme(--breakpoint-sm)) {
.section-title {
font-size: var(--text-xl) !important;
margin-top: 1rem !important;
Expand Down Expand Up @@ -1292,7 +1292,7 @@ p:not([class*='text-']) {
margin-bottom: 0.25rem !important;
}

@media (min-width: 26.75rem) {
@media (width >= theme(--breakpoint-sm)) {
.subsection-title {
font-size: var(--text-lg) !important;
margin-top: 0.75rem !important;
Expand Down Expand Up @@ -1324,7 +1324,7 @@ p:not([class*='text-']) {
margin-bottom: 0.125rem !important;
}

@media (min-width: 26.75rem) {
@media (width >= theme(--breakpoint-sm)) {
.minor-heading {
font-size: var(--text-base) !important;
margin-top: 0.5rem !important;
Expand Down
2 changes: 1 addition & 1 deletion src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ export default function RootLayout({
<CountdownBanner />
<SetupBanner />
<ErrorBoundary level="page">
<div className="bg-base-200 min-h-0 flex-1 overflow-hidden pb-14">
<div className="bg-base-200 min-h-0 flex-1 overflow-x-clip">
{children}
</div>
</ErrorBoundary>
Expand Down
6 changes: 3 additions & 3 deletions src/app/messages/setup/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ export default function MessagingSetupPage() {
// Loading states
if (authLoading || checkingKeys) {
return (
<div className="flex min-h-screen items-center justify-center">
<div className="flex min-h-full items-center justify-center">
<span className="loading loading-spinner loading-lg"></span>
</div>
);
Expand All @@ -165,15 +165,15 @@ export default function MessagingSetupPage() {
// Redirect if already set up
if (hasExistingKeys) {
return (
<div className="flex min-h-screen items-center justify-center">
<div className="flex min-h-full items-center justify-center">
<span className="loading loading-spinner loading-lg"></span>
<span className="ml-2">Redirecting to messages...</span>
</div>
);
}

return (
<div className="flex min-h-screen items-center justify-center p-4">
<div className="flex min-h-full items-center justify-center p-4">
<div className="card bg-base-100 rounded-box w-full max-w-md">
<div className="card-body">
<h1 className="card-title text-2xl">Set Up Encrypted Messaging</h1>
Expand Down
2 changes: 1 addition & 1 deletion src/app/not-found.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import Link from 'next/link';

export default function NotFound() {
return (
<div className="flex min-h-screen flex-col items-center justify-center">
<div className="flex min-h-full flex-col items-center justify-center">
<div className="text-center">
<h1 className="mb-4 text-6xl font-bold">404</h1>
<h2 className="mb-4 text-2xl">Page Not Found</h2>
Expand Down
2 changes: 1 addition & 1 deletion src/app/themes/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ export default function ThemesPage() {
};

return (
<main className="bg-base-100 min-h-screen">
<main className="bg-base-100 min-h-full">
<div className="container mx-auto px-4 py-6 sm:py-8 md:py-12">
<header className="mb-8">
<p className="text-base-content mb-2 font-mono text-xs tracking-wider uppercase">
Expand Down
62 changes: 48 additions & 14 deletions src/components/GlobalNav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,14 @@ function NavGroupMenu({
);
}

/**
* Every item in a DaisyUI `menu` needs an explicit height floor: `li > a`
* renders at 26px, and the 44px gate cannot see items inside a closed
* dropdown, so they stay under it indefinitely (#378). Named rather than
* repeated so a new entry cannot arrive without one.
*/
const MENU_ITEM = 'min-h-11 flex items-center';

/** A single nav destination. */
type NavLeaf = { href: string; label: string; reload?: boolean };
/** A labelled group of destinations, rendered as a dropdown. */
Expand Down Expand Up @@ -519,10 +527,14 @@ export function GlobalNav() {
<span>{user.email}</span>
</li>
<li>
<Link href="/profile">Profile</Link>
<Link href="/profile" className={MENU_ITEM}>
Profile
</Link>
</li>
<li>
<Link href="/account">Account Settings</Link>
<Link href="/account" className={MENU_ITEM}>
Account Settings
</Link>
</li>
<li>
<Link
Expand All @@ -538,11 +550,18 @@ export function GlobalNav() {
</Link>
</li>
<li>
<Link href="/messages?tab=connections">Connections</Link>
<Link
href="/messages?tab=connections"
className={MENU_ITEM}
>
Connections
</Link>
</li>
{isAdmin && (
<li>
<Link href="/admin">Admin Dashboard</Link>
<Link href="/admin" className={MENU_ITEM}>
Admin Dashboard
</Link>
</li>
)}
<li>
Expand Down Expand Up @@ -624,14 +643,14 @@ export function GlobalNav() {
{item.reload ? (
<a
href={getInternalUrl(item.href)}
className={isActive(item) ? 'active' : ''}
className={`${MENU_ITEM} ${isActive(item) ? 'active' : ''}`}
>
{item.label}
</a>
) : (
<Link
href={item.href}
className={isActive(item) ? 'active' : ''}
className={`${MENU_ITEM} ${isActive(item) ? 'active' : ''}`}
>
{item.label}
</Link>
Expand All @@ -644,15 +663,19 @@ export function GlobalNav() {
<span>Account</span>
</li>
<li>
<Link href="/profile">Profile</Link>
<Link href="/profile" className={MENU_ITEM}>
Profile
</Link>
</li>
<li>
<Link href="/account">Settings</Link>
<Link href="/account" className={MENU_ITEM}>
Settings
</Link>
</li>
<li>
<Link
href="/messages"
className="flex items-center justify-between"
className={`${MENU_ITEM} justify-between`}
>
<span>Messages</span>
{unreadCount > 0 && (
Expand All @@ -663,11 +686,18 @@ export function GlobalNav() {
</Link>
</li>
<li>
<Link href="/messages?tab=connections">Connections</Link>
<Link
href="/messages?tab=connections"
className={MENU_ITEM}
>
Connections
</Link>
</li>
{isAdmin && (
<li>
<Link href="/admin">Admin Dashboard</Link>
<Link href="/admin" className={MENU_ITEM}>
Admin Dashboard
</Link>
</li>
)}
<li>
Expand Down Expand Up @@ -695,10 +725,14 @@ export function GlobalNav() {
<span>Account</span>
</li>
<li>
<Link href="/sign-in">Sign In</Link>
<Link href="/sign-in" className={MENU_ITEM}>
Sign In
</Link>
</li>
<li>
<Link href="/sign-up">Sign Up</Link>
<Link href="/sign-up" className={MENU_ITEM}>
Sign Up
</Link>
</li>
</>
)}
Expand Down Expand Up @@ -774,7 +808,7 @@ export function GlobalNav() {
<h3 className="mb-2 text-sm font-semibold tracking-wide uppercase">
Theme
</h3>
<ul className="max-h-64 overflow-y-auto">
<ul>
{THEMES.map((t) => (
<li key={t}>
<button
Expand Down
2 changes: 1 addition & 1 deletion src/components/auth/AuthGuard/AuthGuard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export default function AuthGuard({

if (isLoading) {
return (
<div className="flex min-h-screen items-center justify-center">
<div className="flex min-h-full items-center justify-center">
<span className="loading loading-spinner loading-lg"></span>
</div>
);
Expand Down
4 changes: 2 additions & 2 deletions src/components/auth/ProtectedRoute/ProtectedRoute.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ export default function ProtectedRoute({

if (isLoading) {
return (
<div className="flex min-h-screen items-center justify-center">
<div className="flex min-h-full items-center justify-center">
<span className="loading loading-spinner loading-lg"></span>
</div>
);
Expand All @@ -76,7 +76,7 @@ export default function ProtectedRoute({
// sign-out the component unmounts via window.location.href anyway.
if (!isAuthenticated && !wasAuthenticated.current) {
return (
<div className="flex min-h-screen items-center justify-center px-4">
<div className="flex min-h-full items-center justify-center px-4">
<div className="card bg-base-100 w-full max-w-md shadow-xl">
<div className="card-body items-center text-center">
<svg
Expand Down
20 changes: 17 additions & 3 deletions src/components/molecular/ColorblindToggle/ColorblindToggle.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,14 @@ export const ColorVisionPanel: React.FC = () => {

return (
<div>
<h3 className="text-lg font-bold">Color Vision Assistance</h3>
{/* A SECTION LABEL, not a page heading (#469). As a standalone popover
this was `text-lg font-bold`; inside `Display ▾` that put it a whole
tier above the "TEXT SETTINGS" and "THEME" labels beside it, and it
wrapped to two lines at 390px — measured 25.7px/80px against their
18.5px/26px. */}
<h3 className="mb-2 text-sm font-semibold tracking-wide uppercase">
Color Vision Assistance
</h3>

<div>
<label className="label" htmlFor={selectId}>
Expand Down Expand Up @@ -105,8 +112,15 @@ export const ColorVisionPanel: React.FC = () => {
</div>
)}

<div className="alert alert-info mt-4" role="status" aria-live="polite">
<span className="text-sm">
{/* A hint line, not an `alert` (#469): the tinted block measured 105px
for one sentence inside a menu. `role="status"` and `aria-live` stay —
the announcement when the mode changes is the point of this text. */}
<div
className="text-base-content mt-3 text-sm"
role="status"
aria-live="polite"
>
<span>
{mode === ColorblindType.NONE
? 'Select your color vision type for visual assistance'
: `Correcting for ${COLORBLIND_LABELS[mode]}`}
Expand Down
24 changes: 16 additions & 8 deletions src/config/breakpoints.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,27 +13,35 @@ import type { BreakpointConfig } from '@/types/mobile-first';
*
* Strategy: Mobile-first with progressive enhancement
* - xs (320px): Minimum supported mobile width
* - sm (428px): Standard mobile devices
* - sm (430px): Standard mobile devices
* - md (768px): Tablet portrait
* - lg (1024px): Desktop and tablet landscape
* - xl (1280px): Large desktop
*
* Note: Tailwind CSS 4 uses `@theme` in globals.css for configuration
* Note: Tailwind CSS 4 uses `@theme` in globals.css for configuration, which is
* the SOURCE OF TRUTH — this file mirrors it, and `pnpm validate:breakpoints`
* fails when the two disagree.
*
* `sm` is 430px, not 428px, deliberately: the iPhone 14 Pro Max is exactly 428
* CSS px, and it is a phone, so `sm:` utilities must NOT apply there. That is
* also why `xs` ends at 429 rather than 427 — 428 and 429 belong to the phone
* bucket. This file said 428 while globals.css said 430, so at 428-429px the
* hand-written media queries fired and no Tailwind `sm:` utility did (#373 B2).
*/
export const BREAKPOINTS: BreakpointConfig[] = [
{
name: 'xs',
minWidth: 320,
maxWidth: 427,
maxWidth: 429,
category: 'mobile',
mediaQuery: '(min-width: 320px) and (max-width: 427px)',
mediaQuery: '(min-width: 320px) and (max-width: 429px)',
},
{
name: 'sm',
minWidth: 428,
minWidth: 430,
maxWidth: 767,
category: 'mobile',
mediaQuery: '(min-width: 428px) and (max-width: 767px)',
mediaQuery: '(min-width: 430px) and (max-width: 767px)',
},
{
name: 'md',
Expand Down Expand Up @@ -105,8 +113,8 @@ export function getDeviceCategory(width: number): string {
export const mediaQueries = {
/** Mobile and up (>= 320px) */
mobile: '(min-width: 320px)',
/** Standard mobile and up (>= 428px) */
sm: '(min-width: 428px)',
/** Standard mobile and up (>= 430px) */
sm: '(min-width: 430px)',
/** Tablet and up (>= 768px) */
md: '(min-width: 768px)',
/** Desktop and up (>= 1024px) */
Expand Down
Loading
Loading