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
3 changes: 2 additions & 1 deletion config/quality/e2e-timings.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"_meta": "Relative weights for scripts/quality/balance-e2e-shards.mjs (LPT shard packing). Unitless \u2014 only ratios matter. Seeded from spec LOC (proxy) on 2026-07-13; replace values with real per-file durations (seconds) from a full CI run's reports whenever convenient. New specs without an entry get the median weight.",
"_meta": "Relative weights for scripts/quality/balance-e2e-shards.mjs (LPT shard packing). Unitless — only ratios matter. Seeded from spec LOC (proxy) on 2026-07-13; replace values with real per-file durations (seconds) from a full CI run's reports whenever convenient. New specs without an entry get the median weight.",
"a11y-resilience.spec.ts": 59,
"a11y.spec.ts": 281,
"agent-bridge-traffic-cross.spec.ts": 155,
Expand All @@ -17,6 +17,7 @@
"group-b-quota-plans-config.spec.ts": 154,
"group-b-quota-share-pools.spec.ts": 98,
"group-b-redirect-logs-activity.spec.ts": 56,
"keyboard-a11y.spec.ts": 109,
"memory-engine.spec.ts": 607,
"memory-qdrant-routes.spec.ts": 360,
"memory-settings.spec.ts": 193,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ export default function CheaperInferenceSponsorBanner() {
open_in_new
</span>
</a>
<span className="text-[9px] text-text-muted/70">{t("partnerLinkNote")}</span>
<span className="text-[9px] text-text-muted">{t("partnerLinkNote")}</span>
</div>
<button
type="button"
Expand Down
2 changes: 1 addition & 1 deletion src/app/(dashboard)/dashboard/HomePageClient.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ function mergeUpdateStep(steps: UpdateStep[], nextStep: UpdateStep) {

// Quick-start link classes, extracted so each <Link> still fits on one line with
// prefetch={false} (#8281) — this file is size-frozen.
const INLINE_LINK = "text-primary hover:underline";
const INLINE_LINK = "text-primary underline underline-offset-2";
const DOCS_LINK =
"hidden sm:inline-flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-xs font-medium border border-border text-text-muted hover:text-text-main hover:bg-bg-subtle transition-colors";

Expand Down
4 changes: 2 additions & 2 deletions src/app/(dashboard)/dashboard/KimiSponsorBanner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -97,14 +97,14 @@ export default function KimiSponsorBanner() {
target="_blank"
rel="noopener noreferrer"
title={t("partnerLinkNote")}
className="inline-flex items-center gap-1.5 whitespace-nowrap rounded-lg bg-[#1783FF] px-3 py-1.5 text-xs font-semibold text-white transition-colors hover:brightness-110"
className="inline-flex items-center gap-1.5 whitespace-nowrap rounded-lg bg-[#0b6ad4] px-3 py-1.5 text-xs font-semibold text-white transition-colors hover:brightness-110"
>
{t("cta")}
<span className="material-symbols-outlined text-[14px]" aria-hidden="true">
open_in_new
</span>
</a>
<span className="text-[9px] text-text-muted/70">{t("partnerLinkNote")}</span>
<span className="text-[9px] text-text-muted">{t("partnerLinkNote")}</span>
</div>
<button
type="button"
Expand Down
2 changes: 1 addition & 1 deletion src/app/(dashboard)/dashboard/VscodeCopilotBanner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ export default function VscodeCopilotBanner() {
open_in_new
</span>
</a>
<span className="text-[9px] text-text-muted/70">{t("secondaryNote")}</span>
<span className="text-[9px] text-text-muted">{t("secondaryNote")}</span>
</div>
<button
type="button"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,7 @@ export default function ProviderSummaryCard({
<button
key={cat.key ?? "all"}
onClick={() => onCategoryChange(cat.key, cat.key === "free")}
aria-pressed={isActive}
className={`flex items-center gap-1.5 px-3 py-1.5 rounded-full border text-xs font-medium transition-colors ${
isActive
? "bg-primary text-white border-primary"
Expand All @@ -269,9 +270,10 @@ export default function ProviderSummaryCard({
>
{cat.color && <CategoryDot color={cat.color} label={cat.label} />}
<span>{cat.label}</span>
<span className={`text-[11px] ${isActive ? "text-white/80" : "text-text-muted"}`}>
{/* Full-opacity counts: the dimmed variants measured 2.1–2.6:1 contrast. */}
<span className={`text-[11px] ${isActive ? "text-white" : "text-text-muted"}`}>
{cat.stat.configured}
<span className="opacity-70">/{cat.stat.total}</span>
<span>/{cat.stat.total}</span>
</span>
</button>
);
Expand All @@ -295,7 +297,9 @@ export default function ProviderSummaryCard({
: "bg-bg-subtle border-border text-text-muted hover:text-text-primary hover:border-primary/30"
}`}
>
<span className="material-symbols-outlined text-[14px]">{chip.icon}</span>
<span className="material-symbols-outlined text-[14px]" aria-hidden="true">
{chip.icon}
</span>
<span>{providerText(t, chip.labelKey, chip.fallback)}</span>
</button>
);
Expand Down
14 changes: 10 additions & 4 deletions src/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,10 @@
--desktop-safe-bottom: 0px;
color-scheme: light;

/* Primary - Coral Red (OpenClaw) */
--color-primary: #e54d5e;
--color-primary-hover: #c93d4e;
/* Primary - Coral Red (OpenClaw). Light theme uses a deeper shade so primary text and
white-on-primary reach WCAG AA 4.5:1 (#e54d5e was 3.1–3.8:1); .dark keeps #e54d5e. */
--color-primary: #b83242;
--color-primary-hover: #9f2a38;

/* Accent - Indigo/Violet (buttons, links, gradients) */
--color-accent: #6366f1;
Expand Down Expand Up @@ -71,7 +72,8 @@
--color-border: rgba(0, 0, 0, 0.08);
--color-text-main: #1a1a2e;
--color-text-primary: #1a1a2e;
--color-text-muted: #71717a;
/* #71717a measured 4.25:1 on bg-subtle and 3.81:1 on control grays (axe color-contrast). */
--color-text-muted: #65656e;

/* Fumadocs light theme */
--color-fd-background: #f9f9fb;
Expand Down Expand Up @@ -125,6 +127,10 @@
.dark {
/* Dark theme (ClawHub deep) */
color-scheme: dark;
/* The light-theme primary is darkened for contrast; on dark surfaces the original
coral already passes (5.1:1 on --color-bg) and a darker shade would not. */
--color-primary: #e54d5e;
--color-primary-hover: #c93d4e;
--color-bg: #0b0e14;
--color-bg-alt: #111520;
--color-bg-primary: #0b0e14;
Expand Down
68 changes: 54 additions & 14 deletions src/app/login/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import { useState, useEffect } from "react";
import { Button, Input } from "@/shared/components";
import { useRouter } from "next/navigation";

const PASSWORD_INPUT_ID = "login-password";

export default function LoginPage() {
const t = useTranslations("auth");
const [password, setPassword] = useState("");
Expand Down Expand Up @@ -103,7 +105,12 @@ export default function LoginPage() {
<div className="bg-red-950/60 border-2 border-red-500/40 rounded-2xl p-6 shadow-lg shadow-red-900/20 backdrop-blur-sm">
<div className="flex items-start gap-4">
<div className="w-12 h-12 rounded-xl bg-red-500/20 flex items-center justify-center flex-shrink-0 mt-0.5">
<span className="material-symbols-outlined text-red-400 text-[28px]">error</span>
<span
className="material-symbols-outlined text-red-400 text-[28px]"
aria-hidden="true"
>
error
</span>
</div>
<div className="flex-1 min-w-0">
<h3 className="text-base font-bold text-red-300 mb-1">
Expand All @@ -114,13 +121,17 @@ export default function LoginPage() {
</p>
<div className="bg-black/40 rounded-lg px-4 py-3 font-mono text-sm border border-red-500/20">
<div className="flex items-center gap-2 text-red-300/60 mb-1">
<span className="material-symbols-outlined text-[14px]">terminal</span>
<span className="material-symbols-outlined text-[14px]" aria-hidden="true">
terminal
</span>
<span className="text-xs">{t("nodeIncompatibleFixLabel")}</span>
</div>
<code className="text-amber-300">nvm install 22 && nvm use 22</code>
</div>
<p className="text-xs text-red-300/50 mt-3 flex items-center gap-1.5">
<span className="material-symbols-outlined text-[14px]">info</span>
<span className="material-symbols-outlined text-[14px]" aria-hidden="true">
info
</span>
{t("nodeIncompatibleHint")}
</p>
</div>
Expand All @@ -137,8 +148,8 @@ export default function LoginPage() {
return (
<div className="min-h-screen flex flex-col items-center justify-center p-6">
{nodeWarningBanner}
<div className="flex flex-col items-center gap-3">
<div className="relative">
<div className="flex flex-col items-center gap-3" role="status" aria-live="polite">
<div className="relative" aria-hidden="true">
<div className="w-10 h-10 border-2 border-primary/20 rounded-full"></div>
<div className="absolute inset-0 w-10 h-10 border-2 border-primary border-t-transparent rounded-full animate-spin"></div>
</div>
Expand All @@ -157,7 +168,10 @@ export default function LoginPage() {
>
<div className="text-center mb-10">
<div className="inline-flex items-center justify-center w-20 h-20 rounded-3xl bg-gradient-to-br from-primary/10 to-primary/5 border border-primary/10 mb-6">
<span className="material-symbols-outlined text-primary text-[40px]">
<span
className="material-symbols-outlined text-primary text-[40px]"
aria-hidden="true"
>
rocket_launch
</span>
</div>
Expand Down Expand Up @@ -195,7 +209,10 @@ export default function LoginPage() {
>
<div className="text-center mb-10">
<div className="inline-flex items-center justify-center w-20 h-20 rounded-3xl bg-gradient-to-br from-amber-500/10 to-amber-500/5 border border-amber-500/10 mb-6">
<span className="material-symbols-outlined text-amber-500 text-[40px]">
<span
className="material-symbols-outlined text-amber-500 text-[40px]"
aria-hidden="true"
>
shield_person
</span>
</div>
Expand Down Expand Up @@ -239,7 +256,12 @@ export default function LoginPage() {
<div className="mb-10">
<div className="flex items-center gap-3 mb-8">
<div className="w-10 h-10 rounded-xl bg-gradient-to-br from-primary to-primary-hover flex items-center justify-center">
<span className="material-symbols-outlined text-white text-[20px]">hub</span>
<span
className="material-symbols-outlined text-white text-[20px]"
aria-hidden="true"
>
hub
</span>
</div>
<span className="text-xl font-semibold text-text-main tracking-tight">
OmniRoute
Expand All @@ -261,16 +283,24 @@ export default function LoginPage() {
className="w-full h-11 text-sm font-medium flex items-center justify-center gap-2"
onClick={() => (window.location.href = "/api/auth/oidc/login")}
>
<span className="material-symbols-outlined text-lg">login</span>
<span className="material-symbols-outlined text-lg" aria-hidden="true">
login
</span>
{t("continueWithOidc")}
</Button>
</div>
) : (
<>
<form onSubmit={handleLogin} className="space-y-5 w-full">
<div className="space-y-2">
<label className="text-sm font-medium text-text-main">{t("password")}</label>
<label
htmlFor={PASSWORD_INPUT_ID}
className="text-sm font-medium text-text-main"
>
{t("password")}
</label>
<Input
id={PASSWORD_INPUT_ID}
type="password"
placeholder={t("enterPassword")}
value={password}
Expand All @@ -280,8 +310,13 @@ export default function LoginPage() {
className="h-11"
/>
{error && (
<p className="text-sm text-red-500 flex items-center gap-1.5 pt-1">
<span className="material-symbols-outlined text-base">error</span>
<p
role="alert"
className="text-sm text-red-500 flex items-center gap-1.5 pt-1"
>
<span className="material-symbols-outlined text-base" aria-hidden="true">
error
</span>
{error}
</p>
)}
Expand Down Expand Up @@ -316,7 +351,9 @@ export default function LoginPage() {
className="w-full h-11 text-sm font-medium flex items-center justify-center gap-2"
onClick={() => (window.location.href = "/api/auth/oidc/login")}
>
<span className="material-symbols-outlined text-lg">login</span>
<span className="material-symbols-outlined text-lg" aria-hidden="true">
login
</span>
{t("continueWithOidc")}
</Button>
</div>
Expand Down Expand Up @@ -370,7 +407,10 @@ export default function LoginPage() {
className="flex items-start gap-4 p-4 rounded-xl bg-surface/50 border border-border"
>
<div className="w-10 h-10 rounded-lg bg-primary/10 flex items-center justify-center flex-shrink-0">
<span className="material-symbols-outlined text-primary text-[20px]">
<span
className="material-symbols-outlined text-primary text-[20px]"
aria-hidden="true"
>
{item.icon}
</span>
</div>
Expand Down
15 changes: 7 additions & 8 deletions src/shared/components/Breadcrumbs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,9 @@ export default function Breadcrumbs() {
alignItems: "center",
gap: "6px",
fontSize: "13px",
color: "var(--text-secondary, #888)",
// --text-primary/--text-secondary were never defined, so the dark-theme fallbacks
// (#e0e0e0 / #888) rendered in light mode at 1.25:1 and 3.4:1 contrast.
color: "var(--color-text-muted)",
padding: "8px 0",
marginBottom: "8px",
}}
Expand All @@ -152,25 +154,22 @@ export default function Breadcrumbs() {
</span>
)}
{crumb.isLast ? (
<span
aria-current="page"
style={{ color: "var(--text-primary, #e0e0e0)", fontWeight: 500 }}
>
<span aria-current="page" style={{ color: "var(--color-text-main)", fontWeight: 500 }}>
{crumb.label}
</span>
) : (
<Link
href={crumb.href}
style={{
color: "var(--text-secondary, #888)",
color: "var(--color-text-muted)",
textDecoration: "none",
transition: "color 0.15s",
}}
onMouseEnter={(e) =>
((e.currentTarget as HTMLElement).style.color = "var(--accent, #818cf8)")
((e.currentTarget as HTMLElement).style.color = "var(--color-accent)")
}
onMouseLeave={(e) =>
((e.currentTarget as HTMLElement).style.color = "var(--text-secondary, #888)")
((e.currentTarget as HTMLElement).style.color = "var(--color-text-muted)")
}
>
{crumb.label}
Expand Down
10 changes: 8 additions & 2 deletions src/shared/components/Checkbox.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"use client";

import { useId } from "react";
import { cn } from "@/shared/utils/cn";

interface CheckboxProps extends React.InputHTMLAttributes<HTMLInputElement> {
Expand All @@ -10,12 +11,17 @@ interface CheckboxProps extends React.InputHTMLAttributes<HTMLInputElement> {
* Checkbox — token-driven native checkbox (brand accent + keyboard focus ring).
* Replaces the ad-hoc `<input type="checkbox" style={{ accentColor: "#6366f1" }}>`
* pattern scattered across the dashboard. Optional `label` wraps it in a clickable row.
* Without a `label`, pass `aria-label` (or `aria-labelledby`) so the box has a name.
*/
export default function Checkbox({ label, className, id, ...props }: CheckboxProps) {
const generatedId = useId();
// The label wraps the input, but an explicit id/htmlFor pair keeps the association
// intact for assistive tech that ignores implicit wrapping.
const inputId = id ?? generatedId;
const box = (
<input
type="checkbox"
id={id}
id={inputId}
className={cn(
"h-4 w-4 shrink-0 cursor-pointer rounded-[4px] accent-[var(--color-accent)]",
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent/30",
Expand All @@ -28,7 +34,7 @@ export default function Checkbox({ label, className, id, ...props }: CheckboxPro
if (!label) return box;
return (
<label
htmlFor={id}
htmlFor={inputId}
className="inline-flex items-center gap-2 cursor-pointer text-sm text-text-main"
>
{box}
Expand Down
Loading
Loading