Put every test group on the same column grid

The subsystems are separate tables, so each sized its own columns from
its own content and no two lined up. Reading down the page meant reading
down six different layouts, which is what made it look busy. They now
share one colgroup and a fixed table layout: Kind, Status, Last result
and the Start button are the same width in every group, and the Test
column takes the remainder.

Two things had to come out of the columns first, because both are long
enough to stretch a cell and drag one group's grid out of step with the
rest. The details block moves to a full-width row of its own, where the
prose and the operator steps have room and opening one no longer widens
the Test column. The requires note moves from under the Start button to
under Status, which is sized for it: the catalog's longest names three
prerequisites in 73 characters, and beneath a button it wrapped into a
ragged stack. It reads better there anyway, next to the reason the test
is required rather than beneath the control it disables.

Also gives a test's description its own block in the details. Without
operator steps to separate them it ran straight into the Requires line.
This commit is contained in:
ScottW514
2026-08-21 10:44:46 -04:00
parent 0258268aae
commit e810d52c9d
2 changed files with 68 additions and 7 deletions
+33 -7
View File
@@ -43,6 +43,28 @@ table{width:100%;border-collapse:collapse}
th{font-size:11.5px;color:var(--dim);text-align:left;font-weight:600;padding:6px 8px;border-bottom:1px solid var(--line)}
td{padding:7px 8px;border-bottom:1px solid var(--line);vertical-align:top}
tr:last-child td{border-bottom:0}
/* One grid for every subsystem. The groups are separate tables, so
left to themselves each sizes its own columns from its own content
and no two line up. Fixed widths put them all on the same rails. */
#groups .grp{overflow-x:auto}
#groups table{table-layout:fixed;min-width:700px}
#groups col.k{width:132px}
#groups col.s{width:250px}
#groups col.l{width:210px}
#groups col.a{width:96px}
/* Why a test stands where it does reads as one block under Status. It
used to sit under the Start button, where a requires list of three
test ids wrapped into a ragged stack and dragged that group's columns
out of line with every other group's. */
#groups .req{overflow-wrap:break-word}
/* The details sit in a full-width row of their own: inside the Test
cell they would have to be read through a column, and opening one
would push that group's grid out of step with the rest. */
#groups td{border-bottom:0}
#groups tr.detrow td{padding:0 8px;border-bottom:1px solid var(--line)}
#groups tr.detrow:last-child td{border-bottom:0}
#groups .details{margin:2px 0 8px}
#groups .details .dsc{margin-bottom:6px}
.tid{color:var(--dim);font-size:11.5px;font-family:ui-monospace,Consolas,monospace}
.badge{display:inline-block;font-size:10.5px;padding:2px 6px;border-radius:9px;margin-right:4px;background:#e8e9ec;color:#444;font-weight:600;letter-spacing:.2px;text-transform:uppercase}
.badge.live{background:#fbe1e1;color:#a11}
@@ -257,21 +279,25 @@ function renderGroups(){var ids=[];catalog.forEach(function(t){ids.push(t.id)});
var sig=catalogHash+'|'+ids.join(',');
if(sig!==groupsKey){groupsKey=sig;buildGroups()}
updateGroups()}
var COLS="<colgroup><col><col class='k'><col class='s'><col class='l'><col class='a'></colgroup>";
function buildGroups(){var groups={},order=[];
catalog.forEach(function(t){if(!groups[t.subsystem]){groups[t.subsystem]=[];order.push(t.subsystem)}groups[t.subsystem].push(t)});
var h='';
order.forEach(function(g){h+="<div class='card grp'><h2>"+esc(g)+"</h2><table><tr><th>Test</th><th>Kind</th><th>Status</th><th>Last result</th><th></th></tr>";
order.forEach(function(g){h+="<div class='card grp'><h2>"+esc(g)+"</h2><table>"+COLS+"<tr><th>Test</th><th>Kind</th><th>Status</th><th>Last result</th><th></th></tr>";
groups[g].forEach(function(t){var d=esc(t.id);var badges='';
if(t.always)badges+="<span class='badge core'>core</span>";badges+="<span class='badge "+esc(t.kind)+"'>"+esc(t.kind)+"</span>";
if(t.hardware==='takeover')badges+="<span class='badge takeover'>takeover</span>";
var det="<div class='details"+(openDetails[t.id]?' on':'')+"' id='det-"+d+"'>"+esc(t.description||'')+
(t.steps&&t.steps.length?"<br><b>Operator steps:</b><ol>"+t.steps.map(function(x){return '<li>'+esc(x)+'</li>'}).join('')+"</ol>":'')+
var det="<div class='details"+(openDetails[t.id]?' on':'')+"' id='det-"+d+"'>"+
(t.description?("<div class='dsc'>"+esc(t.description)+"</div>"):'')+
(t.steps&&t.steps.length?"<b>Operator steps:</b><ol>"+t.steps.map(function(x){return '<li>'+esc(x)+'</li>'}).join('')+"</ol>":'')+
"<b>Requires:</b> "+esc((t.requires||[]).join(', ')||'-')+"<br><b>Covers:</b> "+esc((t.covers||[]).map(function(c){return c[0]+':'+c[1]}).join(', ')||'-')+
"<br><span id='detdyn-"+d+"'></span></div>";
h+="<tr><td><div>"+esc(t.title)+"</div><div class='tid'>"+d+" <a href='#' onclick='toggleDet(\""+d+"\");return false'>details</a></div>"+det+
"</td><td>"+badges+"</td><td id='st-"+d+"'></td><td id='last-"+d+"'></td>"+
"<td><button class='pri' id='btn-"+d+"' onclick='startTest(\""+d+"\")'>Start</button>"+
"<div id='unmet-"+d+"'></div><div id='note-"+d+"'></div></td></tr>"});
h+="<tr><td><div>"+esc(t.title)+"</div><div class='tid'>"+d+" <a href='#' onclick='toggleDet(\""+d+"\");return false'>details</a></div>"+
"</td><td>"+badges+"</td>"+
"<td><div id='st-"+d+"'></div><div id='unmet-"+d+"'></div><div id='note-"+d+"'></div></td>"+
"<td id='last-"+d+"'></td>"+
"<td><button class='pri' id='btn-"+d+"' onclick='startTest(\""+d+"\")'>Start</button></td></tr>"+
"<tr class='detrow'><td colspan='5'>"+det+"</td></tr>"});
h+="</table></div>"});
$('groups').innerHTML=h;rowEls={};
catalog.forEach(function(t){rowEls[t.id]={st:$('st-'+t.id),last:$('last-'+t.id),btn:$('btn-'+t.id),
+35
View File
@@ -18,6 +18,7 @@ holds the shape of that rule.
"""
import json
import os
import re
import shutil
import tempfile
import unittest
@@ -191,6 +192,40 @@ class PageTests(unittest.TestCase):
self.assertNotIn("id='" + bid + "-", html)
self.assertIn("function renderQueue()", html)
def test_every_group_is_built_on_the_same_grid(self):
"""The subsystems are separate tables. Left to size themselves
from their own content no two line up, which is what made the
page look busy, so they share one colgroup and a fixed layout."""
html = page.render("0" * 32)
self.assertIn("#groups table{table-layout:fixed", html)
# one colgroup definition, emitted into every group's table
self.assertEqual(html.count('var COLS="<colgroup>'), 1)
self.assertEqual(html.count('<table>"+COLS+"'), 1,
"a group table built without the shared colgroup")
colgroup = re.search(r'var COLS="(.*?)";', html).group(1)
heads = ["Test", "Kind", "Status", "Last result"]
for h in heads:
self.assertIn("<th>%s</th>" % h, html)
self.assertEqual(len(re.findall(r"<col(?:>| )", colgroup)), len(heads) + 1,
"a column per header, the action column included")
# every fixed column carries a width, or the grid is only a wish
for cls in re.findall(r"<col class='(\w)'", colgroup):
self.assertRegex(html, r"#groups col\.%s\{width:\d+px\}" % cls)
def test_details_and_the_requires_note_stay_out_of_the_columns(self):
"""Both are long enough to stretch a cell. In a column they would
pull one group's grid out of step with the rest: the details get a
full-width row, and the note sits under Status, which is sized for
it."""
html = page.render("0" * 32)
self.assertIn("<tr class='detrow'><td colspan='5'>", html)
# the status cell carries the status, the unmet note and the row
# message; the action cell carries the button and nothing else
self.assertIn("<td><div id='st-\"+d+\"'></div><div id='unmet-\"+d+"
"\"'></div><div id='note-\"+d+\"'></div></td>", html)
self.assertIn("<td><button class='pri' id='btn-\"+d+\"' "
"onclick='startTest(\\\"\"+d+\"\\\")'>Start</button></td>", html)
def test_page_is_self_contained_ascii(self):
html = page.render("0" * 32)
self.assertNotIn("__TOKEN__", html)