diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1905d36..de64b43 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -71,6 +71,22 @@ All notable changes to **basou** are recorded here. The project follows
keeps its milestones, which a start among them did not fit. A moved board
keeps the milestones it had that happened, and leaves out those that were
only planned.
+- **A record's board page draws the reach matrix right after the summary's
+ tiles**, then where each lane is, then the observations outside in a
+ section of their own, then the period and effort and the rest as before.
+ The summary held the observations, and the matrix came after them and
+ after the charts of the effort, about 4,500px down a page 800px wide. The
+ summary keeps its prose and its tiles, and the observations their rows,
+ their notes and their `reported` mark, now on their section's heading. The
+ board measured on the spot, which draws no matrix, keeps its order. The
+ list in `basou board guide` of what the page draws, and what the agent
+ writes for it, follows the new order.
+- **A record's board page folds the components counted in no lane** under
+ one line that says how many they are (`Counted in no lane (123)`), closed
+ until it is opened. A board that registers most of its components with
+ `lane: "-"` had them fill about a third of the page. When there is none,
+ the page says so as before; the components of each lane, those found and
+ not registered, and those gone are drawn open as before.
### Fixed
@@ -89,6 +105,14 @@ All notable changes to **basou** are recorded here. The project follows
as the command quotes it, and a plain name (`owner/repo`, `quality.yml`,
`main`) is written as before, so the sources of existing boards change
only where a name needed quoting.
+- **The board page's reach matrix fits a narrow screen.** On a screen 600px
+ wide or less, each stage's head shows its id alone, the stages' meanings
+ are listed under the matrix, the cells take less room, and a long lane name
+ wraps, so that the six stages and a lane name of 16 characters fit a phone
+ 375px wide. The matrix was 479px wide in the 343px left for it there, with
+ stages 05 and 06 out of sight to the right. Each stage's head also carries
+ its meaning as its title at every width, which a screen reader reads once
+ and a pointer that hovers shows. A wider screen draws the matrix as before.
## 0.72.0 — 2026-10-20
diff --git a/packages/cli/src/commands/view.test.ts b/packages/cli/src/commands/view.test.ts
index d5ce45c..7900322 100644
--- a/packages/cli/src/commands/view.test.ts
+++ b/packages/cli/src/commands/view.test.ts
@@ -1420,6 +1420,7 @@ describe("basou view: the board page", () => {
"setStatus",
"heading",
"summary",
+ "observations",
"effort",
"matrix",
"lanes",
@@ -1462,12 +1463,15 @@ describe("basou view: the board page", () => {
const words = boardPageStrings("en").effort;
call("draw", board);
expect(document.title).toBe("Board");
- // Every section of a record, the repos and the components it measured among them.
+ // Every section of a record, the repos and the components it measured
+ // among them: the matrix and the lanes right after the summary, then the
+ // observations in a section of their own, then the effort.
expect(byId.board?.find((n) => n.tag === "h2").map((n) => n.textContent)).toEqual([
"Summary",
- "Period and effort",
"Reach matrix",
"Where each lane is",
+ "Observed outside",
+ "Period and effort",
"Measures",
"Composition",
"The operator's turns",
@@ -1539,10 +1543,52 @@ describe("basou view: the board page", () => {
expect(none.map((o) => [o.attrs.value, o.disabled])).toEqual([["", true]]);
});
+ it("draws the summary's prose and tiles, and the observations apart from them, each marked as reported", () => {
+ const { call } = drawing(["summary", "observations"]);
+ const S = boardPageStrings("en");
+ const made = { observed_at: "2026-10-07T00:00:00Z", source: "s" };
+ const board = {
+ summary: {
+ text: "All `well`.",
+ tiles: [{ key: "live_lanes", value: 2, detail: 3, reported: true }],
+ previous_record: "unreadable",
+ observed: [
+ { name: "npm", value: "1.2.3", notes: ["mind the `tag`"], ...made },
+ { name: "site", value: null, error: "timeout", ...made },
+ ],
+ },
+ };
+ const summary = call("summary", board);
+ expect(summary.children[0]?.textContent).toBe(S.sections.summary);
+ expect(summary.find((n) => n.className === "tile")).toHaveLength(1);
+ // The prose's mark and the tile's: no observation, no heading of them.
+ expect(summary.find((n) => n.className === "reported")).toHaveLength(2);
+ expect(summary.find((n) => n.tag === "table" || n.tag === "h3")).toEqual([]);
+ expect(summary.text()).not.toContain(S.observed.heading);
+ expect(summary.text()).not.toContain(S.observed.previousUnreadable);
+ const observations = call("observations", board);
+ const [head] = observations.children;
+ expect([head?.tag, head?.textContent]).toEqual(["h2", S.observed.heading]);
+ expect(head?.find((n) => n.className === "reported")).toHaveLength(1);
+ // Its rows and the notes under them, and the line on the previous record.
+ const rows = observations.find((n) => n.tag === "tr").slice(1);
+ expect(rows.map((r) => [r.className, r.children[0]?.text()])).toEqual([
+ ["", "npm"],
+ ["notes", "mind the tag"],
+ ["", "site"],
+ ]);
+ expect(observations.text()).toContain(S.observed.previousUnreadable);
+ expect(observations.find((n) => n.className === "tile")).toEqual([]);
+ expect(observations.text()).not.toContain("All ");
+ const none = call("observations", { summary: { ...board.summary, observed: [] } });
+ expect(none.text()).toContain(S.observed.none);
+ expect(none.text()).not.toContain(S.observed.previousUnreadable);
+ });
+
it("draws an http observation's words beside what it found of each, in the order declared", () => {
- const { call } = drawing(["summary"]);
+ const { call } = drawing(["observations"]);
const made = { observed_at: "2026-10-07T00:00:00Z", source: "s" };
- const drawn = call("summary", {
+ const drawn = call("observations", {
summary: {
text: "",
tiles: [],
@@ -1596,11 +1642,11 @@ describe("basou view: the board page", () => {
});
it("draws an http value in its shape as its parts, what each observation is and looks at, and its notes", () => {
- const { call } = drawing(["summary"]);
+ const { call } = drawing(["observations"]);
const DOT = ` ${String.fromCodePoint(0xb7)} `;
const [YES, NO] = [String.fromCodePoint(0x2713), String.fromCodePoint(0x2717)];
const made = { observed_at: "2026-10-07T00:00:00Z", source: "https://example.com/x" };
- const drawn = call("summary", {
+ const drawn = call("observations", {
summary: {
text: "",
tiles: [],
@@ -1687,7 +1733,7 @@ describe("basou view: the board page", () => {
});
it("marks an observation that moved since the previous record, with what that record held", () => {
- const { call } = drawing(["summary"]);
+ const { call } = drawing(["observations"]);
const DOT = ` ${String.fromCodePoint(0xb7)} `;
const [YES, NO] = [String.fromCodePoint(0x2713), String.fromCodePoint(0x2717)];
const made = { observed_at: "2026-10-07T00:00:00Z", source: "s" };
@@ -1697,9 +1743,9 @@ describe("basou view: the board page", () => {
final_url: "https://example.com/",
body: { words: [{ word, found }] },
});
- const summary = (previous_record: string, observed: unknown[]) =>
- call("summary", { summary: { text: "", tiles: [], previous_record, observed } });
- const drawn = summary("found", [
+ const observations = (previous_record: string, observed: unknown[]) =>
+ call("observations", { summary: { text: "", tiles: [], previous_record, observed } });
+ const drawn = observations("found", [
{ name: "npm", value: "1.1.0", moved: { status: "value", value: "1.0.0" }, ...made },
// Its type alone moved: both as JSON would tell them apart.
{ name: "count", value: 12, moved: { status: "value", value: "12" }, ...made },
@@ -1759,10 +1805,10 @@ describe("basou view: the board page", () => {
expect(drawn.find((n) => n.className === "chip changed")).toHaveLength(8);
const unreadable = boardPageStrings("en").observed.previousUnreadable;
expect(drawn.text()).not.toContain(unreadable);
- expect(summary("unreadable", [{ name: "npm", value: "1", ...made }]).text()).toContain(
+ expect(observations("unreadable", [{ name: "npm", value: "1", ...made }]).text()).toContain(
unreadable,
);
- expect(summary("unreadable", []).text()).not.toContain(unreadable);
+ expect(observations("unreadable", []).text()).not.toContain(unreadable);
});
it("draws every measure in a table, with its lanes and how much a number moved", () => {
@@ -1895,9 +1941,19 @@ describe("basou view: the board page", () => {
gone: ["app/db"],
},
});
- const flow = drawn.children
- .slice(1)
- .map((n) => (n.tag === "ul" ? n.children.map((li) => li.text()) : n.text()));
+ const items = (ul: Node | undefined) => ul?.children.map((li) => li.text());
+ // Those in no lane folded, closed, under how many they are.
+ const flow = drawn.children.slice(1).map((n) =>
+ n.tag === "ul"
+ ? items(n)
+ : n.tag === "details"
+ ? {
+ open: n.attrs.open,
+ summary: [n.children[0]?.tag, n.children[0]?.text()],
+ items: items(n.children[1]),
+ }
+ : n.text(),
+ );
expect(flow).toEqual([
"Core",
[
@@ -1907,13 +1963,31 @@ describe("basou view: the board page", () => {
],
"Docs",
S.record.noComponentInLane,
- S.record.uncounted,
- ["app/scripts markertooling"],
+ {
+ open: undefined,
+ summary: ["summary", "Counted in no lane (1)"],
+ items: ["app/scripts markertooling"],
+ },
S.record.unacknowledged,
["app/new ci"],
S.record.gone,
["app/db"],
]);
+ // Only they are folded.
+ expect(drawn.find((n) => n.tag === "details")).toHaveLength(1);
+ const many = call("recordComponents", {
+ components: {
+ measured: true,
+ reasons: [],
+ lanes: [],
+ uncounted: Array.from({ length: 1234 }, (_, i) => ({ key: `c${i}`, kinds: ["marker"] })),
+ unacknowledged: [],
+ gone: [],
+ },
+ });
+ const [folded] = many.find((n) => n.tag === "details");
+ expect(folded?.children[0]?.text()).toBe("Counted in no lane (1,234)");
+ expect(folded?.find((n) => n.tag === "li")).toHaveLength(1234);
const notMeasured = call("recordComponents", {
components: {
measured: false,
@@ -1937,11 +2011,95 @@ describe("basou view: the board page", () => {
},
});
expect(unknown.text()).toContain(S.record.goneUnknown);
+ // None in no lane: said under its heading, with nothing to fold.
+ expect(unknown.find((n) => n.tag === "details")).toEqual([]);
+ expect(unknown.children.slice(1, 3).map((n) => [n.tag, n.text()])).toEqual([
+ ["h3", S.record.uncounted],
+ ["p", S.record.none],
+ ]);
expect(call("recordComponents", { components: null }).text()).toContain(
S.record.componentsUnread,
);
});
+ it("puts each stage's meaning in its head's title, and draws a stage by its id alone on a narrow screen, with the meanings listed under the matrix", () => {
+ const { call } = drawing(["matrix"]);
+ const drawn = call("matrix", {
+ matrix: {
+ anomalies: [],
+ stages: [
+ { id: "01", meaning: "Concept" },
+ { id: "02", meaning: "Spec x" },
+ ],
+ lanes: [{ id: "core", name: "Core", cells: [{ state: "done" }, { state: "none" }] }],
+ },
+ });
+ const stages = drawn.find((n) => n.tag === "th" && n.className === "stage");
+ expect(stages.map((th) => th.attrs.title)).toEqual(["Concept", "Spec x"]);
+ // The id, and the meaning in an element of its own, which a narrow screen hides.
+ expect(stages.map((th) => th.children.map((c) => [c.tag, c.className, c.text()]))).toEqual([
+ [
+ ["b", "", "01"],
+ ["span", "meaning", "Concept"],
+ ],
+ [
+ ["b", "", "02"],
+ ["span", "meaning", "Spec x"],
+ ],
+ ]);
+ // A screen reader reads the meaning once, as the title, not again as the text.
+ expect(stages.map((th) => th.children[1]?.attrs["aria-hidden"])).toEqual(["true", "true"]);
+ // The meanings listed under the matrix, as text.
+ const [listed] = drawn.find((n) => n.tag === "ul" && n.className === "stage-meanings");
+ expect(listed?.children.map((li) => li.text())).toEqual(["01 Concept", "02 Spec x"]);
+ // The page's style, without its comments, and the rules of a part of it.
+ const style = BOARD_HTML.slice(
+ BOARD_HTML.indexOf(""),
+ ).replace(/\/\*[\s\S]*?\*\//g, "");
+ const rules = (css: string) =>
+ Object.fromEntries(
+ [...css.matchAll(/([^{}]+)\{([^{}]*)\}/g)].map((m) => [m[1]?.trim(), m[2]?.trim()]),
+ );
+ const opener = "@media (max-width: 600px) {";
+ const start = style.indexOf(opener);
+ expect(start).toBeGreaterThan(-1);
+ const end = style.indexOf("\n }", start) + "\n }".length;
+ const narrow = rules(style.slice(start + opener.length, end - 1));
+ expect(narrow).toEqual({
+ "table.matrix th.stage": "min-width: 0; padding: 4px 2px;",
+ "table.matrix th.stage .meaning": "display: none;",
+ "table.matrix td.cell": "font-size: 16px; padding: 4px 2px;",
+ // A long lane name wraps, in a column of a width it cannot pass.
+ "table.matrix th:first-child": "max-width: 10em; overflow-wrap: anywhere;",
+ ".stage-meanings": "display: block;",
+ });
+ // Its rules take the place of those of the same weight only by coming after them.
+ for (const selector of [
+ "table.matrix th.stage {",
+ "table.matrix td.cell {",
+ ".stage-meanings {",
+ ]) {
+ const at = style.indexOf(selector);
+ expect(at, selector).toBeGreaterThan(-1);
+ expect(at, selector).toBeLessThan(start);
+ }
+ expect(style.indexOf("table.matrix", end)).toBe(-1);
+ expect(style.indexOf(".stage-meanings", end)).toBe(-1);
+ // Wider screens draw the matrix as before: its meanings in the heads, no list.
+ const wide = rules(style.slice(0, start) + style.slice(end));
+ expect(wide["table.matrix th.stage"]).toContain("min-width: 64px;");
+ expect(wide["table.matrix td.cell"]).toContain("font-size: 18px;");
+ expect(Object.keys(wide).filter((selector) => selector.includes("meaning"))).toEqual([
+ ".stage-meanings",
+ ]);
+ expect(wide[".stage-meanings"]).toContain("display: none;");
+ for (const [selector, body] of Object.entries(wide)) {
+ if (selector.includes("th.stage")) expect(body, selector).not.toContain("display: none");
+ }
+ expect(wide[".scroll"]).toContain("overflow-x: auto;");
+ });
+
it("draws the paths of a file_paths measure as a count, with the paths folded under it", () => {
const { call } = drawing(["lanes"]);
const drawn = call("lanes", {
diff --git a/packages/cli/src/lib/board-ui.ts b/packages/cli/src/lib/board-ui.ts
index c8b7372..7f99b4b 100644
--- a/packages/cli/src/lib/board-ui.ts
+++ b/packages/cli/src/lib/board-ui.ts
@@ -69,6 +69,17 @@ export const BOARD_HTML = `
table.matrix th.stage { font-weight: 400; font-size: 12px; text-align: center; min-width: 64px; }
table.matrix th.stage b { display: block; font-size: 13px; }
table.matrix td.cell { text-align: center; font-size: 18px; line-height: 1; }
+ .stage-meanings { display: none; list-style: none; padding: 0; margin: 6px 0 0; font-size: 12px; color: var(--fg-2); }
+ /* Six stages do not fit beside their meanings below about 600px: each is
+ drawn by its id, the meanings are listed under the matrix, and a lane
+ name wraps. After the rules above, which it overrides. */
+ @media (max-width: 600px) {
+ table.matrix th.stage { min-width: 0; padding: 4px 2px; }
+ table.matrix th.stage .meaning { display: none; }
+ table.matrix td.cell { font-size: 16px; padding: 4px 2px; }
+ table.matrix th:first-child { max-width: 10em; overflow-wrap: anywhere; }
+ .stage-meanings { display: block; }
+ }
.st::before { display: inline-block; width: 1.2em; }
.st.done::before { content: "\\25CF"; color: var(--done); }
.st.part::before { content: "\\25D0"; color: var(--done); }
@@ -357,6 +368,13 @@ export const BOARD_HTML = `
k, el('div', { class: 'v', text: num(t.value) }), sub === null ? null : el('div', { class: 's', text: sub })
]));
});
+ return section(S.sections.summary, false, [el('div', null, [prose(s.text), reported()]), tiles]);
+ }
+
+ // The observations outside, which the record holds in its summary, in a
+ // section of their own after the lanes.
+ function observations(b) {
+ var s = b.summary;
var observed;
if (s.observed.length === 0) {
observed = el('p', { class: 'muted', text: S.observed.none });
@@ -399,17 +417,11 @@ export const BOARD_HTML = `
])
].concat(rows))]);
}
- var observedHeading = el('h3', { text: S.observed.heading }, [reported()]);
- var children = [
- el('div', null, [prose(s.text), reported()]),
- tiles,
- observedHeading,
- observed
- ];
+ var children = [observed];
if (s.previous_record === 'unreadable' && s.observed.length > 0) {
children.push(el('p', { class: 'muted', text: S.observed.previousUnreadable }));
}
- return section(S.sections.summary, false, children);
+ return section(S.observed.heading, true, children);
}
var SVGNS = 'http://www.w3.org/2000/svg';
@@ -696,8 +708,13 @@ export const BOARD_HTML = `
})
)));
}
+ // The meaning is the head's title too, as a screen reader reads it and a
+ // pointer that hovers shows it, and the text beside the id is left out of
+ // what a screen reader reads, so that it is read once.
var head = el('tr', null, [el('th', { text: S.matrix.lane })].concat(x.stages.map(function (s) {
- return el('th', { class: 'stage' }, [el('b', { text: s.id }), s.meaning]);
+ return el('th', { class: 'stage', title: s.meaning }, [
+ el('b', { text: s.id }), el('span', { class: 'meaning', 'aria-hidden': 'true', text: s.meaning })
+ ]);
})));
var rows = x.lanes.map(function (lane) {
return el('tr', null, [el('th', { text: lane.name })].concat(lane.cells.map(function (c) {
@@ -709,6 +726,11 @@ export const BOARD_HTML = `
})));
});
children.push(el('div', { class: 'scroll' }, [el('table', { class: 'matrix' }, [head].concat(rows))]));
+ // What each stage means, drawn where a narrow screen gives a stage its id
+ // alone (and hidden where the heads say it).
+ children.push(el('ul', { class: 'stage-meanings' }, x.stages.map(function (s) {
+ return el('li', null, [el('b', { text: s.id }), ' ' + s.meaning]);
+ })));
var legend = el('div', { class: 'legend' });
['done', 'part', 'blocked', 'shelved', 'none', 'unverified'].forEach(function (s) {
legend.appendChild(el('span', null, [el('span', { class: 'st ' + s }), stateName(s)]));
@@ -864,8 +886,10 @@ export const BOARD_HTML = `
var b = page.board;
// The board's own title, so that boards open in other tabs can be told apart.
document.title = b.heading.title;
- [heading(b), summary(b), effort(b, false), matrix(b), lanes(b), measures(b), composition(b), turns(b),
- recordRepos(b), recordComponents(b), footnotes(b)]
+ // Where each lane stops, right under the summary's tiles, and the lanes,
+ // before the observations and the charts of the effort.
+ [heading(b), summary(b), matrix(b), lanes(b), observations(b), effort(b, false), measures(b), composition(b),
+ turns(b), recordRepos(b), recordComponents(b), footnotes(b)]
.forEach(function (node) { root.appendChild(node); });
}
@@ -936,7 +960,7 @@ export const BOARD_HTML = `
return section(S.live.repos, false, children);
}
// The components the record found, by the lanes its registry counts them
- // in, then those in no lane, those not registered, and those gone.
+ // in, then those in no lane (folded), those not registered, and those gone.
function recordComponents(b) {
var c = b.components;
if (!c) return section(S.live.components, false, [el('p', { class: 'muted', text: S.record.componentsUnread })]);
@@ -962,7 +986,15 @@ export const BOARD_HTML = `
c.lanes.forEach(function (lane) {
children = children.concat(group(lane.name, lane.components, S.record.noComponentInLane));
});
- children = children.concat(group(S.record.uncounted, c.uncounted, S.record.none));
+ // Those in no lane can be most of them: folded under how many they are.
+ if (c.uncounted.length > 0) {
+ children.push(el('details', null, [
+ el('summary', { text: fill(S.record.uncountedFolded, { n: num(c.uncounted.length) }) }),
+ el('ul', null, c.uncounted.map(item))
+ ]));
+ } else {
+ children = children.concat(group(S.record.uncounted, c.uncounted, S.record.none));
+ }
children = children.concat(group(S.record.unacknowledged, c.unacknowledged, S.record.none));
children.push(el('h3', { text: S.record.gone }));
children.push(c.gone === null ? el('p', { class: 'muted', text: S.record.goneUnknown })
diff --git a/packages/core/src/board/guide.test.ts b/packages/core/src/board/guide.test.ts
index 6f7cd2c..ea24c0a 100644
--- a/packages/core/src/board/guide.test.ts
+++ b/packages/core/src/board/guide.test.ts
@@ -1590,6 +1590,36 @@ describe("boardGuide: a github_check observation and an observation's notes", ()
}
});
+ it("lists what the board page draws in the order a record's page draws it", () => {
+ const guide = boardGuide(
+ guideInput({
+ board: declared(httpDeclaration([{ key: "shop", kind: "http", url: SHOP }])),
+ }),
+ );
+ const list = guide.slice(
+ guide.indexOf("## What the board page draws, and what you write"),
+ guide.indexOf("## Do not"),
+ );
+ const items = list
+ .split("\n")
+ .filter((line) => /^\d+\. /.test(line))
+ .map((line) => line.slice(0, line.indexOf(":")));
+ expect(items).toEqual([
+ "1. Heading",
+ "2. Summary",
+ "3. Reach matrix",
+ "4. Each lane",
+ "5. Observed outside",
+ "6. Period and effort",
+ "7. Ratios",
+ "8. Operator's turns",
+ "9. Footnotes",
+ ]);
+ // The summary draws its tiles; the observations are a section of their own.
+ expect(list).toContain("basou draws the tiles.\n");
+ expect(list).not.toContain("table of observations");
+ });
+
it("prints each observation's notes under how it is made, and nothing for one without", () => {
const guide = boardGuide(
guideInput({
diff --git a/packages/core/src/board/guide.ts b/packages/core/src/board/guide.ts
index 4d4c6f7..14f19b3 100644
--- a/packages/core/src/board/guide.ts
+++ b/packages/core/src/board/guide.ts
@@ -335,13 +335,14 @@ export function boardGuide(input: BoardGuideInput): string {
"## What the board page draws, and what you write",
"",
"1. Heading: basou draws it (title, when recorded, the model, whether everything was measured).",
- "2. Summary: `prose.summary`, what matters this time and what moved since the last record. basou draws the tiles and the table of observations.",
- "3. Period and effort: basou draws it. Write nothing that gives the numbers a meaning.",
- "4. Reach matrix: your `cells`. basou draws the legend, the order anomalies and the cells that moved.",
- "5. Each lane: `prose.lanes`. For a stuck lane, one sentence on what would move it, with the measured values it rests on.",
- "6. Ratios: basou draws them.",
- "7. Operator's turns: `prose.operator_turns`, the items waiting for a ruling (not for implementation), the ones that move most first, each with its source (a decision or task id, a pull request) in `source`. This is what is read most: what the operator can do to move the board.",
- "8. Footnotes: `prose.footnotes`, what was not confirmed, what is estimated, whether the trail was refreshed, a build that is not the repo's main. basou adds the axis, the model, what was not measured and which basou measured.",
+ "2. Summary: `prose.summary`, what matters this time and what moved since the last record. basou draws the tiles.",
+ "3. Reach matrix: your `cells`. basou draws the legend, the order anomalies and the cells that moved.",
+ "4. Each lane: `prose.lanes`. For a stuck lane, one sentence on what would move it, with the measured values it rests on.",
+ "5. Observed outside: your `observed`. basou draws each value as its kind reads, with what it looks at, its notes and whether it moved since the last record.",
+ "6. Period and effort: basou draws it. Write nothing that gives the numbers a meaning.",
+ "7. Ratios: basou draws them.",
+ "8. Operator's turns: `prose.operator_turns`, the items waiting for a ruling (not for implementation), the ones that move most first, each with its source (a decision or task id, a pull request) in `source`. This is what is read most: what the operator can do to move the board.",
+ "9. Footnotes: `prose.footnotes`, what was not confirmed, what is estimated, whether the trail was refreshed, a build that is not the repo's main. basou adds the axis, the model, what was not measured and which basou measured.",
"",
"## Do not",
"",
diff --git a/packages/core/src/lib/view-strings.ts b/packages/core/src/lib/view-strings.ts
index 44e7e9b..6e6af60 100644
--- a/packages/core/src/lib/view-strings.ts
+++ b/packages/core/src/lib/view-strings.ts
@@ -962,6 +962,8 @@ export type BoardPageStrings = {
componentsNotMeasured: string;
noComponentInLane: string;
uncounted: string;
+ /** The line the components in no lane are folded under, with `{n}` of them. */
+ uncountedFolded: string;
unacknowledged: string;
gone: string;
goneUnknown: string;
@@ -1172,6 +1174,7 @@ const BOARD_PAGE_EN: BoardPageStrings = {
componentsNotMeasured: "The components were not measured:",
noComponentInLane: "No component is registered in this lane.",
uncounted: "Counted in no lane",
+ uncountedFolded: "Counted in no lane ({n})",
unacknowledged: "Found, not registered",
gone: "Registered or found before, not found now",
goneUnknown: "not known (the previous record could not be read)",
@@ -1383,6 +1386,7 @@ const BOARD_PAGE_JA: BoardPageStrings = {
componentsNotMeasured: "構成要素は測れなかった:",
noComponentInLane: "このレーンに登録した構成要素はない。",
uncounted: "どのレーンにも数えない",
+ uncountedFolded: "どのレーンにも数えない({n})",
unacknowledged: "見つかったが登録していない",
gone: "登録していた・前回見つかったが、今回見つからない",
goneUnknown: "分からない(前回の記録が読めない)",