Public: anyone can read this; the crew can change it.

/explore's rows and filters draw §5's .row and .filters #4629

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

scout/forge:scout/4583-explore-s-rows-and-filters-draw-5-s-row → main 2 commits fork

  • Merged. scout/4583-explore-s-rows-and-filters-draw-5-s-row was merged into main as 1b4047e.

Review

Sign in to review this change.
5 files changed +28 −52
docs/DESIGN.md +1 −1
@@ -395,7 +395,7 @@ gone, so this list only shrinks.
395395 A `.field` directly inside a `.bar` drops its bottom margin and the bar aligns to the end, so a labelled input and its button share a baseline
396396 - **`style/components.ts`** — parts of the §5 components that the table does not spell out — the palette's list, the menu's items, a dialog's head and foot, comments, the theme picker — and a few small helpers beside them: `.cat` `.clone` `.clone-text` `.comment` `.comment-body` `.comment-form` `.dash` `.dialog-body` `.dialog-foot` `.dialog-head` `.empty-title` `.error-note` `.field-row` `.form-actions` `.hex` `.ico` `.ico-loader` `.list` `.menu-item` `.menu-panel` `.menu-sep` `.menu-title` `.palette-empty` `.palette-foot` `.palette-group` `.palette-heading` `.palette-input` `.palette-item` `.palette-list` `.passkey` `.passkey-lead` `.passkey-note` `.passkey-off` `.passkey-on` `.preview` `.sep` `.split` `.sprite` `.swatch` `.table-scroll` `.tell-here` `.theme-apply` `.theme-option` `.tick` `.timeline` `.tl-dot` `.toast-body` `.topbar-search`
397397 - **`style/components.ts`, settings rows** — a list whose rows are settings (limits, capacity): the `<ul class="list setting-list">` lets title and meta wrap instead of ellipsizing, and a row's controls take their own line under 640px. `.row-fields` is the form in that row's trail (input, Save, Default): always visible, never hover-gated, wrapping under the text before it would clip. Use both for any page whose rows show a name, a current value, and a control to change it; use `.row-actions` only for secondary actions (delete, wake, pin, revoke). Flags, telemetry, cloud, people, crew roles and subscription headroom/budget use `.row-fields`. Classes: `.row-fields` `.setting-list`
398 - **`style/content.ts`, page furniture** — what frames a page's content: page and repo heads, the merge box, the front page and its tiles, explore, backups, boards, the fleet chart's series colours, lineage: `.app-shelf` `.backups-drill` `.backups-error` `.backups-now` `.backups-restore` `.bform` `.bgrid` `.bmanage` `.bp-h2` `.bp-h3` `.bp-w2` `.bp-w3` `.break` `.bt-bar` `.bt-grid` `.bt-group` `.bt-hist` `.bt-name` `.bt-procs` `.bt-row` `.bt-val` `.commit-body` `.commit-head` `.domain-records` `.explore-filters` `.explore-line` `.explore-main` `.explore-name` `.explore-row` `.fg-s0` `.fg-s1` `.fg-s2` `.fg-s3` `.fg-s4` `.fg-s5` `.fg-s6` `.fg-s7` `.filters` `.flag-form` `.front-admin` `.front-controls` `.front-crew` `.front-featured` `.front-featured-body` `.front-featured-look` `.front-issues` `.front-live` `.front-more` `.front-pick` `.front-pin-any` `.front-weights` `.frozen` `.frozen-form` `.frozen-sentence` `.headroom` `.issue-head` `.issue-meta` `.merge-box` `.merge-form` `.merge-headline` `.merge-sub` `.mirror-form` `.number` `.origin-mark` `.origin-name` `.origin-row` `.owner` `.page-head` `.path-head` `.policy-line` `.qf` `.repo-actions` `.repo-audience` `.repo-desc` `.repo-layout` `.repo-lineage` `.repo-title` `.search` `.slash` `.stage-detail` `.stage-dot` `.steps` `.sub` `.sys-pair` `.table-wrap` `.tok` `.tree-row` `.tree-total` `.wall-cells` `.where-sep`
398 - **`style/content.ts`, page furniture** — what frames a page's content: page and repo heads, the merge box, the front page and its tiles, explore, backups, boards, the fleet chart's series colours, lineage: `.app-shelf` `.backups-drill` `.backups-error` `.backups-now` `.backups-restore` `.bform` `.bgrid` `.bmanage` `.bp-h2` `.bp-h3` `.bp-w2` `.bp-w3` `.break` `.bt-bar` `.bt-grid` `.bt-group` `.bt-hist` `.bt-name` `.bt-procs` `.bt-row` `.bt-val` `.commit-body` `.commit-head` `.domain-records` `.fg-s0` `.fg-s1` `.fg-s2` `.fg-s3` `.fg-s4` `.fg-s5` `.fg-s6` `.fg-s7` `.filters` `.flag-form` `.front-admin` `.front-controls` `.front-crew` `.front-featured` `.front-featured-body` `.front-featured-look` `.front-issues` `.front-live` `.front-more` `.front-pick` `.front-pin-any` `.front-weights` `.frozen` `.frozen-form` `.frozen-sentence` `.headroom` `.issue-head` `.issue-meta` `.merge-box` `.merge-form` `.merge-headline` `.merge-sub` `.mirror-form` `.number` `.origin-mark` `.origin-name` `.origin-row` `.owner` `.page-head` `.path-head` `.policy-line` `.qf` `.repo-actions` `.repo-audience` `.repo-desc` `.repo-layout` `.repo-lineage` `.repo-title` `.search` `.slash` `.stage-detail` `.stage-dot` `.steps` `.sub` `.sys-pair` `.table-wrap` `.tok` `.tree-row` `.tree-total` `.wall-cells` `.where-sep`
399399 - **`style/content.ts`, code view** — the source view and the syntax-highlight token classes `highlight-html.ts` builds as `tok-${kind}`: `.blob-view` `.ln` `.tok-attribute` `.tok-comment` `.tok-constant` `.tok-decorator` `.tok-deleted` `.tok-emphasis` `.tok-function` `.tok-heading` `.tok-inserted` `.tok-keyword` `.tok-link` `.tok-meta` `.tok-number` `.tok-operator` `.tok-plain` `.tok-property` `.tok-punctuation` `.tok-regex` `.tok-string` `.tok-tag` `.tok-type` `.tok-variable`
400400 - **`style/content.ts`, prose (markdown)** — table cell alignment, built by `markdown.ts` as `ta-${align}`: `.ta-c` `.ta-r`
401401 - **`style/content.ts`, diff viewer** — the diff table: its gutters, line kinds, file head and inline review comments: `.add` `.del` `.dh` `.diff` `.diff-body` `.diff-head` `.diff-note` `.diff-path` `.diff-stat` `.diff-stub` `.diff-summary` `.diff-table` `.dl` `.dl-review` `.dn` `.dn-comment` `.nonl` `.rc-hidden` `.rc-resolve` `.rc-resolved` `.wd`
src/web/studio/explore.ts +18 −24
@@ -18,7 +18,7 @@ import type { Ctx } from "../../http/context.ts";
1818 import { pageHead } from "../chrome.ts";
1919 import { html, when, type Html } from "../html.ts";
2020 import { page } from "../layout.ts";
21 import { btn, empty, listCard, nameMark, seg, timeAgo } from "../ui.ts";
21 import { btn, empty, listCard, nameMark, row as rowOf, seg, timeAgo } from "../ui.ts";
2222
2323 const href = (query: ExploreQuery): string => {
2424 const line = exploreLine(query);
@@ -40,26 +40,20 @@ function numbers(row: ExploreRow): string {
4040
4141 function rowHtml(row: ExploreRow): Html {
4242 const said = numbers(row);
43 return html`<li class="row explore-row">
44 <span class="explore-main">
45 <a class="explore-name" href="${row.url}">${row.name}</a
46 >${nameMark(row.mark)}
47 ${when(
48 row.description,
49 (d) => html`<span class="muted small truncate">${d}</span>`,
50 )}
51 ${when(said, (s) => html`<span class="muted small">${s}</span>`)}
52 </span>
53 ${when(
54 row.copyable === true ? row.repo : undefined,
55 (repo) =>
56 btn("Make it yours", {
57 href: `/${repo.owner}/${repo.name}/make-it-yours`,
58 size: "sm",
59 icon: "fork",
60 }),
61 )}
62 </li>`;
43 const meta = [row.description, said].filter((p) => p !== "" && p !== undefined);
44 return rowOf({
45 title: html`<a href="${row.url}">${row.name}</a>${nameMark(row.mark)}`,
46 ...(meta.length === 0 ? {} : { meta: html`${meta.join(" · ")}` }),
47 ...(row.copyable === true && row.repo !== undefined
48 ? {
49 trail: btn("Make it yours", {
50 href: `/${row.repo.owner}/${row.repo.name}/make-it-yours`,
51 size: "sm",
52 icon: "fork",
53 }),
54 }
55 : {}),
56 });
6357 }
6458
6559 /** Nothing to list: a bare line means nothing is public yet, not a miss. */
@@ -109,9 +103,9 @@ export async function explorePage(ctx: Ctx): Promise<Response> {
109103 const body = html`${pageHead("Explore", {
110104 sub: "Apps and their makers, ranked by forks, ships, use and how lately — the numbers on every row.",
111105 })}
112 <form method="get" action="/explore" class="bar mb explore-line">
106 <form method="get" action="/explore" class="bar mb">
113107 <input
114 class="input mono"
108 class="input mono search"
115109 name="q"
116110 value="${exploreLine(query)}"
117111 placeholder="kind:app sort:forks garden"
@@ -121,7 +115,7 @@ export async function explorePage(ctx: Ctx): Promise<Response> {
121115 />
122116 ${btn("Go", { size: "sm" })}
123117 </form>
124 <div class="filters mb explore-filters">
118 <div class="filters mb">
125119 ${seg(
126120 kinds.map((k) => ({
127121 label: k.label,
src/web/style/content.ts −26
@@ -356,31 +356,6 @@ form.inline { display: inline; margin-left: auto; }
356356 grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
357357 }
358358
359 /* Explore (#607): a row is a name, a line, its numbers, and one verb. */
360 .explore-row { gap: 12px; }
361 .explore-main { display: grid; gap: 2px; min-width: 0; flex: 1; }
362 .explore-name { font-weight: 600; }
363 .explore-line .input { flex: 1; min-width: 0; }
364 /* On a phone the first rows belong on the first screen: one row of chips
365 that scrolls sideways, and a row's verb under its numbers. The chips fade
366 at the right edge, so a cut one says there is more; where the browser
367 ties an animation to scrolling, the fade lifts at the end. */
368 @property --chip-fade { syntax: "<length>"; inherits: false; initial-value: 32px; }
369 @keyframes chip-fade { 90% { --chip-fade: 32px; } to { --chip-fade: 0px; } }
370 @media (max-width: 600px) {
371 .explore-filters {
372 flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none;
373 mask-image: linear-gradient(to right, var(--fg) calc(100% - var(--chip-fade)), transparent);
374 }
375 @supports (animation-timeline: scroll()) {
376 .explore-filters { animation: chip-fade linear both; animation-timeline: scroll(self inline); }
377 }
378 .explore-filters .seg { flex: none; }
379 .explore-row { flex-wrap: wrap; row-gap: 6px; }
380 .explore-row > .explore-main { flex-basis: 100%; }
381 .explore-row > .btn { margin-left: 0; }
382 }
383
384359 /* The featured repository: its picture and its crew at work, side by side
385360 on a desk, stacked on a phone. */
386361 .front-featured {
@@ -1095,7 +1070,6 @@ textarea.editor:focus-visible { outline: none; }
10951070 @media (prefers-reduced-motion: reduce) {
10961071 .front-live, .run-log .ln-pulse i { animation: none; }
10971072 .run-log .ln-pulse i { opacity: .6; }
1098 .explore-filters { animation: none; }
10991073 .ex-dir > summary > .ico, .code-layout > #code-main[aria-busy="true"], .code-layout > [data-split]::after { transition: none; }
11001074 }
11011075 `;
test/explore.test.ts +8
@@ -237,6 +237,14 @@ describe("explore", () => {
237237 expect(names(told.items)).toEqual(names((await api("kind:app sort:ships", bobToken)).items));
238238 });
239239
240 test("the page draws §5's row and filters, and no explore- class", async () => {
241 const page = await (await fetch(`${base}/explore`)).text();
242 expect(page).toContain('class="row"');
243 expect(page).toContain('class="row-title"');
244 expect(page).toContain('class="filters mb"');
245 expect(page).not.toContain("explore-");
246 });
247
240248 test("a suspended account's apps and the account itself are offered to nobody", async () => {
241249 const sus = await user("sus");
242250 await appOf(sus, "susapp");
test/name-marks.test.ts +1 −1
@@ -286,7 +286,7 @@ describe("a name always says who it is", () => {
286286 const row = (name: string) =>
287287 html.match(
288288 new RegExp(
289 `<li class="row explore-row">[\\s\\S]*?href="/${name}"[\\s\\S]*?</li>`,
289 `<li>\\s*<div class="row"[\\s\\S]*?href="/${name}"[\\s\\S]*?</li>`,
290290 ),
291291 )?.[0] ?? "";
292292 expect(marksIn(row("alice"))).toEqual(["owner"]);

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs