Skip to content

Commit 5fbddf8

Browse files
zaerlclaude
andauthored
Draw each site's server dot before its name in the sites list (#652)
## Why The prototype draws each site's server dot at the left of its name in the sites list, so which sites are running can be seen without opening each. The app had the dot only in the open site's header: a contributor with three sites had to open each one to learn which server was up. The dot also sat 1.5px above the lowercase letters beside it, in the header as in the list. ## What changes Every entry in the sites list now wears the header's own dot before its name: grey while the server is stopped, green running, amber starting, red for a server that went by itself or could not start, with the reason as the dot's tooltip. The attention dot for a stale or half-updated site (#94) stays, drawn after it. A server lives in its site's view, open or not, so the view is the one thing that knows. Each `SiteRow` reports its server's state to the window as it changes and takes the report back when it unmounts; the window keeps the reports by path and hands them to `sitesListRows`. What a report says, what it does to the reports and what the dot shows are all decided in `src/renderer/sites-list.cjs`, where the suite can reach them. For a screen reader the dot's words follow the name, as the attention dot's do, and only when the dot is not the grey every site starts with: said after every name, "Server stopped" would be the thing heard most and needed least. So an entry is named `my-site` while stopped and `my-site (Server running)` while running. Both dots, and the header's, are now centred on the text's x-height with `vertical-align: middle`, which centres an inline box on the parent font's x-height by definition and so holds on Windows with a different font. A flex item, which the dots were, is centred on the line box instead, 1.5px higher. Deliberately not in it: a dot for the build watch in the list, and any control on the list's dot. The dot reports; the header and the details act. ## How to test this Platforms: any. The x-height alignment is worth a look on Windows too, where the font differs: the rule is font-agnostic, but only a look confirms it. The first journey in `tests/e2e/journeys/dev-server.spec.js` walks the reported path and reads the list's dot at each turn: starting, running, stopped and crashed, with the entry's name. It is red without the change. From the repository root: ```bash npx playwright test --project=journeys tests/e2e/journeys/dev-server.spec.js ``` What the journey cannot reach is a real server, since journeys stub the handler that starts one, and what the dots look like beside real text: **Starting state:** two sites, both set up, neither server running. The Buildkite artifact for the head commit, on macOS or Windows. 1. Look at the sites list. Expected: every entry has a grey dot at the left of its name, on the same line as the lowercase letters, and a stale site has its amber dot after it. 2. On the open site, open the server's menu in the page header and choose **Start development server**. Expected: the entry's dot turns amber while the server starts and green once the site opens in the browser. The header's dot sits on its words' x-height, as the list's does. 3. Click the other site in the list. Expected: its own dot is still grey, and the first site's dot is still green while you look at the second. 4. Go back to the first site and choose **Stop development server**. Expected: its dot is grey again. 5. Narrow the window until the header's menus show only their dots. Expected: one gap between a dot and its chevron, not two. **What must not have happened:** no site's entry has lost its name or project line; a long name still ends in an ellipsis; the attention dot of a stale site is still there, after the server dot; nothing else in the header moved. ## Risks and limitations - A running stale site shows two dots side by side, a green and an amber. The prototype has no attention dot to reconcile with, so this keeps both rather than folding two meanings into one dot. - A crashed server's words are the whole sentence from the details, so that entry's accessible name is long. It says what the red dot means, which "Server stopped" would not. - The guide's sidebar screenshots were not retaken. - Pre-PR review: 2 [fix here] · 0 [follow-up], both fixed in `6229fb6`. ## Related Follow-up to #590 (the sites list) and #595 (the server and the watch in the header). --- <details> <summary>Design decisions and alternatives considered</summary> - **The view reports up, rather than the main process broadcasting.** "Starting" is a renderer state: it covers the wait for the build watch before the server is even asked for. Only the view knows it, so a main-process list of running servers would have drawn the wrong colour for the longest phase. - **One effect, with a cleanup that takes the report back.** Two effects, one to report and one to clean up on unmount, would have lost the entry if the callback's identity ever changed; one effect's cleanup and re-run land in the same commit, so the list sees a single change and no grey dot in between. - **No words for a stopped server.** The prototype announces "Server stopped" before every name. Here the grey dot is the default and says nothing, as the attention dot says nothing when there is nothing to report. - **`vertical-align: middle` over a pixel offset.** The dot was measured 1.5px high against the ink of the letters, in a screenshot of the running app. A `margin-top` would have fixed that for this font and this size; `vertical-align: middle` is the CSS definition of "centred on the x-height", so it follows the font. </details> <details> <summary>Review outcome (required — see AGENTS.md)</summary> 2 [fix here] · 0 [follow-up] — both fixed. - 🟡 Architecture, `[fix here]`: the words a view reports of its server, and what a report does to the window's reports, were decided inline in `index.jsx`, where the suite cannot reach them. Fixed: `serverReport` and `withServerReport` in `src/renderer/sites-list.cjs`, with unit tests. - 🔵 Layout, `[fix here]`: the space between a process's dot and its words was the dot's margin, which stayed when the words gave way to the dot in a short header and doubled the gap before the chevron. Fixed: the margin is the words' own and goes with them. - **Review:** completed — a separate agent context running `.github/instructions/code-review.instructions.md`; reviewed head `aad1b72` / base `0cd70e4` (origin/trunk); `npm test` 2047 pass, `eslint src tests scripts` and Stylelint clean (a repo-wide `eslint .` fails only on stale checkouts under `.claude/worktrees`, which are not part of the branch); outcome above. - **Since review:** `aad1b72 → 6229fb6` checked; the one commit applies the two findings and nothing else. `npm test` 2049 pass, lint clean, and the dev-server, site-header, auto-start and logs journeys pass on the new head. - **CodeRabbit:** not yet requested. </details> <details> <summary>Implementation notes</summary> - `src/renderer/sites-list.cjs`: `serverDot` (what the row draws; unknown or missing reports are offline), `serverReport` (what a view reports from `serverProcess`), `withServerReport` (the reducer). `sitesListRows` takes `servers` by path. - `src/renderer/index.jsx`: `serverReports` state and the `onServerStatus` callback in `App`; the reporting effect in `SiteRow`. - `src/renderer/components/sites-sidebar.jsx`: draws `ProcessStatus` before the name and the hidden words after it. `ProcessStatus` in `site-header-actions.jsx` gained an optional `title`. - `src/renderer/shell.css`: the name span is one nowrap line with the ellipsis, the dots inline boxes; the header's dot and words are one inline span inside the button. - Journeys: `dev-server.spec.js` reads the list's dot at each state; `logs.spec.js` and `auto-start.spec.js` name a running site's entry with its dot's words, since the accessible name carries them. - Measured alignment, in the ink of a window screenshot at 1x: list dot centre 93.5 against the letters' 95.5 before, 95.5 against 95.5 after; header dot 32.5 against 34 before, 33.5 against 34 after. </details> <details> <summary>Screenshots or recording</summary> The list before the alignment fix (dots centred on the line box) and after (dots on the x-height), and the header's menu, are to be attached from the measuring run; `gh` cannot upload images, so they go in from the web UI. </details> 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
1 parent 0cd70e4 commit 5fbddf8

11 files changed

Lines changed: 245 additions & 39 deletions

File tree

‎docs/guide/creating-a-site.md‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,9 @@ Each site is an ordinary folder on your disk: the parent folder you chose in **S
3838

3939
Every site appears in the sidebar, newest first. Click a site to switch to it; the button for the active site is highlighted. Every site carries its project as a tag on its row and next to its status, **Core** or **Gutenberg**, so the two kinds are told apart at a glance; on a Gutenberg site the checklist, the terminal and the cards on its page follow suit (a GitHub issue instead of a Trac ticket, and pull-request checkout instead of Trac attachments or a local patch-file picker). The chevron at the top collapses the sidebar to a narrow strip showing only each site's initial.
4040

41-
A colored dot next to a site name warns that it has fallen behind trunk:
41+
A dot before each site name shows its development server, so you can see which sites are running without opening each: grey while it is stopped, green while it is running, amber while it starts, and red when it stopped by itself or could not start. The red dot's reason is in the open site's details and in its Logs.
42+
43+
A second colored dot, after it, warns that a site has fallen behind trunk:
4244

4345
- **Amber** — the checkout is more than 14 days old.
4446
- **Red** — a previous update moved the code but never finished installing or rebuilding.

‎docs/guide/trunk-updates.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ After the update, the work-item card may say **Trunk has moved since this ticket
4242

4343
## Staleness dots and notices
4444

45-
The app flags sites that have fallen behind with a coloured dot next to the site name in the sidebar:
45+
The app flags sites that have fallen behind with a coloured dot next to the site name in the sidebar, after the grey or green dot that shows the site's development server:
4646

4747
- **Amber** — the snapshot is more than 14 days old. The site view also shows a notice recommending an update before you prepare changes against old code, with an **Update to latest trunk** button.
4848
- **Red** — a previous update moved the code but never finished installing or rebuilding, so the built assets no longer match the source. Run the update again.

‎src/renderer/components/site-header-actions.jsx‎

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,11 @@ function MenuAction({ item, onAction }) {
3535
* same, so the dot is for the eye only.
3636
*
3737
* @param {Object} props
38-
* @param {string} props.status 'online', 'busy', 'failed' or 'offline'.
38+
* @param {string} props.status 'online', 'busy', 'failed' or 'offline'.
39+
* @param {string} [props.title] What the dot stands for, for a pointer over it; the words beside it say it everywhere else.
3940
*/
40-
export function ProcessStatus({ status }) {
41-
return <span className={`process-status is-${status}`} aria-hidden="true" />;
41+
export function ProcessStatus({ status, title }) {
42+
return <span className={`process-status is-${status}`} title={title} aria-hidden="true" />;
4243
}
4344

4445
// One of the site's two processes, in the header (#557): what it is doing, and
@@ -51,10 +52,14 @@ function ProcessMenu({ process, onToggle, links = [], onOpenLink = null }) {
5152
<Menu.Trigger
5253
render={
5354
<Button className="process-menu-trigger" variant="minimal" tone="neutral" size="compact" title={process.label}>
54-
<ProcessStatus status={process.status} />
55-
{/* The words give way to the dot when the header is short of
56-
room (shell.css); they are still the button's name. */}
57-
<span className="header-wide-only">{process.label}</span>
55+
{/* The dot and the words are one line, so that the dot can sit
56+
on the words' x-height (shell.css). The words give way to
57+
the dot when the header is short of room; they are still the
58+
button's name. */}
59+
<span className="process-menu-state">
60+
<ProcessStatus status={process.status} />
61+
<span className="header-wide-only">{process.label}</span>
62+
</span>
5863
<Icon icon={chevronDown} size={16} />
5964
</Button>
6065
}

‎src/renderer/components/sites-sidebar.jsx‎

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,18 @@ import { DataViews } from '@wordpress/dataviews';
44
import { __, sprintf } from '@wordpress/i18n';
55
import { Icon, wordpress } from '@wordpress/icons';
66
import { Button, VisuallyHidden } from '@wordpress/ui';
7+
import { ProcessStatus } from './site-header-actions.jsx';
78

89
// The list's two fields: the name, which is the row's title and so its
9-
// accessible name, and the line under it. A site with something to report
10-
// (#94) wears a dot before its name, and says what the dot means in words
11-
// after it, so that the name is what a screen reader says first. A site on
12-
// its way out says so the same way, since pressing its entry opens nothing.
13-
// Built on first render and not as the module loads, which is before the
14-
// locale has.
10+
// accessible name, and the line under it. Every site wears its server's dot
11+
// before its name, the one the page header draws for the open site, so that
12+
// which site is running can be seen without opening each: grey until the
13+
// view has said otherwise. A site with something to report (#94) wears a
14+
// second dot after it. Both say what they mean in words after the name, so
15+
// that the name is what a screen reader says first, and the server's only
16+
// when it is not the grey every site starts with. A site on its way out says
17+
// so the same way, since pressing its entry opens nothing. Built on first
18+
// render and not as the module loads, which is before the locale has.
1519
const buildFields = () => [
1620
{
1721
id: 'name',
@@ -20,8 +24,17 @@ const buildFields = () => [
2024
enableHiding: false,
2125
render: ({ item }) => (
2226
<span className="sites-sidebar-name">
27+
<ProcessStatus status={item.server.status} title={item.server.text || undefined} />
2328
{item.attention ? <span className={`sites-sidebar-dot is-${item.attention.kind}`} title={item.attention.text} aria-hidden="true" /> : null}
2429
<span className="sites-sidebar-name-text">{item.name}</span>
30+
{item.server.text ? (
31+
<VisuallyHidden render={<span />}>
32+
{
33+
// translators: %s: what a site's server is doing, such as "Server running". It follows the site's name.
34+
sprintf(__('(%s)'), item.server.text)
35+
}
36+
</VisuallyHidden>
37+
) : null}
2538
{item.attention ? (
2639
<VisuallyHidden render={<span />}>
2740
{

‎src/renderer/index.jsx‎

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import { toggleTray, trayAfterReveal, trayList } from './tray.cjs';
2525
import { formatElapsed, watchTabLabel } from './dev-server-command.cjs';
2626
import { watchBusyMessage, appliedBannerState } from './watch-activity.cjs';
2727
import { pathBasename } from './path-basename.cjs';
28-
import { sitesListRows, siteToOpen } from './sites-list.cjs';
28+
import { serverReport, sitesListRows, siteToOpen, withServerReport } from './sites-list.cjs';
2929
import { deleteSiteQuestion } from './site-dialogs.cjs';
3030
import { serverProcess, watchProcess, serverSection } from './site-processes.cjs';
3131
import { applyLocale, textDirection } from './locale-setup.cjs';
@@ -476,6 +476,17 @@ function App({ settingsState }) {
476476
setSiteMeta((m) => ({ ...(m || {}), [sitePath]: { ...(m?.[sitePath] || {}), ...patch } }));
477477
}, [setSiteMeta]);
478478

479+
// What each site's view last said of its server, by path, for the dot the
480+
// sites list draws before every name. A server lives in its site's view,
481+
// open or not, so the view is the one thing that knows, and it reports
482+
// each change here; a report of null, from a view on its way out, takes
483+
// the site's entry back. What a report does to the reports is decided in
484+
// sites-list.cjs.
485+
const [serverReports, setServerReports] = useState({});
486+
const onServerStatus = useCallback((sitePath, report) => {
487+
setServerReports((current) => withServerReport(current, sitePath, report));
488+
}, []);
489+
479490
const onDelete = useCallback(async (sitePath) => {
480491
if (deletingSitesRef.current.has(sitePath)) return;
481492
deletingSitesRef.current.add(sitePath);
@@ -531,8 +542,8 @@ function App({ settingsState }) {
531542
// The list reports its selection; which site that opens is decided in
532543
// sites-list.cjs.
533544
const sitesRows = useMemo(
534-
() => sitesListRows({ sites: sortedSites, siteMeta, deleting: deletingSites }),
535-
[sortedSites, siteMeta, deletingSites]
545+
() => sitesListRows({ sites: sortedSites, siteMeta, deleting: deletingSites, servers: serverReports }),
546+
[sortedSites, siteMeta, deletingSites, serverReports]
536547
);
537548
const openRow = sitesRows.find((row) => row.path === activeSite) || null;
538549
const handleChangeSelection = useCallback((selection) => {
@@ -705,6 +716,7 @@ function App({ settingsState }) {
705716
resume={resumeFor(resume, s)}
706717
onInitialized={onInitialized}
707718
onSiteMetaPatch={onSiteMetaPatch}
719+
onServerStatus={onServerStatus}
708720
onDelete={onDelete}
709721
onRename={onRename}
710722
onCreateSite={() => setCreateModalOpen(true)}
@@ -758,7 +770,7 @@ function App({ settingsState }) {
758770
);
759771
}
760772

761-
function SiteRow({ sitePath, initialized, createdAt, label, projectType = null, settings = null, startingPhp = null, resume = null, onInitialized, onSiteMetaPatch, onDelete, onRename, onCreateSite, editor, wporg, isPending = false, isDeleting = false, setupLogs = '', isActive = false, switchProgress = null, carriedWork = null, onClearSwitchNotices = null, deepLink = null, onDeepLinkDone = null, detailsOpen = true, onToggleDetails = null, tray = null, onShowTray = null }) {
773+
function SiteRow({ sitePath, initialized, createdAt, label, projectType = null, settings = null, startingPhp = null, resume = null, onInitialized, onSiteMetaPatch, onServerStatus = null, onDelete, onRename, onCreateSite, editor, wporg, isPending = false, isDeleting = false, setupLogs = '', isActive = false, switchProgress = null, carriedWork = null, onClearSwitchNotices = null, deepLink = null, onDeepLinkDone = null, detailsOpen = true, onToggleDetails = null, tray = null, onShowTray = null }) {
762774
// The window's confirmation queue (#253): confirm(message) after an action
763775
// completes, so the outcome is announced rather than left silent or buried in
764776
// the terminal.
@@ -1300,6 +1312,18 @@ function SiteRow({ sitePath, initialized, createdAt, label, projectType = null,
13001312
const serverState = serverProcess({ active: isDevProcessActive, starting: isServerStarting, isUpdating, failure: serverFailure });
13011313
const watchProcessState = watchProcess({ state: watchState, compiling: watchCompiling, exitCode: watchExitCode, exitOf: watchExitOf, isUpdating, updateWaitingOnWatch, sourceDir: project.cards.sourceDir });
13021314
const serverSectionState = serverSection({ url: serverUrl, running, starting: isServerStarting, elapsed: startElapsed });
1315+
// The sites list draws this server's dot before the site's name whether or
1316+
// not the site is open, and this is how it learns what to draw: what the
1317+
// report says is decided in sites-list.cjs. Reported on each change, and
1318+
// taken back when the view goes: the report before a change is taken back
1319+
// in the same commit as the new one is made, so the list sees one change,
1320+
// not a grey dot in between.
1321+
const { status: serverDotStatus, text: serverDotText } = serverReport(serverState);
1322+
useEffect(() => {
1323+
if (!onServerStatus) return undefined;
1324+
onServerStatus(sitePath, { status: serverDotStatus, text: serverDotText });
1325+
return () => onServerStatus(sitePath, null);
1326+
}, [onServerStatus, sitePath, serverDotStatus, serverDotText]);
13031327
// A link to the running site is opened in the browser by the main process.
13041328
const openSiteLink = (url) => window.api.openExternal(url);
13051329

‎src/renderer/shell.css‎

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -152,28 +152,34 @@ body {
152152
background: transparent;
153153
}
154154

155+
/* One line, the dots then the name, cut with an ellipsis when the name is
156+
too long. The dots are inline boxes and not flex items so that
157+
`vertical-align: middle` can place them: it centres a box on the parent's
158+
x-height, which is where a dot beside lowercase letters belongs, whatever
159+
the font. Centred on the line box instead, as a flex item is, each sat
160+
1.5px above the letters. */
155161
.sites-sidebar-name {
156-
display: flex;
157-
align-items: center;
158-
gap: var(--wpds-dimension-gap-sm);
159-
min-width: 0;
160-
}
161-
162-
.sites-sidebar-name-text {
162+
display: block;
163163
min-width: 0;
164164
overflow: hidden;
165165
text-overflow: ellipsis;
166166
white-space: nowrap;
167167
}
168168

169+
.sites-sidebar-name .process-status,
170+
.sites-sidebar-dot {
171+
display: inline-block;
172+
vertical-align: middle;
173+
margin-inline-end: var(--wpds-dimension-gap-sm);
174+
}
175+
169176
.sites-sidebar-description {
170177
color: var(--wpds-color-foreground-content-neutral-weak);
171178
font-size: var(--wpds-typography-font-size-sm);
172179
}
173180

174181
/* What a site has to report before it is opened (#94). */
175182
.sites-sidebar-dot {
176-
flex: none;
177183
width: 8px;
178184
height: 8px;
179185
border-radius: 50%;
@@ -359,6 +365,24 @@ body {
359365
font-weight: var(--wpds-typography-font-weight-default);
360366
}
361367

368+
/* The dot on the x-height of the words, as in the sites list: an inline box,
369+
not a flex item of the button, which would centre it on the line box. The
370+
space between them is the words' own, so that it goes with the words when
371+
they give way to the dot in a short header, leaving the button's one gap
372+
between the dot and the chevron. */
373+
.process-menu-state {
374+
white-space: nowrap;
375+
}
376+
377+
.process-menu-state .process-status {
378+
display: inline-block;
379+
vertical-align: middle;
380+
}
381+
382+
.process-menu-state .header-wide-only {
383+
margin-inline-start: var(--wpds-dimension-gap-sm);
384+
}
385+
362386
.dashboard-sidebar h3 {
363387
margin: 0;
364388
}

‎src/renderer/sites-list.cjs‎

Lines changed: 69 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@
33
// The list is the one part of the window that describes every site at once,
44
// including the ones that are not open, so everything it says has to come from
55
// what the window keeps for all of them: the label, the project, the trunk's
6-
// date and whether an update was left incomplete. What a site's own view
7-
// knows, such as whether its server is running, is not reachable from here
8-
// yet.
6+
// date and whether an update was left incomplete. What only a site's own view
7+
// knows, whether its server is running, reaches the list the one way it can:
8+
// the view reports it to the window as it changes, and the window hands the
9+
// reports in here by path.
910
'use strict';
1011

1112
const { __, _n, sprintf } = require('@wordpress/i18n');
@@ -38,6 +39,65 @@ function siteAttention(meta = {}, now = Date.now()) {
3839
return null;
3940
}
4041

42+
// The words a server's dot can be, as `serverProcess` in site-processes.cjs
43+
// says them: grey, green, amber and red.
44+
const SERVER_STATUSES = new Set(['offline', 'online', 'busy', 'failed']);
45+
46+
/**
47+
* What a site's server dot reports, from what the site's own view last said
48+
* of its server: the word for the dot's colour, and the words the dot stands
49+
* for, which are said after the site's name. A site whose view has said
50+
* nothing, which every site is until its view has rendered, is offline, and
51+
* so is a report with a word the dot has no colour for. An offline server is
52+
* the state every site opens in and the grey dot says it, so its words are
53+
* '': said after every name, "Server stopped" would be the thing a reader of
54+
* the list hears most and needs least.
55+
*
56+
* @param {{status?: string, text?: string}|null} [report] What the view said last.
57+
* @return {{status: string, text: string}}
58+
*/
59+
function serverDot(report) {
60+
const status = report && SERVER_STATUSES.has(report.status) ? report.status : 'offline';
61+
const text = status === 'offline' ? '' : String((report && report.text) || '');
62+
return { status, text };
63+
}
64+
65+
/**
66+
* What a site's view reports of its server: the word the header's own dot
67+
* is drawn from, and the words to say beside it, which for a server that
68+
* went by itself or could not start are the sentence that says so, since
69+
* "Server stopped" would not say what the red dot means.
70+
*
71+
* @param {{status: string, label: string, detail?: string}} process From `serverProcess` in site-processes.cjs.
72+
* @return {{status: string, text: string}}
73+
*/
74+
function serverReport(process) {
75+
return { status: process.status, text: process.detail || process.label };
76+
}
77+
78+
/**
79+
* The window's reports, by path, after one site has reported: its entry
80+
* replaced, or taken out for a report of null, from a view on its way out.
81+
* The same object comes back when nothing changed, so a report that says
82+
* what the last one said does not re-render the list.
83+
*
84+
* @param {Object} current Reports by path.
85+
* @param {string} sitePath
86+
* @param {{status: string, text: string}|null} report
87+
* @return {Object}
88+
*/
89+
function withServerReport(current, sitePath, report) {
90+
const last = current[sitePath];
91+
if (!report) {
92+
if (!last) return current;
93+
const rest = { ...current };
94+
delete rest[sitePath];
95+
return rest;
96+
}
97+
if (last && last.status === report.status && last.text === report.text) return current;
98+
return { ...current, [sitePath]: report };
99+
}
100+
41101
/**
42102
* A site's id in the list. The list builds element ids from it and points
43103
* `aria-labelledby` at them, and that attribute is a list of ids separated by
@@ -62,10 +122,11 @@ function rowId(sitePath) {
62122
* @param {string[]} root0.sites Site paths, already in display order.
63123
* @param {Object} [root0.siteMeta] Records by path.
64124
* @param {string[]} [root0.deleting] Paths of the sites being deleted.
125+
* @param {Object} [root0.servers] What each site's view last said of its server, by path: `{status, text}`.
65126
* @param {number} [root0.now] The time to measure trunk ages from.
66-
* @return {Array<{id: string, path: string, name: string, project: string, description: string, deleting: boolean, attention: ({kind: string, text: string}|null)}>}
127+
* @return {Array<{id: string, path: string, name: string, project: string, description: string, deleting: boolean, attention: ({kind: string, text: string}|null), server: {status: string, text: string}}>}
67128
*/
68-
function sitesListRows({ sites, siteMeta = {}, deleting = [], now = Date.now() }) {
129+
function sitesListRows({ sites, siteMeta = {}, deleting = [], servers = {}, now = Date.now() }) {
69130
return (sites || []).map((sitePath) => {
70131
const meta = (siteMeta && siteMeta[sitePath]) || {};
71132
const isDeleting = deleting.includes(sitePath);
@@ -77,7 +138,8 @@ function sitesListRows({ sites, siteMeta = {}, deleting = [], now = Date.now() }
77138
project,
78139
description: isDeleting ? __('Deleting site…') : project,
79140
deleting: isDeleting,
80-
attention: siteAttention(meta, now)
141+
attention: siteAttention(meta, now),
142+
server: serverDot(servers && servers[sitePath])
81143
};
82144
});
83145
}
@@ -104,4 +166,4 @@ function siteToOpen({ selection, current, rows }) {
104166
return next ? next.path : current;
105167
}
106168

107-
module.exports = { sitesListRows, siteAttention, siteToOpen, rowId };
169+
module.exports = { sitesListRows, siteAttention, serverDot, serverReport, withServerReport, siteToOpen, rowId };

0 commit comments

Comments
 (0)