🐤 Sync 2026-08-14 17:37:24

This commit is contained in:
github-actions[bot] committed 2026-08-14 17:37:24 +08:00
1 parent dc71ed3a81
commit 71224864ff
85 files changed
+2298 -2242

No files matched your search

@@ -88,10 +88,10 @@ function clearViewIntervals() {
/* ONE line per document whatever went wrong: this runs on every navigation, and a router that
* cannot read L.Poll cannot read it on the next click either — a message per click would bury the
* console the user is reading it in. */
let _pollTimerWarned = false;
let _pollWarned = false;
function warnPollUnreadable(msg) {
if (_pollTimerWarned) return;
_pollTimerWarned = true;
if (_pollWarned) return;
_pollWarned = true;
console.error(msg);
}
@@ -301,11 +301,13 @@ function adoptMarkup(t, head) {
*
* The heading is COPIED, not invented: it is the text of the header cell in the same position, so
* the card says exactly what the column header says. Never overwrites an existing data-title — if
* the app set one, that is the app's answer and it knows more than a positional guess. Cheap enough
* to re-run on every fit pass (it is skipped entirely once the cells carry the attribute), which
* matters because these tables are POLLED: the rows are replaced wholesale every few seconds, and
* the fresh ones arrive without it. */
* the app set one, that is the app's answer and it knows more than a positional guess. Re-run on
* every fit pass, because these tables are POLLED: the rows are replaced wholesale every few
* seconds and the fresh ones arrive without the attribute. What keeps that affordable is the
* PER-ROW skip below: a captioned row is recognised from one cell instead of all of them, so a
* table LuCI captioned itself costs one attribute test per row and nothing else. */
function labelCells(t, head) {
const rows = t.querySelectorAll('.tr, tbody tr');
/* A `<thead>` that was WRITTEN as markup nests a real `<tr>` — the parser inserts one even where
* the author left it out — while one built by E() holds the `<th>`s directly (see above). Reading
* `head.children` blind therefore captioned every cell of a parsed table with the header row's
@@ -315,9 +317,30 @@ function labelCells(t, head) {
const titleRow = (head.firstElementChild && head.firstElementChild.tagName === 'TR') ? head.firstElementChild : head;
const titles = [ ...titleRow.children ].map((c) => (c.textContent || '').trim());
if (!titles.some(Boolean)) return;
for (const row of t.querySelectorAll('.tr, tbody tr')) {
for (const row of rows) {
if (row === head) continue;
const cells = row.children;
/* ---- SKIP A ROW THAT IS ALREADY CAPTIONED, ASKED OF THE ROW AND OF NOTHING ELSE ----
*
* A claimed table is revisited on every fit pass — a MUTATION pass, not a once-a-second one —
* so the common case has to be answered cheaply: everything LuCI renders is captioned by
* `ui.Table` as it builds it, and the walk could never find work there. Testing the row's FIRST
* cell answers that in one attribute read instead of one per cell.
*
* The row, rather than the table, is what carries the answer, and that is the whole point: a
* poll appends or replaces rows one at a time, and a table can hold captioned and bare rows at
* once. A table-level probe has to guess WHICH row speaks for the rest, and every choice is
* wrong for some shape — asking the last row stalls forever on a `<tfoot>` of per-column
* totals, which is captioned once and then never bare again while fresh rows keep arriving in
* the `<tbody>` above it (`t.rows` spans thead, tbody and tfoot, and the query above is in
* document order). Asked per row, there is nothing to guess: a fresh row has no caption and is
* walked, a captioned one is skipped, and cells replaced INSIDE a surviving row arrive bare and
* are walked too.
*
* A first cell that can never take a caption — one that spans columns, or whose header text is
* empty — leaves its row walked on every pass. That is the safe direction and it is bounded by
* the row, not the table: the walk is idempotent and writes nothing it has not been asked to. */
if (cells.length && cells[0].hasAttribute('data-title')) continue;
/* COLUMN cursor, not the cell index: a cell that spans N columns occupies N of the header's
* slots while advancing the cell index by one, so keying titles off `i` captioned every cell
* AFTER a spanning one with the heading of the column to its left — "Hostname" over an IP