Observability gets its own page; tabs never overflow
Deployments had grown eight views, logs and errors among them, which are not deploying. It keeps what ships (Deployments, Environments, Releases, Feature flags); Observability is its own page in the menu (Logs, Errors, Uptime, Analytics), as Vercel has it. Soon tabs are marked with a dot instead of a pill, tab bars never show a scrollbar, and Soon pages under a project's tabs drop the breadcrumb the tabs already give.
5 files+27−170/5 viewed
| 1 | 1 | import { | |
| 2 | + | Activity, | |
| 2 | 3 | ArrowLeft, | |
| 3 | 4 | GanttChart, | |
| 4 | 5 | KanbanSquare, | |
| 496 | 497 | Deployments | |
| 497 | 498 | </SidebarLink> | |
| 498 | 499 | ) : null} | |
| 500 | + | <SidebarSoonLink to={`${base}/soon/logs`} also={soonPaths(base, "Observability")} icon={<Activity size={15} />} about="Logs, errors, uptime and analytics of the project's deployed apps."> | |
| 501 | + | Observability | |
| 502 | + | </SidebarSoonLink> | |
| 499 | 503 | <SidebarSoonLink to={`${base}/soon/security`} also={soonPaths(base, "Security")} icon={<ShieldCheck size={15} />} about="Findings, secret scanning, dependency updates and code scanning, each fixed by an agent."> | |
| 500 | 504 | Security | |
| 501 | 505 | </SidebarSoonLink> |
| 15 | 15 | * Where it lives: a project page whose tabs it joins, or the workspace, | |
| 16 | 16 | * for what spans projects (boards, the roadmap, packages, the fleet). | |
| 17 | 17 | */ | |
| 18 | − | section: "Code" | "Issues" | "Agents" | "Deployments" | "Security" | "Insights" | "Workspace"; | |
| 18 | + | section: "Code" | "Issues" | "Agents" | "Deployments" | "Observability" | "Security" | "Insights" | "Workspace"; | |
| 19 | 19 | /** One line, for the menu's tooltip and the page's lead. */ | |
| 20 | 20 | summary: string; | |
| 21 | 21 | /** Why it matters, in two or three sentences. */ | |
| 273 | 273 | // --- Observe ------------------------------------------------------------ | |
| 274 | 274 | { | |
| 275 | 275 | key: "logs", | |
| 276 | − | title: "Logs and metrics", | |
| 277 | − | section: "Deployments", | |
| 276 | + | title: "Logs", | |
| 277 | + | section: "Observability", | |
| 278 | 278 | summary: "Requests, errors and CPU time of each deployment, and its logs.", | |
| 279 | 279 | why: "Every deployed app's logs and numbers, by deployment, so a regression points at the change that caused it.", | |
| 280 | 280 | plans: ["Live and searchable logs", "Requests, errors, latency and CPU by deployment", "Compare a preview with production"], | |
| 282 | 282 | }, | |
| 283 | 283 | { | |
| 284 | 284 | key: "errors", | |
| 285 | − | title: "Errors and incidents", | |
| 286 | − | section: "Deployments", | |
| 285 | + | title: "Errors", | |
| 286 | + | section: "Observability", | |
| 287 | 287 | summary: "Errors from the running app, each becoming an issue an agent can take.", | |
| 288 | 288 | why: "An error in production should become a fix, not a dashboard. Each new error is grouped, explained, and turned into an issue with the context an agent needs.", | |
| 289 | 289 | plans: ["Errors grouped with stack and request", "One click to an issue for an agent", "Incidents with a timeline and who was told"], | |
| 292 | 292 | { | |
| 293 | 293 | key: "uptime", | |
| 294 | 294 | title: "Uptime", | |
| 295 | − | section: "Deployments", | |
| 295 | + | section: "Observability", | |
| 296 | 296 | summary: "Checks that the app answers, from around the world.", | |
| 297 | 297 | why: "Know the app is down before your users tell you, and who was told.", | |
| 298 | 298 | plans: ["Checks from many places", "Alerts by email and webhook", "A public status page"], | |
| 300 | 300 | { | |
| 301 | 301 | key: "analytics", | |
| 302 | 302 | title: "Web analytics", | |
| 303 | − | section: "Deployments", | |
| 303 | + | section: "Observability", | |
| 304 | 304 | summary: "Who visits the deployed apps, without cookies.", | |
| 305 | 305 | why: "Visits, pages and referrers for each app, private by design, from Cloudflare's own analytics.", | |
| 306 | 306 | plans: ["Visits, pages, referrers and countries", "No cookies, no personal data", "Per deployment and per preview"], |
| 84 | 84 | const current = (tab: PageTab) => | |
| 85 | 85 | [tab.path, ...(tab.also ?? [])].some((path) => rest === path || rest.startsWith(`${path}/`)); | |
| 86 | 86 | return ( | |
| 87 | − | <nav aria-label="Views" className="-mb-px flex gap-1 overflow-x-auto"> | |
| 87 | + | <nav aria-label="Views" className="-mb-px flex gap-1 overflow-x-auto [scrollbar-width:none] [&::-webkit-scrollbar]:hidden"> | |
| 88 | 88 | {tabs.map((tab) => ( | |
| 89 | 89 | <NavLink | |
| 90 | 90 | key={tab.path} | |
| 100 | 100 | }`} | |
| 101 | 101 | > | |
| 102 | 102 | {tab.label} | |
| 103 | + | {/* Soon, said quietly: a dot, with the word for screen readers. */} | |
| 103 | 104 | {tab.soon && ( | |
| 104 | − | <span className="rounded-full px-1.5 py-px text-[0.625rem] font-medium tracking-wide text-muted uppercase ring-1 ring-line"> | |
| 105 | − | Soon | |
| 106 | − | </span> | |
| 105 | + | <> | |
| 106 | + | <span aria-hidden="true" className="size-1.5 rounded-full bg-accent/70" /> | |
| 107 | + | <span className="sr-only">(soon)</span> | |
| 108 | + | </> | |
| 107 | 109 | )} | |
| 108 | 110 | </NavLink> | |
| 109 | 111 | ))} |
| 37 | 37 | const inWorkspace = item.section === "Workspace"; | |
| 38 | 38 | return ( | |
| 39 | 39 | <div className="mx-auto max-w-3xl"> | |
| 40 | − | <p className="flex items-center gap-2 text-sm text-muted"> | |
| 41 | − | <span>{inWorkspace ? "Across projects" : item.section}</span> | |
| 42 | − | <span className="text-line-strong">/</span> | |
| 43 | − | <span className="text-fg">{item.title}</span> | |
| 44 | − | </p> | |
| 45 | − | <div className="mt-4 flex flex-wrap items-center gap-3"> | |
| 40 | + | {/* A project's tabs already say where this is; the workspace's need saying. */} | |
| 41 | + | {inWorkspace && ( | |
| 42 | + | <p className="mb-4 flex items-center gap-2 text-sm text-muted"> | |
| 43 | + | <span>Across projects</span> | |
| 44 | + | <span className="text-line-strong">/</span> | |
| 45 | + | <span className="text-fg">{item.title}</span> | |
| 46 | + | </p> | |
| 47 | + | )} | |
| 48 | + | <div className="flex flex-wrap items-center gap-3"> | |
| 46 | 49 | <h1 className="text-3xl font-semibold tracking-tight">{item.title}</h1> | |
| 47 | 50 | <span className="inline-flex items-center gap-1.5 rounded-full border border-accent/40 bg-accent/10 px-2.5 py-0.5 text-xs font-medium text-accent"> | |
| 48 | 51 | <Sparkles size={12} /> |