Phase 7: polish — overview health, reduced-motion, mobile, a11y, copy-ids
- Overview health hero derives from subsystem probes when the backend gives no overall status, so it never says "Unknown" next to an all-Healthy Subsystems card (the audit's most jarring contradiction). Home error copy now uses the Phase-2 describeError instead of raw backend strings. - Global prefers-reduced-motion guard (transitions, loops, aurora field) + motion-reduce on the sidebar width transition. - Mobile: content reserves a top row (pt-16 md:p-6) so the floating hamburger never overlaps the page H1. - A11y: collapsed rail items get an explicit aria-label. - Monitoring: disk "busiest mount" tile shows the % as the value and the long mount path as a truncated caption, so it no longer clips mid-word. - New CopyId component (click-to-copy UUIDs), wired into Profile. Typecheck 36→36, 24-route sweep clean. Remaining items (lib a11y labels, table aria-sort, dev-only monitoring data) noted in the spec. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -11,7 +11,7 @@ import {
|
||||
Users as UsersIcon,
|
||||
} from "lucide-react"
|
||||
|
||||
import { ArcadiaError, useArcadiaClient } from "@crema/arcadia-core-client"
|
||||
import { useArcadiaClient } from "@crema/arcadia-core-client"
|
||||
import { AlertBanner } from "@crema/feedback-ui"
|
||||
|
||||
import { AppShell } from "~/components/layout/app-shell"
|
||||
@@ -37,6 +37,7 @@ import { listTenants, type Tenant } from "~/lib/arcadia/tenants"
|
||||
import { listUsers, type User } from "~/lib/arcadia/users"
|
||||
import { useRegisterContext } from "@crema/aifirst-ui/context"
|
||||
import { pageTitle } from "~/lib/page-meta"
|
||||
import { errorMessage } from "~/lib/errors"
|
||||
import { useSession } from "~/lib/session"
|
||||
|
||||
export const meta = () => pageTitle("Overview")
|
||||
@@ -56,7 +57,7 @@ export default function HomeRoute() {
|
||||
|
||||
const [data, setData] = useState<DashboardData>(EMPTY)
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
const [error, setError] = useState<unknown>(null)
|
||||
const [refreshedAt, setRefreshedAt] = useState<Date | null>(null)
|
||||
|
||||
const refresh = useCallback(async () => {
|
||||
@@ -70,7 +71,7 @@ export default function HomeRoute() {
|
||||
listAuditLogs(arcadia, { limit: 10 }),
|
||||
getHealth(arcadia).catch(() => null),
|
||||
]).catch((err) => {
|
||||
setError(err instanceof ArcadiaError ? err.message : "Failed to load overview.")
|
||||
setError(err)
|
||||
return [[], [], [], null] as [Tenant[], User[], AuditLog[], OverallHealth | null]
|
||||
})
|
||||
setData({ tenants, users, audit, health })
|
||||
@@ -92,7 +93,7 @@ export default function HomeRoute() {
|
||||
tenants: { total: data.tenants.length, active: activeTenants },
|
||||
users: { total: data.users.length, active: activeUsers },
|
||||
audit: { recent: data.audit.length, errors: errorEvents },
|
||||
health: data.health?.status ?? "unconfigured",
|
||||
health: deriveOverallStatus(data.health),
|
||||
}
|
||||
}, [data])
|
||||
|
||||
@@ -129,7 +130,7 @@ export default function HomeRoute() {
|
||||
|
||||
{error ? (
|
||||
<AlertBanner variant="error" dismissible onDismiss={() => setError(null)}>
|
||||
{error}
|
||||
{errorMessage(error, "load the overview")}
|
||||
</AlertBanner>
|
||||
) : null}
|
||||
|
||||
@@ -408,6 +409,25 @@ function statusLabel(status: HealthStatus | string): string {
|
||||
return "Unknown"
|
||||
}
|
||||
|
||||
/**
|
||||
* The overall health hero must never say "Unknown" while its own Subsystems
|
||||
* card reports everything Healthy. When the backend gives no meaningful overall
|
||||
* status (unknown/unconfigured/missing), derive it from the subsystem probes
|
||||
* the same card shows: any error → Down, any degraded → Degraded, otherwise
|
||||
* Healthy if at least one subsystem reported ok.
|
||||
*/
|
||||
function deriveOverallStatus(health: OverallHealth | null): HealthStatus | string {
|
||||
if (!health) return "unconfigured"
|
||||
if (health.status === "ok" || health.status === "degraded" || health.status === "error") {
|
||||
return health.status
|
||||
}
|
||||
const statuses = Object.values(health.subsystems ?? {}).map((s) => s?.status)
|
||||
if (statuses.some((s) => s === "error")) return "error"
|
||||
if (statuses.some((s) => s === "degraded")) return "degraded"
|
||||
if (statuses.some((s) => s === "ok")) return "ok"
|
||||
return health.status ?? "unconfigured"
|
||||
}
|
||||
|
||||
function statusTone(status: HealthStatus | string): "default" | "ok" | "warning" | "error" {
|
||||
if (status === "ok") return "ok"
|
||||
if (status === "degraded") return "warning"
|
||||
|
||||
Reference in New Issue
Block a user