htranx commited on
Commit
a019dfb
·
verified ·
1 Parent(s): 5eb0340

lay the invited tab out as the boards' 3x2 grid, tinted blue

Browse files

Six full-width tables stacked vertically did not match the Validation tab and read as six unrelated tables.

Now the boards' own layout and class names: one .lb-track per track, each holding a 2-up .lb-grid of Small | Large, so the six sub-tracks sit 3 down by 2 across in rounded cards with a scroll container. auto-fit with a 460px floor drops to one card per row on a narrow viewport rather than squeezing two unreadable ones.

Table surfaces are tinted light blue (#f2f8ff, headers #e2eefb) so the tab is distinguishable from the two white boards beside it while keeping the same type, borders and spacing.

19 tests, and four negative controls each biting only its own test: flat tables restored, tint reverted to white, content wrapper removed, documentElement restored. One test imported results.py just to compare class names, which pulled in pandas and failed on any interpreter without it; it reads the source as text now and passes under both interpreters.

Renders 3 track sections, 6 cards, 70 rows against the live shortlist, all tags balanced, htranx filtered.

Files changed (2) hide show
  1. invited.py +104 -71
  2. tests/test_invited.py +65 -8
invited.py CHANGED
@@ -18,51 +18,68 @@ import json
18
 
19
  import config
20
 
21
- # Sub-track order on the page. Fixed rather than file order, so a regenerated
22
- # shortlist cannot silently reorder what participants read.
23
- _ORDER = [(t, d) for t in config.TRACKS for d in config.DIVISIONS]
24
-
25
  # Deliberately the same variables, fonts and border colours as results._DOC_CSS,
26
  # because this tab sits beside the two boards and a different table style reads
27
  # as a different system rather than a third view of the same data.
28
  CSS = """
29
- :root{--lb-border:#e4e1f0;--lb-text:#1b1830;--lb-sec:#6a6585;--lb-faint:#9b96b3;
30
- --lb-head:#f6f5fc;--lb-hover:#faf9fe;
31
  --lb-mono:'IBM Plex Mono',ui-monospace,SFMono-Regular,Menlo,monospace;
32
  --lb-sans:'IBM Plex Sans',ui-sans-serif,system-ui,-apple-system,sans-serif}
33
  *{box-sizing:border-box}
34
  html,body{margin:0;padding:0}
35
  body{font-family:var(--lb-sans);color:var(--lb-text);background:transparent;
36
  font-size:14px;padding:2px 0 12px}
37
- h3{font-size:1.05rem;font-weight:700;letter-spacing:-.01em;margin:1.5rem 0 .5rem;
38
- padding-bottom:.4rem;border-bottom:2px solid var(--lb-border)}
39
- p.note{color:var(--lb-sec);font-size:.87rem;margin:.2rem 0 1.1rem;line-height:1.5}
40
- /* Fixed layout + a shared colgroup is what makes all six tables line up. With
41
- auto layout each table sizes to its own longest cell, so the columns stepped
42
- left and right between sub-tracks and it read as six unrelated tables. */
43
- table{border-collapse:collapse;width:100%;table-layout:fixed;
44
- margin:.3rem 0 1.2rem;font-size:.87rem}
45
- th,td{border:1px solid var(--lb-border);padding:.4rem .6rem;text-align:left;
46
- vertical-align:top}
47
- th{background:var(--lb-head);font-weight:600;white-space:nowrap}
48
- tbody tr:hover{background:var(--lb-hover)}
49
- /* Team, account and model WRAP: model_name is free text from participants and
50
- reaches 80+ chars, so nowrap here is what pushed the table off the page. */
51
- td.team,td.acct,td.model{white-space:normal;overflow-wrap:anywhere}
52
- td.team{font-weight:600}
53
- td.acct,td.model{color:var(--lb-sec)}
54
- td.num,th.num{text-align:right;white-space:nowrap;
55
- font-variant-numeric:tabular-nums;font-family:var(--lb-mono)}
56
- td.rank{text-align:right;color:var(--lb-faint);font-family:var(--lb-mono)}
57
- .selfrep{color:var(--lb-sec);font-weight:400;font-size:.85em}
58
- .empty{color:var(--lb-sec);font-style:italic}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
  """
60
 
61
- # One colgroup for every table, so a column is in the same place in all six.
62
- COLS = ("<colgroup><col style='width:2.8rem'><col style='width:20%'>"
63
- "<col style='width:15%'><col><col style='width:8.5rem'>"
64
- "<col style='width:7rem'></colgroup>")
65
-
66
  # Same self-sizing contract as results.board_document(). Two things it must get
67
  # right, both learned there: measure the CONTENT wrapper for height, never
68
  # documentElement.scrollHeight, which is bounded below by the iframe viewport and
@@ -128,6 +145,44 @@ def _head() -> str:
128
  )
129
 
130
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
131
  def document(token: str | None) -> str:
132
  """The whole page, as a standalone HTML document for an iframe srcdoc.
133
 
@@ -140,7 +195,7 @@ def document(token: str | None) -> str:
140
  out = [_head(), "<div id='invroot'>"]
141
 
142
  if not rep or not rep.get("subtracks"):
143
- out.append("<p class=empty>The invited teams have not been published "
144
  "yet. They appear here once the shortlist is final.</p>")
145
  out.append(f"</div>{FIT_JS}</body></html>")
146
  return "".join(out)
@@ -154,48 +209,26 @@ def document(token: str | None) -> str:
154
  n_teams = rep.get("distinct_teams")
155
 
156
  across = f" across <b>{n_teams} teams</b>" if n_teams else ""
157
- out.append(f"<p class=note><b>{total} seats</b>{across}. "
158
  "A team is invited per sub-track, so a team may appear in several. "
159
  "If your team is listed, register it in <b>Submit &rarr; Test &rarr; "
160
  "Step 1</b> to collect your registry credentials.</p>")
161
 
162
- for key in _ORDER:
163
- st = by_sub.get(key)
164
- if st is None:
165
- continue
166
- rows = _rows(st)
167
- label = st.get("track_label") or config.TRACK_LABELS.get(key[0], key[0])
168
- metric = st.get("primary_metric_label") or "Score"
169
- if st.get("primary_is_self_reported"):
170
- metric += " <span class=selfrep>(self-rep.)</span>"
171
- out.append(f"<h3>{esc(label)} / {esc(st['division'].capitalize())} "
172
- f"&mdash; {len(rows)} invited</h3>")
173
- if not rows:
174
- out.append("<p class=empty>No teams invited in this sub-track.</p>")
175
  continue
176
- out.append(f"<table>{COLS}<thead><tr><th class=num>#</th><th>Team</th>"
177
- "<th>HF account</th>"
178
- f"<th>Model</th><th class=num>{metric}</th>"
179
- "<th class=num>Total params</th></tr></thead><tbody>")
180
- for i, t in enumerate(rows, 1):
181
- score = t.get("primary_score")
182
- try:
183
- score = f"{float(score):.4f}"
184
- except (TypeError, ValueError):
185
- score = "—"
186
- params = t.get("total_params")
187
- params = (f"{params/1e9:g}B"
188
- if isinstance(params, (int, float)) and params else "—")
189
- out.append(
190
- f"<tr><td class=rank>{i}</td>"
191
- f"<td class=team>{esc(str(t.get('team_name') or ''))}</td>"
192
- f"<td class=acct>{esc(str(t.get('hf_user') or ''))}</td>"
193
- f"<td class=model>{esc(str(t.get('model_name') or ''))}</td>"
194
- f"<td class=num>{score}</td>"
195
- f"<td class=num>{esc(params)}</td></tr>")
196
- out.append("</tbody></table>")
197
-
198
- out.append("<p class=note>Ranked by each sub-track's own validation metric, "
199
  "open-weight entries only, deduplicated by team. Scores are the "
200
  "validation results already shown on the Validation board.</p>")
201
  out.append(f"</div>{FIT_JS}</body></html>")
 
18
 
19
  import config
20
 
21
+ # Order on the page comes from config.TRACKS / config.DIVISIONS at render time,
22
+ # not from the shortlist file, so a regenerated shortlist cannot silently
23
+ # reorder what participants read.
24
+ #
25
  # Deliberately the same variables, fonts and border colours as results._DOC_CSS,
26
  # because this tab sits beside the two boards and a different table style reads
27
  # as a different system rather than a third view of the same data.
28
  CSS = """
29
+ :root{--lb-border:#cfe0f2;--lb-text:#1b1830;--lb-sec:#5a6b80;--lb-faint:#8ba0b8;
30
+ --lb-surface:#f2f8ff;--lb-head:#e2eefb;--lb-hover:#e8f2fd;
31
  --lb-mono:'IBM Plex Mono',ui-monospace,SFMono-Regular,Menlo,monospace;
32
  --lb-sans:'IBM Plex Sans',ui-sans-serif,system-ui,-apple-system,sans-serif}
33
  *{box-sizing:border-box}
34
  html,body{margin:0;padding:0}
35
  body{font-family:var(--lb-sans);color:var(--lb-text);background:transparent;
36
  font-size:14px;padding:2px 0 12px}
37
+ .lb-note{color:var(--lb-sec);font-size:.87rem;margin:.2rem 0 1.1rem;line-height:1.5}
38
+ .lb-track{margin-top:1.6rem}
39
+ .lb-track:first-of-type{margin-top:.2rem}
40
+ .lb-track-name{font-size:1.2rem;font-weight:700;letter-spacing:-.01em;
41
+ padding-bottom:.45rem;border-bottom:2px solid var(--lb-border);margin-bottom:.8rem}
42
+ /* Same grid as the boards: 3 tracks down, Small|Large across. auto-fit with a
43
+ 460px floor means a narrow viewport falls back to one board per row instead
44
+ of squeezing two unreadable ones. */
45
+ .lb-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,460px),1fr));gap:.8rem}
46
+ .lb-board{min-width:0;background:var(--lb-surface);border:1px solid var(--lb-border);
47
+ border-radius:12px;overflow:hidden}
48
+ .lb-board-head{padding:.6rem .9rem;border-bottom:1px solid var(--lb-border);
49
+ font-weight:600;font-size:.9rem;background:var(--lb-head)}
50
+ .lb-board-head .n{color:var(--lb-sec);font-weight:400}
51
+ .lb-scroll{overflow:auto;max-width:100%;overscroll-behavior:contain}
52
+ table{width:100%;border-collapse:collapse;table-layout:auto;font-size:.83rem;
53
+ border:0;margin:0;background:var(--lb-surface)}
54
+ th,td{padding-right:1.1rem}
55
+ th:last-child,td:last-child{padding-right:.55rem}
56
+ thead th{font-family:var(--lb-mono);font-size:.66rem;letter-spacing:.04em;
57
+ text-transform:uppercase;color:var(--lb-sec);font-weight:600;text-align:left;
58
+ padding:.45rem .55rem;border-bottom:1px solid var(--lb-border);
59
+ background:var(--lb-head);white-space:nowrap}
60
+ tbody td{padding:.4rem .55rem;border-bottom:1px solid var(--lb-border);
61
+ color:var(--lb-text);white-space:nowrap;vertical-align:middle}
62
+ tbody tr:last-child td{border-bottom:0}
63
+ tbody tr:hover td{background:var(--lb-hover)}
64
+ .lb-rank{font-family:var(--lb-mono);font-weight:600;color:var(--lb-sec)}
65
+ /* Team and model are participant free text (model_name seen at 82 chars), so
66
+ they wrap inside a ch bound. Everything else stays on one line. */
67
+ .lb-team{font-weight:600;white-space:normal;overflow-wrap:anywhere;max-width:14ch}
68
+ .lb-acct{font-family:var(--lb-mono);font-size:.78rem;color:var(--lb-sec);
69
+ white-space:normal;overflow-wrap:anywhere;max-width:12ch}
70
+ .lb-model{color:var(--lb-sec);font-size:.78rem;white-space:normal;
71
+ overflow-wrap:anywhere;max-width:22ch;line-height:1.3}
72
+ .lb-score,.lb-params{font-family:var(--lb-mono);text-align:right}
73
+ .lb-th-num{text-align:right}
74
+ .lb-th-score{background:linear-gradient(rgba(31,111,196,.07),rgba(31,111,196,.07)),var(--lb-head)}
75
+ .lb-score{background:linear-gradient(rgba(31,111,196,.05),rgba(31,111,196,.05))}
76
+ .lb-selfrep{display:inline-block;font-family:var(--lb-sans);font-size:.6rem;
77
+ font-weight:600;text-transform:none;letter-spacing:0;padding:1px 5px;
78
+ border-radius:6px;background:#d6e8fa;color:#3c5a78}
79
+ .lb-empty{padding:1.3rem;color:var(--lb-faint);font-style:italic;text-align:center;
80
+ font-size:.85rem}
81
  """
82
 
 
 
 
 
 
83
  # Same self-sizing contract as results.board_document(). Two things it must get
84
  # right, both learned there: measure the CONTENT wrapper for height, never
85
  # documentElement.scrollHeight, which is bounded below by the iframe viewport and
 
145
  )
146
 
147
 
148
+ def _board(st: dict, division: str, esc) -> str:
149
+ """One division's card: a heading, then that sub-track's ranked table."""
150
+ rows = _rows(st)
151
+ head = (f'<div class="lb-board-head">{esc(division.capitalize())} '
152
+ f'<span class="n">&mdash; {len(rows)} invited</span></div>')
153
+ if not rows:
154
+ return (f'<div class="lb-board">{head}'
155
+ f'<div class="lb-empty">No teams invited</div></div>')
156
+
157
+ metric = esc(str(st.get("primary_metric_label") or "Score"))
158
+ if st.get("primary_is_self_reported"):
159
+ metric += ' <span class="lb-selfrep">self-rep.</span>'
160
+
161
+ cells = []
162
+ for i, t in enumerate(rows, 1):
163
+ score = t.get("primary_score")
164
+ try:
165
+ score = f"{float(score):.4f}"
166
+ except (TypeError, ValueError):
167
+ score = "—"
168
+ params = t.get("total_params")
169
+ params = (f"{params / 1e9:g}B"
170
+ if isinstance(params, (int, float)) and params else "—")
171
+ cells.append(
172
+ f'<tr><td class="lb-rank">{i}</td>'
173
+ f'<td class="lb-team">{esc(str(t.get("team_name") or ""))}</td>'
174
+ f'<td class="lb-acct">{esc(str(t.get("hf_user") or ""))}</td>'
175
+ f'<td class="lb-model">{esc(str(t.get("model_name") or ""))}</td>'
176
+ f'<td class="lb-score">{score}</td>'
177
+ f'<td class="lb-params">{esc(params)}</td></tr>')
178
+
179
+ return (f'<div class="lb-board">{head}<div class="lb-scroll"><table>'
180
+ f'<thead><tr><th>#</th><th>Team</th><th>Account</th><th>Model</th>'
181
+ f'<th class="lb-th-num lb-th-score">{metric}</th>'
182
+ f'<th class="lb-th-num">Params</th></tr></thead>'
183
+ f'<tbody>{"".join(cells)}</tbody></table></div></div>')
184
+
185
+
186
  def document(token: str | None) -> str:
187
  """The whole page, as a standalone HTML document for an iframe srcdoc.
188
 
 
195
  out = [_head(), "<div id='invroot'>"]
196
 
197
  if not rep or not rep.get("subtracks"):
198
+ out.append("<p class='lb-empty'>The invited teams have not been published "
199
  "yet. They appear here once the shortlist is final.</p>")
200
  out.append(f"</div>{FIT_JS}</body></html>")
201
  return "".join(out)
 
209
  n_teams = rep.get("distinct_teams")
210
 
211
  across = f" across <b>{n_teams} teams</b>" if n_teams else ""
212
+ out.append(f"<p class='lb-note'><b>{total} seats</b>{across}. "
213
  "A team is invited per sub-track, so a team may appear in several. "
214
  "If your team is listed, register it in <b>Submit &rarr; Test &rarr; "
215
  "Step 1</b> to collect your registry credentials.</p>")
216
 
217
+ # One .lb-track per track, each holding a 2-up .lb-grid of Small | Large,
218
+ # so the six sub-tracks lay out 3 down by 2 across. Same structure and class
219
+ # names as results._boards_inner(), so this reads as a third view of the
220
+ # boards rather than a different page.
221
+ for track in config.TRACKS:
222
+ boards = [_board(by_sub[(track, d)], d, esc)
223
+ for d in config.DIVISIONS if (track, d) in by_sub]
224
+ if not boards:
 
 
 
 
 
225
  continue
226
+ label = config.TRACK_LABELS.get(track, track)
227
+ out.append(f'<div class="lb-track"><div class="lb-track-name">'
228
+ f'{esc(str(label))}</div>'
229
+ f'<div class="lb-grid">{"".join(boards)}</div></div>')
230
+
231
+ out.append("<p class='lb-note'>Ranked by each sub-track's own validation metric, "
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
232
  "open-weight entries only, deduplicated by team. Scores are the "
233
  "validation results already shown on the Validation board.</p>")
234
  out.append(f"</div>{FIT_JS}</body></html>")
tests/test_invited.py CHANGED
@@ -15,6 +15,7 @@ from unittest import mock
15
 
16
  sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
17
 
 
18
  import invited # noqa: E402
19
 
20
 
@@ -51,6 +52,25 @@ def _report(n_subtracks=2, rows_per=3, note_rows=0):
51
  "subtracks": subs}
52
 
53
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
54
  def _doc(rep):
55
  with mock.patch.object(invited, "load_shortlist_report", lambda t: rep):
56
  return invited.document(None)
@@ -107,20 +127,57 @@ class SelfSizingTest(unittest.TestCase):
107
  self.assertIn(f"addEventListener('{hook}',fit)", d)
108
 
109
 
110
- class ColumnsLineUpTest(unittest.TestCase):
111
- def test_every_table_carries_the_same_colgroup(self):
112
- d = _doc(_report(n_subtracks=2))
113
- self.assertEqual(2, d.count("<table>"))
114
- self.assertEqual(d.count("<table>"), d.count(invited.COLS))
 
 
 
115
 
116
- def test_the_layout_is_fixed_so_widths_cannot_be_content_driven(self):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
117
  d = _doc(_report())
118
- self.assertIn("table-layout:fixed", d)
 
 
119
 
120
  def test_long_free_text_wraps_rather_than_widening_the_table(self):
121
  """model_name is participant free text and reaches 80+ chars."""
122
  d = _doc(_report())
123
- self.assertIn("td.team,td.acct,td.model{white-space:normal", d)
124
  self.assertIn("overflow-wrap:anywhere", d)
125
 
126
 
 
15
 
16
  sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
17
 
18
+ import config # noqa: E402
19
  import invited # noqa: E402
20
 
21
 
 
52
  "subtracks": subs}
53
 
54
 
55
+ def _full_report():
56
+ """Every real (track, division), so the grid shape can be asserted."""
57
+ subs = []
58
+ for t in config.TRACKS:
59
+ for d in config.DIVISIONS:
60
+ subs.append({
61
+ "track": t, "division": d,
62
+ "track_label": config.TRACK_LABELS.get(t, t),
63
+ "primary_metric_label": "Accuracy",
64
+ "primary_is_self_reported": False,
65
+ "shortlist": [{
66
+ "rank": 1, "team_name": f"{t}-{d}", "hf_user": "u",
67
+ "model_name": "m", "primary_score": 0.5,
68
+ "total_params": 7_000_000_000,
69
+ }],
70
+ })
71
+ return {"distinct_teams": 6, "total_seats": len(subs), "subtracks": subs}
72
+
73
+
74
  def _doc(rep):
75
  with mock.patch.object(invited, "load_shortlist_report", lambda t: rep):
76
  return invited.document(None)
 
127
  self.assertIn(f"addEventListener('{hook}',fit)", d)
128
 
129
 
130
+ class GridLayoutTest(unittest.TestCase):
131
+ """Re-pointed 2026-08-08 from a flat one-table-per-sub-track page.
132
+
133
+ Six full-width tables stacked vertically did not match the Validation tab
134
+ and read as six unrelated tables. The layout is now the boards' own: one
135
+ .lb-track per track, each holding a 2-up .lb-grid of Small | Large, so the
136
+ six sub-tracks sit 3 down by 2 across.
137
+ """
138
 
139
+ def test_one_track_section_per_track_not_one_per_sub_track(self):
140
+ d = _doc(_full_report())
141
+ self.assertEqual(len(config.TRACKS), d.count('class="lb-track"'))
142
+ self.assertEqual(len(config.TRACKS), d.count('class="lb-grid"'))
143
+
144
+ def test_each_track_holds_one_board_per_division(self):
145
+ d = _doc(_full_report())
146
+ n = len(config.TRACKS) * len(config.DIVISIONS)
147
+ self.assertEqual(n, d.count('class="lb-board"'))
148
+ self.assertEqual(n, d.count("<table>"))
149
+
150
+ def test_the_grid_is_two_up_and_falls_back_to_one_when_narrow(self):
151
+ d = _doc(_full_report())
152
+ self.assertIn("grid-template-columns:repeat(auto-fit", d)
153
+ self.assertIn("minmax(min(100%,460px),1fr)", d)
154
+
155
+ def test_it_uses_the_same_class_names_as_the_boards(self):
156
+ """Divergent markup here is how the two views drift apart visually.
157
+
158
+ results.py is read as TEXT rather than imported: importing it pulls in
159
+ pandas, which made this test fail on any interpreter without it, for a
160
+ reason that has nothing to do with the property being checked.
161
+ """
162
+ here = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
163
+ with open(os.path.join(here, "results.py"), encoding="utf-8") as fh:
164
+ board_src = fh.read()
165
+ for cls in ("lb-track", "lb-track-name", "lb-grid", "lb-board",
166
+ "lb-board-head", "lb-scroll", "lb-rank", "lb-team",
167
+ "lb-model"):
168
+ self.assertIn(cls, board_src, f"{cls} is not a board class")
169
+ self.assertIn(cls, invited.CSS, f"{cls} missing here")
170
+
171
+ def test_the_tables_are_tinted_blue(self):
172
  d = _doc(_report())
173
+ self.assertIn("--lb-surface:#f2f8ff", d)
174
+ self.assertIn("--lb-head:#e2eefb", d)
175
+ self.assertIn("background:var(--lb-surface)", d)
176
 
177
  def test_long_free_text_wraps_rather_than_widening_the_table(self):
178
  """model_name is participant free text and reaches 80+ chars."""
179
  d = _doc(_report())
180
+ self.assertIn(".lb-team{font-weight:600;white-space:normal", d)
181
  self.assertIn("overflow-wrap:anywhere", d)
182
 
183