fix(ui): keep the table columns readable on a phone

The tables were squeezed to fit the screen, and with seven to nine
columns on a 390px phone every heading and every value was truncated to
a few characters - a gravity of "1.0..." and a date of "202..." are
worse than useless, and the beer name was painted over the gravity
reading because the cell was allowed to overflow.

The tables keep their natural width and scroll sideways inside their
card, which is what a table does.  The advice is a small button so the
column that matters is narrow, and the headings have a short form for
a phone ("Brewing advice" -> "Advice", "Attenuation" -> "Att.").

The secondary columns - the drop, the legacy ABV and the days - go away
on a phone; the details are still on the run's own page.

A name and a comment get a sensible minimum width and wrap on a word
boundary rather than one word per line.

The desktop is untouched: the same full headings, the same 806px table,
and the pop-out still opens on hover.
This commit is contained in:
2026-11-09 21:48:07 +10:00
parent 2600622af8
commit 27b2611a20
6 changed files with 232 additions and 209 deletions
+101 -83
View File
@@ -451,7 +451,10 @@ input[type="date"]::-webkit-calendar-picker-indicator:hover { opacity: 1; }
/* ---------------------------------------------------------------- tables */ /* ---------------------------------------------------------------- tables */
.table-wrap { overflow-x: auto; } .table-wrap {
overflow-x: auto;
-webkit-overflow-scrolling: touch;
}
table.data { table.data {
width: 100%; width: 100%;
@@ -480,21 +483,18 @@ table.data tbody tr:hover { background: var(--bg-raised-2); }
table.data .num { font-family: var(--mono); text-align: right; white-space: nowrap; } table.data .num { font-family: var(--mono); text-align: right; white-space: nowrap; }
table.data .comment { color: var(--text-muted); font-size: .84rem; } table.data .comment { color: var(--text-muted); font-size: .84rem; }
/* On a narrow screen the tables reflow into stacked cards. /* A heading that is too long for a phone column. The long form shows
on a desktop and the short form on a phone - see the media query. */
table.data .short { display: none; }
table.data .advice-trigger .short { display: none; }
A seven-column table does not fit a phone. It was left to scroll /* On a narrow screen the header wraps onto two rows: the brand and the
sideways inside its wrapper, which meant the columns on the right were sign-out on the first, the nav tabs on the second where they get the
off the screen and the brewer had to pan the table to see them. full width.
Each row becomes a card: the header cell becomes a label above its They used to sit on one row with the brand and the sign-out, and the
value, and the cells stack. */ last tab was cut off mid-word. */
@media (max-width: 720px) { @media (max-width: 720px) {
/* The header wraps onto two rows on a phone: the brand and the
sign-out on the first, the nav tabs on the second where they get
the full width and scroll sideways if they still do not fit.
They used to sit on one row with the brand and the sign-out, and
the last tab was cut off mid-word. */
.topbar__inner { .topbar__inner {
flex-wrap: wrap; flex-wrap: wrap;
row-gap: .5rem; row-gap: .5rem;
@@ -537,79 +537,97 @@ table.data .comment { color: var(--text-muted); font-size: .84rem; }
letter-spacing: .01em; letter-spacing: .01em;
} }
/* The tables stay tables on a phone.
They scroll sideways inside their card, which is what a table
does. They used to be squeezed to fit instead, and with seven
to nine columns on a 390px screen that meant every heading and
every value was truncated to a few characters - a gravity of
"1.0..." and a date of "202..." are worse than useless.
So the columns keep their natural width, the table scrolls, and
the advice is a small button so the column that matters is
narrow. */
table.data {
table-layout: auto;
width: 100%;
}
table.data th,
table.data td {
padding-left: .5rem;
padding-right: .5rem;
}
/* A name and a comment are words, so they get a sensible minimum
width and wrap on a word boundary - not one word per line. */
table.data td.col-name,
table.data td.col-comment {
min-width: 7rem;
max-width: 12rem;
white-space: normal;
word-break: normal;
overflow-wrap: break-word;
}
/* The heading gets a short form, because a heading that is cut off
is worse than a heading that is brief. */
table.data .col-abv .long,
table.data .col-advice .long,
table.data .col-finished .long,
table.data .col-comment .long {
display: none;
}
table.data .col-abv .short,
table.data .col-advice .short,
table.data .col-finished .short,
table.data .col-comment .short {
display: inline;
}
/* The advice trigger is small on a phone - it is a button, and
the advice itself is in the pop-out. */
table.data .advice-trigger {
font-size: .68rem;
padding: .25rem .4rem;
gap: .2rem;
}
table.data .advice-trigger .long {
display: none;
}
table.data .advice-trigger .short {
display: inline;
}
/* The action column holds a button. */
table.data .col-action {
white-space: nowrap;
}
table.data .col-action .btn {
padding-left: .5rem;
padding-right: .5rem;
font-size: .72rem;
}
/* The secondary columns go away on a phone - the details are still
on the run's own page. */
table.data .col-drop,
table.data .col-legacy,
table.data .col-days {
display: none;
}
/* The scroll area keeps a visible scrollbar on a phone, so it is
obvious there is more of the table to the right. */
.table-wrap { .table-wrap {
overflow-x: visible; scrollbar-width: thin;
-webkit-overflow-scrolling: touch;
} }
table.data,
table.data tbody,
table.data tr,
table.data td {
display: block;
width: auto;
}
table.data thead {
/* HIDDEN in the stacked layout. Each cell shows its column
name as a label, so the header row would be a duplicate - and
it was 230px of the first card. */
display: none;
}
table.data tr {
border: 1px solid var(--border);
border-radius: 8px;
margin-bottom: .75rem;
padding: .25rem .1rem;
background: var(--bg-raised-2);
}
table.data tbody tr:hover {
background: var(--bg-raised-2);
}
table.data th {
display: block;
border-bottom: 1px solid var(--border);
}
table.data td {
display: grid;
grid-template-columns: 8.5rem 1fr;
gap: .5rem;
align-items: baseline;
padding: .45rem .7rem;
border-bottom: 1px solid rgba(255, 255, 255, .04);
text-align: left;
}
table.data td::before {
/* The column heading, shown as the row's label. */
content: attr(data-label);
font-size: .65rem;
font-weight: 650;
letter-spacing: .07em;
text-transform: uppercase;
color: var(--text-faint);
}
table.data td.num {
text-align: left;
}
table.data td.right,
table.data td:empty {
grid-template-columns: 1fr;
}
table.data td.right::before {
content: none;
}
/* A cell with nothing in it must not leave a gap. */
table.data td:empty {
display: none;
}
} }
/* ----------------------------------------------------------------- chips */ /* ----------------------------------------------------------------- chips */
-24
View File
@@ -207,36 +207,12 @@
} }
} }
function labelTableCells() {
var tables = document.querySelectorAll("table.data");
for (var t = 0; t < tables.length; t++) {
var headerRow = tables[t].querySelector("thead tr");
if (!headerRow) { continue; }
var labels = [];
var headers = headerRow.querySelectorAll("th");
for (var h = 0; h < headers.length; h++) {
labels.push(headers[h].textContent.trim());
}
var rows = tables[t].querySelectorAll("tbody tr");
for (var r = 0; r < rows.length; r++) {
var cells = rows[r].querySelectorAll("td");
for (var c = 0; c < cells.length && c < labels.length; c++) {
if (labels[c] && !cells[c].getAttribute("data-label")) {
cells[c].setAttribute("data-label", labels[c]);
}
}
}
}
}
function init() { function init() {
focusFirstEmpty(); focusFirstEmpty();
defaultDateFields(); defaultDateFields();
wireToggles(); wireToggles();
wireConfirms(); wireConfirms();
wireAdvicePopouts(); wireAdvicePopouts();
labelTableCells();
} }
if (document.readyState === "loading") { if (document.readyState === "loading") {
+26 -26
View File
@@ -99,43 +99,43 @@
<table class="data"> <table class="data">
<thead> <thead>
<tr> <tr>
<th>Run</th> <th class="col-run">Run</th>
<th>Name</th> <th class="col-name">Name</th>
<th class="num">OG</th> <th class="num col-gravity">OG</th>
<th class="num">FG</th> <th class="num col-gravity">FG</th>
<th class="num">ABV</th> <th class="num col-abv">ABV</th>
<th class="num">Attenuation</th> <th class="num col-abv" title="Attenuation"><span class="long">Attenuation</span><span class="short">Att.</span></th>
<th class="num">Days</th> <th class="num col-abv col-days">Days</th>
<th class="num">To 1.010</th> <th class="num col-abv" title="Days to reach 1.010"><span class="long">To 1.010</span><span class="short">1.010</span></th>
<th>Finished</th> <th class="col-finished" title="Finished"><span class="long">Finished</span><span class="short">Done</span></th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for row in rows %} {% for row in rows %}
<tr{% if row.suspect %} class="is-suspect"{% endif %}> <tr{% if row.suspect %} class="is-suspect"{% endif %}>
<td class="mono">{{ row.beer_run_id }}</td> <td class="col-run mono">{{ row.beer_run_id }}</td>
<td>{{ row.name }}{% if row.suspect %} <span class="chip chip--warn">not counted</span>{% endif %}</td> <td class="col-name">{{ row.name }}{% if row.suspect %} <span class="chip chip--warn">not counted</span>{% endif %}</td>
<td class="num"> <td class="col-gravity num">
{% if row.og_points is not none %} {% if row.og_points is not none %}
{{ '%.3f' % (row.og_points / 1000) }} {{ '%.3f' % (row.og_points / 1000) }}
{% else %}—{% endif %} {% else %}—{% endif %}
</td> </td>
<td class="num"> <td class="col-gravity num">
{% if row.fg_points is not none %} {% if row.fg_points is not none %}
{{ '%.3f' % (row.fg_points / 1000) }} {{ '%.3f' % (row.fg_points / 1000) }}
{% else %}—{% endif %} {% else %}—{% endif %}
</td> </td>
<td class="num"> <td class="col-abv num">
{% if row.abv is not none %}{{ '%.2f' % row.abv }}%{% else %}—{% endif %} {% if row.abv is not none %}{{ '%.2f' % row.abv }}%{% else %}—{% endif %}
</td> </td>
<td class="num"> <td class="col-abv num">
{% if row.attenuation is not none %} {% if row.attenuation is not none %}
<strong>{{ row.attenuation }}%</strong> <strong>{{ row.attenuation }}%</strong>
{% else %}—{% endif %} {% else %}—{% endif %}
</td> </td>
<td class="num">{{ row.days if row.days is not none else '—' }}</td> <td class="col-abv col-days num">{{ row.days if row.days is not none else '—' }}</td>
<td class="num">{{ row.days_to_1010 if row.days_to_1010 is not none else '—' }}</td> <td class="col-abv num">{{ row.days_to_1010 if row.days_to_1010 is not none else '—' }}</td>
<td class="mono nowrap">{{ row.final_date or '—' }}</td> <td class="col-finished mono nowrap">{{ row.final_date or '—' }}</td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
@@ -155,21 +155,21 @@
<table class="data"> <table class="data">
<thead> <thead>
<tr> <tr>
<th>Run</th> <th class="col-run">Run</th>
<th class="num">OG</th> <th class="num col-gravity">OG</th>
<th class="num">FG</th> <th class="num col-gravity">FG</th>
<th>Problem</th> <th class="col-comment">Problem</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for row in suspect %} {% for row in suspect %}
<tr> <tr>
<td class="mono"> <td class="col-run mono">
<a href="{{ url_for('brews.run_detail', run_id=row.beer_run_id) }}">{{ row.beer_run_id }}</a> <a href="{{ url_for('brews.run_detail', run_id=row.beer_run_id) }}">{{ row.beer_run_id }}</a>
</td> </td>
<td class="num">{{ '%.3f' % (row.og_points / 1000) }}</td> <td class="col-gravity num">{{ '%.3f' % (row.og_points / 1000) }}</td>
<td class="num"><span class="chip chip--warn">{{ '%.3f' % (row.fg_points / 1000) }}</span></td> <td class="col-gravity num"><span class="chip chip--warn">{{ '%.3f' % (row.fg_points / 1000) }}</span></td>
<td class="comment">{{ row.problem }}</td> <td class="col-comment comment">{{ row.problem }}</td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
+36 -32
View File
@@ -78,48 +78,52 @@
<table class="data"> <table class="data">
<thead> <thead>
<tr> <tr>
<th>Run</th> <th class="col-run">Run</th>
<th>Name</th> <th class="col-name">Name</th>
<th class="num">Gravity</th> <th class="num col-gravity">Gravity</th>
<th class="num">Est. ABV</th> <th class="num col-abv" title="Estimated alcohol by volume"><span class="long">Est. ABV</span><span class="short">ABV</span></th>
<th>Comment</th> <th class="col-comment" title="Comment"><span class="long">Comment</span><span class="short">Note</span></th>
<th>Brewing advice</th> <th class="col-advice" title="Brewing advice"><span class="long">Brewing advice</span><span class="short">Advice</span></th>
<th></th> <th class="col-action"></th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for run in active_runs %} {% for run in active_runs %}
<tr> <tr>
<td class="mono">{{ run.beer_run_id }}</td> <td class="mono col-run">{{ run.beer_run_id }}</td>
<td>{{ run.canonical_name }}</td> <td class="col-name">{{ run.canonical_name }}</td>
<td class="num"> <td class="num col-gravity">
{% if run.min_sg is not none %} {% if run.min_sg is not none %}
<span class="mono strong">{{ run.min_sg_decimal }}</span> <span class="mono strong">{{ run.min_sg_decimal }}</span>
<div class="faint tiny">{{ run.min_sg_point_scale }} points</div> <div class="faint tiny point-scale">{{ run.min_sg_point_scale }} points</div>
{% else %}—{% endif %} {% else %}—{% endif %}
</td> </td>
<td class="num"> <td class="num col-abv">
{% if run.projected_abv is not none %} {% if run.projected_abv is not none %}
{{ '%.2f' % run.projected_abv }}% {{ '%.2f' % run.projected_abv }}%
{% else %} {% else %}
<span class="faint" title="Record another reading, or set the original gravity, to get an estimate">—</span> <span class="faint" title="Record another reading, or set the original gravity, to get an estimate">—</span>
{% endif %} {% endif %}
</td> </td>
<td class="comment"> <td class="comment col-comment">
{% if run.comments %} {% if run.comments %}
{{ run.comments[-1] }} {{ run.comments[-1] }}
{% else %} {% else %}
<span class="faint">—</span> <span class="faint">—</span>
{% endif %} {% endif %}
</td> </td>
<td class="advice-cell"> <td class="advice-cell col-advice">
{% if run.advice %} {% if run.advice %}
<button type="button" <button type="button"
class="advice-trigger{% if not run.advice_ok %} advice-trigger--failed{% endif %}" class="advice-trigger{% if not run.advice_ok %} advice-trigger--failed{% endif %}"
aria-expanded="false"> aria-expanded="false">
<span class="advice-trigger__icon" aria-hidden="true">&#9998;</span> <span class="advice-trigger__icon" aria-hidden="true">&#9998;</span>
<span class="advice-trigger__label"> <span class="advice-trigger__label">
{% if run.advice_ok %}Read the advice{% else %}Advice failed{% endif %} {% if run.advice_ok %}
<span class="long">Read the advice</span><span class="short">Advice</span>
{% else %}
<span class="long">Advice failed</span><span class="short">Failed</span>
{% endif %}
</span> </span>
</button> </button>
<div class="advice-pop" role="tooltip"> <div class="advice-pop" role="tooltip">
@@ -134,7 +138,7 @@
<span class="faint">Thinking…</span> <span class="faint">Thinking…</span>
{% endif %} {% endif %}
</td> </td>
<td class="right"> <td class="right col-action">
<a class="btn btn--ghost btn--sm" <a class="btn btn--ghost btn--sm"
href="{{ url_for('brews.run_detail', run_id=run.beer_run_id) }}">Open</a> href="{{ url_for('brews.run_detail', run_id=run.beer_run_id) }}">Open</a>
</td> </td>
@@ -163,41 +167,41 @@
<table class="data"> <table class="data">
<thead> <thead>
<tr> <tr>
<th>Run</th> <th class="col-run">Run</th>
<th>Name</th> <th class="col-name">Name</th>
<th class="num">OG</th> <th class="num col-gravity">OG</th>
<th class="num">FG</th> <th class="num col-gravity">FG</th>
<th class="num">ABV</th> <th class="num col-abv">ABV</th>
<th>Finished</th> <th class="col-finished" title="Finished"><span class="long">Finished</span><span class="short">Done</span></th>
<th>Comment</th> <th class="col-comment" title="Comment"><span class="long">Comment</span><span class="short">Note</span></th>
<th></th> <th class="col-action"></th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for run in completed_runs %} {% for run in completed_runs %}
<tr> <tr>
<td class="mono">{{ run.beer_run_id }}</td> <td class="mono col-run">{{ run.beer_run_id }}</td>
<td>{{ run.canonical_name }}</td> <td class="col-name">{{ run.canonical_name }}</td>
<td class="num"> <td class="num col-gravity">
{% if run.og_points is not none %}{{ run.og_points }}{% else %}—{% endif %} {% if run.og_points is not none %}{{ run.og_points }}{% else %}—{% endif %}
</td> </td>
<td class="num"> <td class="num col-gravity">
{% if run.fg_points is not none %}{{ run.fg_points }}{% else %}—{% endif %} {% if run.fg_points is not none %}{{ run.fg_points }}{% else %}—{% endif %}
</td> </td>
<td class="num"> <td class="num col-abv">
{% if run.abv is not none %} {% if run.abv is not none %}
<strong>{{ '%.2f' % run.abv }}%</strong> <strong>{{ '%.2f' % run.abv }}%</strong>
{% else %}—{% endif %} {% else %}—{% endif %}
</td> </td>
<td class="mono nowrap">{{ run.final_date or '—' }}</td> <td class="mono nowrap col-finished">{{ run.final_date or '—' }}</td>
<td class="comment"> <td class="comment col-comment">
{% if run.comments %} {% if run.comments %}
{{ run.comments[-1] }} {{ run.comments[-1] }}
{% else %} {% else %}
<span class="faint">—</span> <span class="faint">—</span>
{% endif %} {% endif %}
</td> </td>
<td class="right"> <td class="right col-action">
<a class="btn btn--ghost btn--sm" <a class="btn btn--ghost btn--sm"
href="{{ url_for('brews.run_detail', run_id=run.beer_run_id) }}">Open</a> href="{{ url_for('brews.run_detail', run_id=run.beer_run_id) }}">Open</a>
</td> </td>
+44 -44
View File
@@ -84,21 +84,21 @@
<table class="data"> <table class="data">
<thead> <thead>
<tr> <tr>
<th>Run</th> <th class="col-run">Run</th>
<th>Name</th> <th class="col-name">Name</th>
<th>Date</th> <th class="col-finished">Date</th>
<th class="num">Reading</th> <th class="num col-gravity">Reading</th>
<th>Comment</th> <th class="col-comment" title="Comment"><span class="long">Comment</span><span class="short">Note</span></th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for row in outliers %} {% for row in outliers %}
<tr> <tr>
<td class="mono">{{ row.beer_run_id }}</td> <td class="col-run mono">{{ row.beer_run_id }}</td>
<td>{{ row.name }}</td> <td class="col-name">{{ row.name }}</td>
<td class="mono nowrap">{{ row.date or '—' }}</td> <td class="col-finished mono nowrap">{{ row.date or '—' }}</td>
<td class="num"><span class="chip chip--warn">{{ row.sg }}</span></td> <td class="col-gravity num"><span class="chip chip--warn">{{ row.sg }}</span></td>
<td class="comment">{{ row.comment or '—' }}</td> <td class="col-comment comment">{{ row.comment or '—' }}</td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
@@ -118,45 +118,45 @@
<table class="data"> <table class="data">
<thead> <thead>
<tr> <tr>
<th>Run</th> <th class="col-run">Run</th>
<th>Name</th> <th class="col-name">Name</th>
<th>Status</th> <th class="col-finished" title="Status"><span class="long">Status</span><span class="short">Stat</span></th>
<th class="num">OG</th> <th class="num col-gravity">OG</th>
<th class="num">FG</th> <th class="num col-gravity">FG</th>
<th class="num">Drop</th> <th class="num col-drop">Drop</th>
<th class="num">ABV</th> <th class="num col-abv">ABV</th>
<th class="num">Legacy</th> <th class="num col-legacy">Legacy</th>
<th>Finished</th> <th class="col-finished" title="Finished"><span class="long">Finished</span><span class="short">Done</span></th>
<th>Comment</th> <th class="col-comment" title="Comment"><span class="long">Comment</span><span class="short">Note</span></th>
<th></th> <th class="col-action"></th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for run in summaries %} {% for run in summaries %}
<tr> <tr>
<td class="mono">{{ run.beer_run_id }}</td> <td class="col-run mono">{{ run.beer_run_id }}</td>
<td>{{ run.canonical_name }}</td> <td class="col-name">{{ run.canonical_name }}</td>
<td> <td class="col-finished">
{% if run.active %} {% if run.active %}
<span class="chip chip--active">Active</span> <span class="chip chip--active">Active</span>
{% else %} {% else %}
<span class="chip chip--done">Finished</span> <span class="chip chip--done">Finished</span>
{% endif %} {% endif %}
</td> </td>
<td class="num">{{ run.og_points or '—' }}</td> <td class="col-drop col-gravity num">{{ run.og_points or '—' }}</td>
<td class="num">{{ run.fg_points or '—' }}</td> <td class="col-gravity num">{{ run.fg_points or '—' }}</td>
<td class="num">{{ run.drop_points if run.drop_points is not none else '—' }}</td> <td class="col-gravity num">{{ run.drop_points if run.drop_points is not none else '—' }}</td>
<td class="num"> <td class="col-abv num">
{% if run.abv is not none %}<strong>{{ '%.2f' % run.abv }}%</strong> {% if run.abv is not none %}<strong>{{ '%.2f' % run.abv }}%</strong>
{% elif run.projected_abv is not none %}{{ '%.2f' % run.projected_abv }}%* {% elif run.projected_abv is not none %}{{ '%.2f' % run.projected_abv }}%*
{% else %}—{% endif %} {% else %}—{% endif %}
</td> </td>
<td class="num muted">{{ run.legacy_abv if run.legacy_abv is not none else '—' }}</td> <td class="col-legacy col-abv num muted">{{ run.legacy_abv if run.legacy_abv is not none else '—' }}</td>
<td class="mono nowrap">{{ run.final_date or '—' }}</td> <td class="col-finished mono nowrap">{{ run.final_date or '—' }}</td>
<td class="comment"> <td class="col-comment comment">
{% if run.comments %}{{ run.comments[-1] }}{% else %}<span class="faint">—</span>{% endif %} {% if run.comments %}{{ run.comments[-1] }}{% else %}<span class="faint">—</span>{% endif %}
</td> </td>
<td class="right"> <td class="right col-action">
<a class="btn btn--ghost btn--sm" <a class="btn btn--ghost btn--sm"
href="{{ url_for('brews.run_detail', run_id=run.beer_run_id) }}">Open</a> href="{{ url_for('brews.run_detail', run_id=run.beer_run_id) }}">Open</a>
</td> </td>
@@ -178,29 +178,29 @@
<table class="data"> <table class="data">
<thead> <thead>
<tr> <tr>
<th>Run</th> <th class="col-run">Run</th>
<th>Name</th> <th class="col-name">Name</th>
<th class="num">Standard ABV</th> <th class="num col-abv" title="Standard ABV"><span class="long">Standard ABV</span><span class="short">Std</span></th>
<th class="num">Legacy ABV</th> <th class="num col-legacy col-abv" title="Legacy ABV"><span class="long">Legacy ABV</span><span class="short">Old</span></th>
<th class="num">Overstated by</th> <th class="num col-abv" title="Overstated by"><span class="long">Overstated by</span><span class="short">Over</span></th>
<th>Finished</th> <th class="col-finished" title="Finished"><span class="long">Finished</span><span class="short">Done</span></th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for row in abv_rows %} {% for row in abv_rows %}
<tr> <tr>
<td class="mono">{{ row.beer_run_id }}</td> <td class="col-run mono">{{ row.beer_run_id }}</td>
<td>{{ row.name }}</td> <td class="col-name">{{ row.name }}</td>
<td class="num">{{ '%.2f' % row.abv }}%</td> <td class="col-abv num">{{ '%.2f' % row.abv }}%</td>
<td class="num muted">{{ '%.2f' % row.legacy_abv }}%</td> <td class="col-abv num muted">{{ '%.2f' % row.legacy_abv }}%</td>
<td class="num"> <td class="col-abv num">
{% if row.delta > 0 %} {% if row.delta > 0 %}
<span class="chip chip--warn">+{{ '%.2f' % row.delta }}</span> <span class="chip chip--warn">+{{ '%.2f' % row.delta }}</span>
{% else %} {% else %}
<span class="faint">{{ '%.2f' % row.delta }}</span> <span class="faint">{{ '%.2f' % row.delta }}</span>
{% endif %} {% endif %}
</td> </td>
<td class="mono nowrap">{{ row.final_date or '—' }}</td> <td class="col-finished mono nowrap">{{ row.final_date or '—' }}</td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
+25
View File
@@ -1024,6 +1024,31 @@ def test_the_advice_in_the_table_is_a_popout_not_the_text(client, repo):
assert "Warm it to 21C and wait." in body assert "Warm it to 21C and wait." in body
def test_the_tables_keep_their_columns_on_a_phone(client, repo):
"""The tables are readable on a phone, not squeezed into nothing.
The advice is a button and the table scrolls, so nothing is
truncated to an unreadable stub.
"""
repo.users.insert_one({"username": "beer", "password": "beer_pass"})
client.post("/login", data={"username": "beer", "password": "beer_pass"},
follow_redirects=True)
seed_readings(repo, [("1", "Cider raspberry elderberry", "1045", "2026-01-01", False, "")])
doc = repo.readings.find_one({"beer_run_id": "1"})
repo.set_reading_advice(str(doc["_id"]), "Warm it to 21C and wait.", ok=True)
body = client.get("/brews").get_data(as_text=True)
# The long heading has a short form for a phone, and the long form
# is still in the page for a desktop.
assert "Brewing advice" in body
assert ">Advice<" in body.replace("\n", "")
# The beer name is not truncated in the markup.
assert "Cider raspberry elderberry" in body
def test_a_failed_advice_is_marked_as_such(client, repo): def test_a_failed_advice_is_marked_as_such(client, repo):
"""A generation failure reads differently from real advice.""" """A generation failure reads differently from real advice."""
repo.users.insert_one({"username": "beer", "password": "beer_pass"}) repo.users.insert_one({"username": "beer", "password": "beer_pass"})