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
30 changes: 30 additions & 0 deletions apps/web/src/page/chrome.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { describe, expect, test } from "vitest"
import { documentTitleOf } from "./chrome"

describe("documentTitleOf", () => {
test("leads with the page's own name, then its parent", () => {
expect(documentTitleOf([{ label: "Services", href: "/services" }, { label: "checkout-api" }])).toBe(
"checkout-api · Services · Maple",
)
})

test("a name that is still loading leaves the tab on its parent", () => {
expect(documentTitleOf([{ label: "Traces", href: "/traces" }, { label: "Loading…" }])).toBe(
"Traces · Maple",
)
})

test("a deep trail keeps its last two names", () => {
expect(
documentTitleOf([
{ label: "Alerts", href: "/alerts" },
{ label: "Rules", href: "/alerts/rules" },
{ label: "High error rate" },
]),
).toBe("High error rate · Rules · Maple")
})

test("no trail is the app's name alone", () => {
expect(documentTitleOf([])).toBe("Maple")
})
})
18 changes: 18 additions & 0 deletions apps/web/src/page/chrome.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Array as Arr, String as Str } from "effect"
import type { Html, HtmlBuilder } from "foldkit/html"
import { AppRoute, isPublicRoute } from "../route"
import type { BreadcrumbEntry } from "../shell"
Expand All @@ -22,6 +23,23 @@ export const breadcrumbsOf = (route: AppRoute): ReadonlyArray<BreadcrumbEntry> =
() => [{ label: route._tag }],
)

const APP_NAME = "Maple"

/** A crumb standing in for a name that has not loaded ("Loading…") names nothing yet. */
const isPendingLabel = (label: string) => Str.endsWith("…")(label)

/**
* The tab title: the page's own name first, then its parent, so a row of narrow tabs still tells
* them apart ("checkout-api · Services · Maple").
*/
export const documentTitleOf = (breadcrumbs: ReadonlyArray<BreadcrumbEntry>): string => {
const names = Arr.filter(
Arr.map(breadcrumbs, (crumb) => crumb.label),
(label) => label !== "" && !isPendingLabel(label),
)
return Arr.join([...Arr.reverse(Arr.takeRight(names, 2)), APP_NAME], " · ")
}

/**
* The page content while the page has no slot or context yet (no org open, a plan-gate
* redirect in flight): a page skeleton, so nothing but loading ever paints there.
Expand Down
14 changes: 12 additions & 2 deletions apps/web/src/view.scene.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { OrgId } from "@maple/domain"
import { Option, Schema } from "effect"
import type { HtmlBuilder } from "foldkit/html"
import { click, Command, expect as expectView, given, Mount, role, scene, text } from "foldkit/scene"
import { describe, test, vi } from "vitest"
import { describe, expect, test, vi } from "vitest"

// The shell's favorites listener targets `window` at import; the scene never touches it.
vi.hoisted(() => Object.assign(globalThis, { window: new EventTarget() }))
Expand All @@ -21,7 +21,7 @@ import * as Dialog from "@maple/views/ui/dialog"
import { MountSuggestions, MountWhereEditor } from "./page/traces/list/command"
import { Message as TracesMessage } from "./page/traces/list/message"
import { settle, update } from "./update"
import { view } from "./view"
import { titleOf, view } from "./view"
import * as TooltipHost from "@maple/views/ui/tooltip-host"

const orgId = Schema.decodeSync(OrgId)("org_acme")
Expand Down Expand Up @@ -89,3 +89,13 @@ describe("root view", () => {
)
})
})

describe("tab title", () => {
test("an open page names the tab, and a route with no page up yet is just Maple", () => {
expect([
titleOf(tracesPage()),
titleOf(signedIn()),
titleOf({ ...signedIn(), route: AppRoute.NotFound({ path: "/nope" }) }),
]).toEqual(["Traces · Maple", "Maple", "Page not found · Maple"])
})
})
22 changes: 20 additions & 2 deletions apps/web/src/view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,13 @@ import { Message } from "./message"
import { type Model, Session } from "./model"
import { onboardingInputsOf, pageContextOf, planGateOf, shellClerkOf } from "./update"
import * as OnboardingView from "./page/onboarding/view"
import { breadcrumbsOf, notFoundContent, placeholderContent, rendersShell } from "./page/chrome"
import {
breadcrumbsOf,
documentTitleOf,
notFoundContent,
placeholderContent,
rendersShell,
} from "./page/chrome"
import type { PageContext, ShellInfraSurfaces } from "./page/contract"
import {
isBarePageRoute,
Expand Down Expand Up @@ -282,8 +288,20 @@ const body = (model: Model, h: HtmlBuilder<Message>): Html =>
],
)

/** The tab names the open page; a route with no page up yet (or none at all) is just "Maple". */
export const titleOf = (model: Model): string =>
documentTitleOf(
model.page !== null
? pageBreadcrumbs(model.page)
: AppRoute.matchOrElse<ReadonlyArray<Shell.BreadcrumbEntry>>(
model.route,
{ NotFound: () => breadcrumbsOf(model.route) },
() => [],
),
)

// NOTE: Foldkit's first render replaces the container, so the view root is `div#app` itself.
export const view = (model: Model, h: HtmlBuilder<Message>): Document => ({
title: "Maple",
title: titleOf(model),
body: body(model, h),
})
2 changes: 2 additions & 0 deletions docs/design-rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@
step of the hue in light, where the fill itself is near 3:1 as type, and the fill in dark. Fills,
borders and strokes keep the tone. Write the utility; never a fill token as `color` in a style
object.
- Selected text takes `primary-a4` as its highlight and reads in `foreground`, set once in
tokens.css. No `selection:` utility at a call site.

Check notice on line 101 in docs/design-rules.md

View check run for this annotation

Maple Review Bot / Maple / review

convention: New rule forbids a `selection:` utility that `native-select.styles.ts` still uses

The bullet states no call site carries a `selection:` utility, but `packages/views/src/ui/native-select.styles.ts:4` sets `selection:bg-primary selection:text-primary-foreground`. That select keeps a solid primary highlight instead of the new wash, and a later reader of this rule finds it contradicted in the kit.
- Washes are translucent and read the same on any surface: `surface-faint`, `surface-sunken`,
`surface-strong`.
- A see-through surface is a token, never `bg-background/NN` or `bg-card/NN`
Expand Down
6 changes: 6 additions & 0 deletions packages/ui/src/styles/tokens.css
Original file line number Diff line number Diff line change
Expand Up @@ -812,6 +812,12 @@
* {
@apply border-border outline-ring/50;
}
/* Selected text takes the primary's strong wash instead of the system blue, and reads in
`foreground`: a muted or toned run would fall under 4.5:1 on the wash. */
::selection {
background-color: var(--color-primary-a4);
Comment thread
Makisuo marked this conversation as resolved.
Resolved
color: var(--color-foreground);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Warning

Selected text inside a bg-primary fill drops to 2.1:1 in dark mode

F1 · Warning · correctness

Forcing foreground on every selection overwrites the dark text that filled primary surfaces use. In dark mode foreground is oklch(0.955) and primary oklch(0.74 0.15 62), so selecting the label of a primary badge (badge.styles.ts:36), a selected choice-card (choice-card.ts:26) or a calendar day washes it to 2.1:1; primary-foreground was 7.9:1.

Keep the wash but exempt primary fills: `.bg-primary::selection, .bg-primary ::selection { color: var(--color-primary-foreground) }`, or pick `primary`/`primary-foreground` as the pair.
🤖 Prompt to fix with an AI agent
In `packages/ui/src/styles/tokens.css:819-820`: Selected text inside a `bg-primary` fill drops to 2.1:1 in dark mode.

Forcing `foreground` on every selection overwrites the dark text that filled primary surfaces use. In dark mode `foreground` is oklch(0.955) and `primary` oklch(0.74 0.15 62), so selecting the label of a primary badge (`badge.styles.ts:36`), a selected `choice-card` (`choice-card.ts:26`) or a calendar day washes it to 2.1:1; `primary-foreground` was 7.9:1.

Suggested fix: Keep the wash but exempt primary fills: `.bg-primary::selection, .bg-primary ::selection { color: var(--color-primary-foreground) }`, or pick `primary`/`primary-foreground` as the pair.

Verify the problem exists at that location before changing it, and keep the fix to those lines.

}

Check warning on line 820 in packages/ui/src/styles/tokens.css

View check run for this annotation

Maple Review Bot / Maple / review

correctness: Selected text inside a `bg-primary` fill drops to 2.1:1 in dark mode

Forcing `foreground` on every selection overwrites the dark text that filled primary surfaces use. In dark mode `foreground` is oklch(0.955) and `primary` oklch(0.74 0.15 62), so selecting the label of a primary badge (`badge.styles.ts:36`), a selected `choice-card` (`choice-card.ts:26`) or a calendar day washes it to 2.1:1; `primary-foreground` was 7.9:1.
body {
@apply font-sans bg-background text-foreground;
}
Expand Down
Loading