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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## 0.1.21

- Use the official HQBase logo for the signed-out screen, favicon, and installed PWA icons.
- Refine sidebar and dropdown controls with slimmer icons and clearer account-section separation.
- Keep the mobile drawer within iPhone safe areas and prevent form fields from zooming on focus.

## 0.1.20

- Hide the available-update banner and duplicate install action after Cloudflare accepts the update
Expand Down
12 changes: 8 additions & 4 deletions app/components/layout/account-menu.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ChevronsUpDown, LogOut } from "lucide-react";
import { ChevronDown, LogOut } from "lucide-react";
import type * as React from "react";

import { Avatar, AvatarFallback } from "@/components/ui/avatar";
Expand Down Expand Up @@ -53,7 +53,11 @@ export function AccountMenu({
<span className="block truncate text-[13px] text-foreground">{user.name}</span>
<span className="block truncate text-[11px] text-muted-foreground">{user.role}</span>
</span>
<ChevronsUpDown aria-hidden="true" />
<ChevronDown
aria-hidden="true"
className="size-3.5 text-muted-foreground"
strokeWidth={1.5}
/>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="start" className="w-52" side="top">
Expand All @@ -64,8 +68,8 @@ export function AccountMenu({
</div>
</DropdownMenuLabel>
<DropdownMenuGroup>
<DropdownMenuItem onSelect={() => void handleSignOut()}>
<LogOut />
<DropdownMenuItem className="gap-2" onSelect={() => void handleSignOut()}>
<LogOut className="size-4" strokeWidth={1.5} />
Sign out
</DropdownMenuItem>
</DropdownMenuGroup>
Expand Down
2 changes: 1 addition & 1 deletion app/components/layout/app-shell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ type AppShellProps = {

export function AppShell(props: AppShellProps): React.ReactElement {
return (
<div className="flex h-screen overflow-hidden bg-background text-foreground">
<div className="flex h-screen h-[100dvh] overflow-hidden bg-background text-foreground">
<Sidebar
activeFolder={props.activeFolder}
user={props.user}
Expand Down
1 change: 1 addition & 0 deletions app/components/layout/mobile-navigation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ export function MobileNavigation({
<SheetContent
aria-describedby={undefined}
className="w-[min(86vw,18rem)] p-0"
overlayClassName="before:pointer-events-none before:fixed before:inset-x-0 before:top-0 before:h-[env(safe-area-inset-top)] before:bg-background after:pointer-events-none after:fixed after:inset-x-0 after:bottom-0 after:h-[env(safe-area-inset-bottom)] after:bg-background"
ref={drawerRef}
side="left"
onOpenAutoFocus={(event) => {
Expand Down
58 changes: 32 additions & 26 deletions app/components/layout/sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,10 @@ export function Sidebar({
return (
<aside
className={cn(
"w-56 shrink-0 flex-col bg-background p-3",
isDrawer ? "flex h-full w-full" : "hidden border-r md:flex"
"w-56 shrink-0 flex-col bg-background px-3 py-3",
isDrawer
? "flex h-full w-full pb-[max(0.75rem,env(safe-area-inset-bottom))] pt-[max(0.75rem,env(safe-area-inset-top))]"
: "hidden border-r md:flex"
)}
>
<div className="mb-7 flex h-9 items-center gap-2.5 px-2">
Expand Down Expand Up @@ -79,31 +81,35 @@ export function Sidebar({
</Button>
);
})}
<div className="mt-auto flex flex-col gap-0.5 border-t pt-2">
{isDrawer && drawerAction ? drawerAction : null}
<Button
asChild
className={cn(
"h-8 w-full justify-start gap-2.5 px-2.5 text-[13px] font-normal text-muted-foreground",
isDrawer && "h-11 text-sm",
activeFolder === "settings" && "bg-muted text-foreground"
)}
variant="ghost"
>
<a
aria-current={activeFolder === "settings" ? "page" : undefined}
href={appRoutePath({ kind: "settings", tab: "mailboxes" })}
onClick={(event) => {
if (isModifiedNavigation(event)) return;
event.preventDefault();
onFolderChange("settings");
}}
<div className="mt-auto">
<div className="flex flex-col gap-0.5 border-t pt-2">
{isDrawer && drawerAction ? drawerAction : null}
<Button
asChild
className={cn(
"h-8 w-full justify-start gap-2.5 px-2.5 text-[13px] font-normal text-muted-foreground",
isDrawer && "h-11 text-sm",
activeFolder === "settings" && "bg-muted text-foreground"
)}
variant="ghost"
>
<Settings />
Settings
</a>
</Button>
<AccountMenu drawer={isDrawer} user={user} onSignedOut={onSignedOut} />
<a
aria-current={activeFolder === "settings" ? "page" : undefined}
href={appRoutePath({ kind: "settings", tab: "mailboxes" })}
onClick={(event) => {
if (isModifiedNavigation(event)) return;
event.preventDefault();
onFolderChange("settings");
}}
>
<Settings />
Settings
</a>
</Button>
</div>
<div className="mt-2 border-t pt-2">
<AccountMenu drawer={isDrawer} user={user} onSignedOut={onSignedOut} />
</div>
</div>
</nav>
</aside>
Expand Down
4 changes: 2 additions & 2 deletions app/components/ui/select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export const SelectTrigger = React.forwardRef<
>
{children}
<SelectPrimitive.Icon asChild>
<ChevronDown />
<ChevronDown className="size-3.5 shrink-0 text-muted-foreground" strokeWidth={1.5} />
</SelectPrimitive.Icon>
</SelectPrimitive.Trigger>
));
Expand Down Expand Up @@ -56,7 +56,7 @@ export const SelectItem = React.forwardRef<
>
<span className="absolute left-2 flex size-3.5 items-center justify-center">
<SelectPrimitive.ItemIndicator>
<Check />
<Check className="size-3.5" strokeWidth={1.5} />
</SelectPrimitive.ItemIndicator>
</span>
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
Expand Down
10 changes: 8 additions & 2 deletions app/components/ui/sheet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,21 @@ export const SheetTitle = DialogPrimitive.Title;
export const SheetDescription = DialogPrimitive.Description;

type SheetContentProps = React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content> & {
overlayClassName?: string;
side?: "left" | "right";
};

export const SheetContent = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Content>,
SheetContentProps
>(({ className, children, side = "right", ...props }, ref) => (
>(({ className, children, overlayClassName, side = "right", ...props }, ref) => (
<SheetPortal>
<DialogPrimitive.Overlay className="fixed inset-0 z-50 bg-foreground/25 data-[state=closed]:animate-overlay-out data-[state=open]:animate-overlay-in motion-reduce:animate-none" />
<DialogPrimitive.Overlay
className={cn(
"fixed inset-0 z-50 bg-foreground/25 data-[state=closed]:animate-overlay-out data-[state=open]:animate-overlay-in motion-reduce:animate-none",
overlayClassName
)}
/>
<DialogPrimitive.Content
className={cn(
"fixed inset-y-0 z-50 w-[min(92vw,480px)] bg-background p-5 shadow-lg motion-reduce:animate-none",
Expand Down
4 changes: 1 addition & 3 deletions app/features/auth/login-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,7 @@ export function LoginPage({ onLogin }: LoginPageProps): React.ReactElement {
<main className="flex min-h-screen items-center justify-center bg-background px-4 py-12">
<div className="w-full max-w-sm">
<div className="mb-10 flex items-center justify-center gap-2">
<span className="flex size-6 items-center justify-center rounded-md border bg-card font-mono text-[10px] font-semibold">
HQ
</span>
<img alt="" className="h-7 w-auto" src="/logo.svg" />
<span className="text-sm font-medium">HQBase</span>
</div>
<Card className="bg-card/70 shadow-none">
Expand Down
18 changes: 18 additions & 0 deletions app/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -75,15 +75,33 @@
--radius: 0.5rem;
}

html {
background-color: hsl(var(--background));
}

* {
@apply border-border;
}

body {
@apply bg-background text-foreground antialiased;
min-width: 320px;
min-height: 100dvh;
overscroll-behavior: none;
font-feature-settings:
"rlig" 1,
"calt" 1;
}

#root {
min-height: 100dvh;
}
}

@media (max-width: 767px) {
input:not([type="checkbox"]):not([type="radio"]),
textarea[class],
[contenteditable="true"][class] {
font-size: 16px;
}
}
11 changes: 7 additions & 4 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,20 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, viewport-fit=cover, interactive-widget=resizes-content"
/>
<meta name="description" content="HQBase shared webmail for your Cloudflare account." />
<meta name="theme-color" content="#09090b" />
<meta name="theme-color" content="#080808" />
<meta name="color-scheme" content="dark" />
<meta name="application-name" content="HQBase" />
<meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-title" content="HQBase" />
<meta name="apple-mobile-web-app-status-bar-style" content="black" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<link rel="manifest" href="/manifest.webmanifest" />
<link rel="icon" href="/icons/icon-192.png" type="image/png" />
<link rel="icon" href="/logo.svg" type="image/svg+xml" />
<link rel="apple-touch-icon" href="/icons/apple-touch-icon.png" />
<link rel="preload" href="/fonts/Geist-Regular.woff2" as="font" type="font/woff2" crossorigin />
<title>HQBase</title>
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "hqbase",
"version": "0.1.20",
"version": "0.1.21",
"private": true,
"type": "module",
"packageManager": "pnpm@11.7.0",
Expand Down Expand Up @@ -29,6 +29,7 @@
"test:e2e:staging": "playwright test --config playwright.config.ts app-shell-smoke.spec.ts lifecycle.spec.ts",
"test:e2e:staging:smoke": "playwright test --config playwright.config.ts app-shell-smoke.spec.ts",
"test:e2e:staging:lifecycle": "playwright test --config playwright.config.ts lifecycle.spec.ts",
"pwa:icons": "node scripts/generate-pwa-icons.mjs",
"check": "pnpm code:check && pnpm typecheck && pnpm test && pnpm test:coverage && pnpm test:architecture && pnpm build",
"deploy:dry-run": "pnpm build && wrangler deploy --dry-run",
"hqbase": "node scripts/hqbase/cli.mjs",
Expand Down
Binary file modified public/icons/apple-touch-icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified public/icons/icon-192.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified public/icons/icon-512.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified public/icons/icon-maskable-512.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions public/manifest.webmanifest
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
"start_url": "/",
"scope": "/",
"display": "standalone",
"background_color": "#09090b",
"theme_color": "#09090b",
"background_color": "#080808",
"theme_color": "#080808",
"categories": ["business", "productivity", "utilities"],
"icons": [
{
Expand Down
13 changes: 8 additions & 5 deletions public/offline.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,18 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="theme-color" content="#09090b" />
<link rel="icon" href="/icons/icon-192.png" type="image/png" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, viewport-fit=cover"
/>
<meta name="theme-color" content="#080808" />
<link rel="icon" href="/logo.svg" type="image/svg+xml" />
<title>HQBase is offline</title>
<style>
:root {
color-scheme: dark;
font-family: Geist, ui-sans-serif, system-ui, sans-serif;
background: #09090b;
background: #080808;
color: #fafafa;
}
body {
Expand Down Expand Up @@ -47,7 +50,7 @@
border-radius: 6px;
padding: 0 18px;
background: #fafafa;
color: #09090b;
color: #080808;
font: inherit;
font-weight: 600;
cursor: pointer;
Expand Down
53 changes: 53 additions & 0 deletions scripts/generate-pwa-icons.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { readFile } from "node:fs/promises";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { chromium } from "@playwright/test";

const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
const logo = await readFile(path.join(root, "public/logo.svg"), "utf8");
const browser = await chromium.launch({ headless: true });

try {
for (const icon of [
{ file: "icon-512.png", logoWidth: 380, size: 512 },
{ file: "icon-192.png", logoWidth: 143, size: 192 },
{ file: "apple-touch-icon.png", logoWidth: 134, size: 180 },
{ file: "icon-maskable-512.png", logoWidth: 330, size: 512 }
]) {
const page = await browser.newPage({
deviceScaleFactor: 1,
viewport: { height: icon.size, width: icon.size }
});

await page.setContent(
`<!doctype html>
<style>
html, body {
width: 100%;
height: 100%;
margin: 0;
overflow: hidden;
background: #080808;
}
body {
display: grid;
place-items: center;
}
svg {
display: block;
width: ${icon.logoWidth}px;
height: auto;
}
</style>
${logo}`,
{ waitUntil: "load" }
);
await page.screenshot({
path: path.join(root, "public/icons", icon.file),
type: "png"
});
await page.close();
}
} finally {
await browser.close();
}
6 changes: 0 additions & 6 deletions scripts/pwa/app-icon.svg

This file was deleted.

11 changes: 11 additions & 0 deletions test/unit/app/layout/mail-shell.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { describe, expect, it } from "vitest";
import { MobileNavigation } from "@/components/layout/mobile-navigation";
import { Sidebar } from "@/components/layout/sidebar";
import { TopBar } from "@/components/layout/top-bar";
import { LoginPage } from "@/features/auth/login-page";
import { ComposeWindow } from "@/features/compose/compose-window";
import { InboxPage } from "@/features/inbox/inbox-page";
import { McpConnectionDetails } from "@/features/mcp/connection-dialog";
Expand Down Expand Up @@ -61,10 +62,19 @@ describe("mail shell", () => {
expect(html).toContain("Open profile menu");
expect(html).toContain("OB");
expect(html.indexOf("Settings")).toBeLessThan(html.indexOf("Open profile menu"));
expect(html.match(/border-t pt-2/g)).toHaveLength(2);
expect(html).toContain('stroke-width="1.5"');
expect(html).not.toContain(">HQ<");
expect(html).not.toContain(">Mail</div>");
});

it("uses the canonical logo on the signed-out surface", () => {
const html = renderToStaticMarkup(<LoginPage onLogin={() => undefined} />);

expect(html).toContain('src="/logo.svg"');
expect(html).not.toContain(">HQ</span>");
});

it("uses a labelled hamburger trigger instead of a mobile folder select", () => {
const html = renderToStaticMarkup(
<MobileNavigation
Expand Down Expand Up @@ -100,6 +110,7 @@ describe("mail shell", () => {
expect(html).toContain('aria-current="page"');
expect(html).toContain("Connect MCP");
expect(html.indexOf("Settings")).toBeLessThan(html.indexOf("Open profile menu"));
expect(html.match(/border-t pt-2/g)).toHaveLength(2);
});

it("combines the compact folder label and conversation count in one list header", () => {
Expand Down
Loading