Public: anyone can read this; the crew can change it.
The /explore page speaks the home page's language, with looks before and after #9687
Check · Review · Land · Live done — Landed and live in production.
scout/forge:scout/9645-the-explore-page-speaks-the-home-page-s → main 4 commits fork 2 threads · 2 unresolved
4 files
changed +22
−6
docs/design/areas/home.md +2
| @@ -6,3 +6,5 @@ | ||
| 6 | 6 | - `.home-panels-slot` — home's place for the pinned panels, right after the KPIs (`pinnedSlot` in `src/web/studio/pinned.ts`). Only while it still waits for `/live/home/panels` (`data-refresh`) it holds 240px, about one panel row, with the 16px below it that `.bgrid` keeps (the system has no spacing tokens, only the 4px scale in §4, and both sit on it), so the charts arriving after first paint shift nothing; the refresh script drops the attribute when the fragment lands, is empty, or fails, and the slot collapses to its content. |
| 7 | 7 | |
| 8 | 8 | The signed-in home's door line (Notes, Talk) is `.home-doors`: accent underlined links, each a 44px tap target (#9584). |
| 9 | ||
| 10 | `/explore` (#9645) speaks the same language: the kinds (All, Apps, People, Forges) are `tabs()` as on home; sort and time stay one `seg` each in a small line (they stay boxed: they are two-to-three-way switches whose current choice must read at a glance, and a plain link row would blur it from the tabs above), with "Show archived" a link (`aria-pressed`) wearing `.home-doors`' 44px hit area, no box; the rows are a `.card.bare` list, rows and space on the page's one background, no box. Row words, ranking, "Make it yours" and the URL line are unchanged, so the terminal's explore search keeps the same words. No new class. |
src/web/studio/explore.ts +8 −5
| @@ -21,7 +21,7 @@ import { edgeLoginUrl } from "../../http/edge.ts"; | ||
| 21 | 21 | import { offersSignup, pageHead } from "../chrome.ts"; |
| 22 | 22 | import { html, type Html } from "../html.ts"; |
| 23 | 23 | import { page } from "../layout.ts"; |
| 24 | import { btn, empty, listCard, nameMark, row as rowOf, seg, timeAgo } from "../ui.ts"; | |
| 24 | import { btn, card, empty, nameMark, row as rowOf, seg, tabs, timeAgo } from "../ui.ts"; | |
| 25 | 25 | |
| 26 | 26 | const href = (query: ExploreQuery): string => { |
| 27 | 27 | const line = exploreLine(query); |
| @@ -183,14 +183,17 @@ export async function explorePage(ctx: Ctx): Promise<Response> { | ||
| 183 | 183 | ? html`` |
| 184 | 184 | : html` Did you mean <a href="/explore?q=${encodeURIComponent(suggestion.line)}">${suggestion.fix}</a>?` |
| 185 | 185 | }</p>`} |
| 186 | <div class="bar mb"> | |
| 187 | ${seg( | |
| 186 | <div class="mb"> | |
| 187 | ${tabs( | |
| 188 | 188 | kinds.map((k) => ({ |
| 189 | 189 | label: k.label, |
| 190 | 190 | href: href({ ...query, kind: k.kind }), |
| 191 | 191 | current: query.kind === k.kind, |
| 192 | 192 | })), |
| 193 | "Kind", | |
| 193 | 194 | )} |
| 195 | </div> | |
| 196 | <div class="bar mb small"> | |
| 194 | 197 | ${seg( |
| 195 | 198 | sorts.map((s) => ({ |
| 196 | 199 | label: s.label, |
| @@ -206,7 +209,7 @@ export async function explorePage(ctx: Ctx): Promise<Response> { | ||
| 206 | 209 | })), |
| 207 | 210 | )} |
| 208 | 211 | <a |
| 209 | class="btn" data-size="sm" | |
| 212 | class="muted home-doors" | |
outdated
Principle 6/phone: a bare muted link is a tiny thumb target at 360px. Give it a 44px tap area (as .home-doors does) without adding a box. |
||
| 210 | 213 | href="${href({ ...query, archived: !query.archived })}" |
| 211 | 214 | aria-pressed="${query.archived ? "true" : "false"}" |
| 212 | 215 | >Show archived</a |
| @@ -214,7 +217,7 @@ export async function explorePage(ctx: Ctx): Promise<Response> { | ||
| 214 | 217 | </div> |
| 215 | 218 | ${rows.length === 0 |
| 216 | 219 | ? emptyState(ctx, query) |
| 217 | : listCard(rows.map(rowHtml))}</div>`; | |
| 220 | : card(html`<ul class="list">${rows.map(rowHtml)}</ul>`, { pad: false, extra: "bare" })}</div>`; | |
| 218 | 221 | const response = page({ ctx, title: "Explore", body }); |
| 219 | 222 | notePhase(ctx.request, "render", performance.now() - rendering); |
| 220 | 223 | return response; |
test/explore.test.ts +2 −1
| @@ -280,7 +280,8 @@ describe("explore", () => { | ||
| 280 | 280 | const page = await (await fetch(`${base}/explore`)).text(); |
| 281 | 281 | expect(page).toContain('class="row"'); |
| 282 | 282 | expect(page).toContain('class="row-title"'); |
| 283 | expect(page).toContain(`<div class="bar mb">`); | |
| 283 | expect(page).toContain(`<nav class="tabs" aria-label="Kind">`); | |
| 284 | expect(page).toContain(`<div class="bar mb small">`); | |
| 284 | 285 | expect(page).not.toMatch(/class="[^"]*\bfilters\b/); |
| 285 | 286 | expect(page).not.toContain("explore-"); |
| 286 | 287 | }); |
test/explorer-fit.test.ts +10
| @@ -103,3 +103,13 @@ describe.skipIf(chromium === undefined)("/explore, measured at 390x844", () => { | ||
| 103 | 103 | }, 30_000); |
| 104 | 104 | } |
| 105 | 105 | }); |
| 106 | ||
| 107 | describe("/explore speaks the home page's language (#9645)", () => { | |
| 108 | test("kinds are tabs, rows sit on a bare card, filters and Make it yours remain", () => { | |
| 109 | expect(pages[0]).toContain('<nav class="tabs"'); | |
| 110 | expect(pages[0]).toMatch(/class="card bare"/); | |
| 111 | expect(pages[0]).not.toMatch(/class="card"/); | |
| 112 | expect(pages[0]).toContain("Show archived"); | |
| 113 | expect(pages[0]).toContain("make-it-yours"); | |
| 114 | }); | |
| 115 | }); |
Principle 6: seg is still a boxed pill control for sort and time. Justify in the area note or use plain links.