Release: one log page, grouped dishes, live search (#1)

Structure
- Kirjaa and Historia are one page. They were two views of the same thing — every history row already linked into the logger, and the logger had a day switcher. Two tabs instead of three. Also closed a gap: on an already-logged day there was no way to swap to a different dish, only to re-pick its sides.
- Ruoat → Ruuat, label and route.
- The catalog has a structure. It had no top-level headings at all — the mains simply began with "Liha". Both halves now carry a heading and a count, categories are visibly subordinate, and the add/edit forms collapse instead of filling the screen before any content.

Finding things
- Dishes grouped by category on both screens, Sekalaiset for multi-category ones. Derived from the stored set, not a fifth category, so one Tortillat still covers all four for the §8.1 suggester later.
- Live search on both lists, 250 ms after typing stops. Both remain plain GET forms, so they still filter with JavaScript off.
- History is paged 30 days at a time — it previously rendered every day back to the first entry, forever.

Correctness
- Future meals refused. The picker offered them and ?pvm= accepted them.
- today() wasn't midnight, so it never equalled a date parsed from ?pvm= — after saving, the card read "la 5.9. kirjattu" instead of "Tänään kirjattu".
- Deletes ask first, for dishes and logged meals. The meal is the more destructive: a dish is only soft-deleted.
- DB open failures name the path and uid, instead of unable to open database file (14).

Visual
- Category icons replace colour dots — steak, drumstick, fish, leaf, quartered circle.
- Row actions are a pencil and a bin; the header has a surface.

Housekeeping
- Datastar SDK dropped — one JSON decode was pulling in four modules including an HTTP compression stack. Five lines replace it.
- Release policy documented: main protected, releases arrive as PRs.

Co-authored-by: Esa Kataja <[email protected]>
Reviewed-on: #1
This commit was merged in pull request #1.
This commit is contained in:
2026-09-05 19:28:54 +00:00
co-authored by Esa Kataja
parent eb95bd0a03
commit e9754488db
13 changed files with 1194 additions and 264 deletions
+145
View File
@@ -0,0 +1,145 @@
<!doctype html>
<html lang="fi" data-theme="dark">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Foodster — kategoriakuvakkeet</title>
<style>
:root{
color-scheme: light dark;
--paper: light-dark(#ECEDE8, #121316);
--card: light-dark(#FFFFFF, #1C1E22);
--sunk: light-dark(#E3E4DE, #17181B);
--ink: light-dark(#14161A, #E9EAE5);
--muted: light-dark(#6B6F6A, #8B8F89);
--line: light-dark(#D5D6D0, #2C2F34);
--liha: light-dark(#AF4230, #DE7561);
--kana: light-dark(#B57E10, #DFA83B);
--kala: light-dark(#25688F, #63AAD8);
--kasvis:light-dark(#457A3C, #82BE7A);
}
html[data-theme="light"]{color-scheme:only light;}
html[data-theme="dark"] {color-scheme:only dark;}
*{box-sizing:border-box;}
body{margin:0;background:var(--paper);color:var(--ink);
font-family:system-ui,sans-serif;font-size:16px;line-height:1.45;}
.wrap{max-width:900px;margin:0 auto;padding:22px 20px 60px;}
h1{font-size:19px;letter-spacing:-.02em;margin:0 0 4px;}
.lede{margin:0 0 18px;color:var(--muted);font-size:14px;}
button.t{font:inherit;font-size:13px;background:var(--card);border:1px solid var(--line);
color:var(--muted);padding:7px 12px;border-radius:7px;cursor:pointer;margin-bottom:20px;}
.panel{background:var(--card);border:1px solid var(--line);border-radius:12px;
padding:16px 18px;margin-bottom:14px;}
.panel h2{font-size:14px;margin:0 0 12px;letter-spacing:-.01em;}
table{border-collapse:collapse;width:100%;}
th{font-size:10px;letter-spacing:.07em;text-transform:uppercase;color:var(--muted);
text-align:left;font-weight:600;padding:0 10px 8px 0;}
td{padding:7px 10px 7px 0;vertical-align:middle;}
td.name{font-size:13px;color:var(--muted);}
.i-liha { color: var(--liha); }
.i-kana { color: var(--kana); }
.i-kala { color: var(--kala); }
.i-kasvis{ color: var(--kasvis); }
svg{display:block;}
/* in context */
.board{display:flex;flex-wrap:wrap;gap:8px;}
.pill{display:flex;align-items:center;gap:9px;background:var(--card);
border:1px solid var(--line);border-radius:11px;color:var(--ink);
font-weight:600;letter-spacing:-.022em;padding:11px 15px;font-size:16px;}
.pill.big{font-size:21px;padding:14px 18px;}
.n{font-family:ui-monospace,monospace;font-weight:400;font-size:10px;color:var(--muted);}
.row{display:flex;align-items:center;gap:11px;padding:11px 0;
border-bottom:1px solid var(--line);}
.row .nm{font-size:16px;font-weight:600;}
.dotrow{display:flex;align-items:center;gap:11px;padding:11px 0;
border-bottom:1px solid var(--line);}
.dot{width:9px;height:9px;border-radius:2px;flex:none;}
.d-liha{background:var(--liha);}.d-kana{background:var(--kana);}
.d-kala{background:var(--kala);}.d-kasvis{background:var(--kasvis);}
.d-sek{background:conic-gradient(var(--liha) 0 25%,var(--kana) 25% 50%,
var(--kala) 50% 75%,var(--kasvis) 75% 100%);}
</style>
</head>
<body>
<div class="wrap">
<h1>Kategoriakuvakkeet</h1>
<p class="lede">Colour and shape together. The 14&nbsp;px column and the pill row are the sizes that actually ship.</p>
<button class="t" id="t">Teema: tumma</button>
<div class="panel">
<h2>Every size</h2>
<table>
<tr><th>Kategoria</th><th>12</th><th>14</th><th>18</th><th>26</th></tr>
<tr>
<td class="name">liha</td>
<td class="i-liha"><svg width="12" height="12" viewBox="0 0 16 16"><path fill="currentColor" fill-rule="evenodd" d="M8 1.8c3.4 0 6.1 2.3 6.1 5.1 0 1.7-1 3.2-2.5 4.1-.4 1.9-1.9 3.2-3.6 3.2-3.4 0-6.1-2.5-6.1-5.6C1.9 5 4.6 1.8 8 1.8zm2.7 7.5a1.6 1.6 0 1 0-3.2 0 1.6 1.6 0 0 0 3.2 0z"/></svg></td>
<td class="i-liha"><svg width="14" height="14" viewBox="0 0 16 16"><path fill="currentColor" fill-rule="evenodd" d="M8 1.8c3.4 0 6.1 2.3 6.1 5.1 0 1.7-1 3.2-2.5 4.1-.4 1.9-1.9 3.2-3.6 3.2-3.4 0-6.1-2.5-6.1-5.6C1.9 5 4.6 1.8 8 1.8zm2.7 7.5a1.6 1.6 0 1 0-3.2 0 1.6 1.6 0 0 0 3.2 0z"/></svg></td>
<td class="i-liha"><svg width="18" height="18" viewBox="0 0 16 16"><path fill="currentColor" fill-rule="evenodd" d="M8 1.8c3.4 0 6.1 2.3 6.1 5.1 0 1.7-1 3.2-2.5 4.1-.4 1.9-1.9 3.2-3.6 3.2-3.4 0-6.1-2.5-6.1-5.6C1.9 5 4.6 1.8 8 1.8zm2.7 7.5a1.6 1.6 0 1 0-3.2 0 1.6 1.6 0 0 0 3.2 0z"/></svg></td>
<td class="i-liha"><svg width="26" height="26" viewBox="0 0 16 16"><path fill="currentColor" fill-rule="evenodd" d="M8 1.8c3.4 0 6.1 2.3 6.1 5.1 0 1.7-1 3.2-2.5 4.1-.4 1.9-1.9 3.2-3.6 3.2-3.4 0-6.1-2.5-6.1-5.6C1.9 5 4.6 1.8 8 1.8zm2.7 7.5a1.6 1.6 0 1 0-3.2 0 1.6 1.6 0 0 0 3.2 0z"/></svg></td>
</tr>
<tr>
<td class="name">kana</td>
<td class="i-kana"><svg width="12" height="12" viewBox="0 0 16 16"><g fill="none" stroke="currentColor" stroke-width="2.6" stroke-linecap="round"><path d="M7.8 8.2l3.1-3.1"/></g><circle cx="5.4" cy="10.6" r="3.6" fill="currentColor"/><circle cx="12.2" cy="3.8" r="2.1" fill="currentColor"/><circle cx="13.4" cy="5.6" r="1.7" fill="currentColor"/></svg></td>
<td class="i-kana"><svg width="14" height="14" viewBox="0 0 16 16"><g fill="none" stroke="currentColor" stroke-width="2.6" stroke-linecap="round"><path d="M7.8 8.2l3.1-3.1"/></g><circle cx="5.4" cy="10.6" r="3.6" fill="currentColor"/><circle cx="12.2" cy="3.8" r="2.1" fill="currentColor"/><circle cx="13.4" cy="5.6" r="1.7" fill="currentColor"/></svg></td>
<td class="i-kana"><svg width="18" height="18" viewBox="0 0 16 16"><g fill="none" stroke="currentColor" stroke-width="2.6" stroke-linecap="round"><path d="M7.8 8.2l3.1-3.1"/></g><circle cx="5.4" cy="10.6" r="3.6" fill="currentColor"/><circle cx="12.2" cy="3.8" r="2.1" fill="currentColor"/><circle cx="13.4" cy="5.6" r="1.7" fill="currentColor"/></svg></td>
<td class="i-kana"><svg width="26" height="26" viewBox="0 0 16 16"><g fill="none" stroke="currentColor" stroke-width="2.6" stroke-linecap="round"><path d="M7.8 8.2l3.1-3.1"/></g><circle cx="5.4" cy="10.6" r="3.6" fill="currentColor"/><circle cx="12.2" cy="3.8" r="2.1" fill="currentColor"/><circle cx="13.4" cy="5.6" r="1.7" fill="currentColor"/></svg></td>
</tr>
<tr>
<td class="name">kala</td>
<td class="i-kala"><svg width="12" height="12" viewBox="0 0 16 16"><path fill="currentColor" fill-rule="evenodd" d="M14.4 8c-1.8 2.7-4.4 4.2-7 4.2-1.3 0-2.6-.4-3.6-1.1L1.4 13.2V2.8l2.4 2.1c1-.7 2.3-1.1 3.6-1.1 2.6 0 5.2 1.5 7 4.2zm-3.6-1.1a.95.95 0 1 0 0 1.9.95.95 0 0 0 0-1.9z"/></svg></td>
<td class="i-kala"><svg width="14" height="14" viewBox="0 0 16 16"><path fill="currentColor" fill-rule="evenodd" d="M14.4 8c-1.8 2.7-4.4 4.2-7 4.2-1.3 0-2.6-.4-3.6-1.1L1.4 13.2V2.8l2.4 2.1c1-.7 2.3-1.1 3.6-1.1 2.6 0 5.2 1.5 7 4.2zm-3.6-1.1a.95.95 0 1 0 0 1.9.95.95 0 0 0 0-1.9z"/></svg></td>
<td class="i-kala"><svg width="18" height="18" viewBox="0 0 16 16"><path fill="currentColor" fill-rule="evenodd" d="M14.4 8c-1.8 2.7-4.4 4.2-7 4.2-1.3 0-2.6-.4-3.6-1.1L1.4 13.2V2.8l2.4 2.1c1-.7 2.3-1.1 3.6-1.1 2.6 0 5.2 1.5 7 4.2zm-3.6-1.1a.95.95 0 1 0 0 1.9.95.95 0 0 0 0-1.9z"/></svg></td>
<td class="i-kala"><svg width="26" height="26" viewBox="0 0 16 16"><path fill="currentColor" fill-rule="evenodd" d="M14.4 8c-1.8 2.7-4.4 4.2-7 4.2-1.3 0-2.6-.4-3.6-1.1L1.4 13.2V2.8l2.4 2.1c1-.7 2.3-1.1 3.6-1.1 2.6 0 5.2 1.5 7 4.2zm-3.6-1.1a.95.95 0 1 0 0 1.9.95.95 0 0 0 0-1.9z"/></svg></td>
</tr>
<tr>
<td class="name">kasvis</td>
<td class="i-kasvis"><svg width="12" height="12" viewBox="0 0 16 16"><path fill="currentColor" d="M14 1.6C6.9 1.4 2.6 4.7 2.6 9.5c0 1.2.3 2.2.8 3.1l-1.7 1.7 1.3 1.3 1.7-1.7c.9.5 1.9.8 3.1.8 4.8 0 7-4.4 6.2-13z"/><path fill="none" stroke="var(--card)" stroke-width="1.1" stroke-linecap="round" d="M12.4 3.4L5.1 10.7"/></svg></td>
<td class="i-kasvis"><svg width="14" height="14" viewBox="0 0 16 16"><path fill="currentColor" d="M14 1.6C6.9 1.4 2.6 4.7 2.6 9.5c0 1.2.3 2.2.8 3.1l-1.7 1.7 1.3 1.3 1.7-1.7c.9.5 1.9.8 3.1.8 4.8 0 7-4.4 6.2-13z"/><path fill="none" stroke="var(--card)" stroke-width="1.1" stroke-linecap="round" d="M12.4 3.4L5.1 10.7"/></svg></td>
<td class="i-kasvis"><svg width="18" height="18" viewBox="0 0 16 16"><path fill="currentColor" d="M14 1.6C6.9 1.4 2.6 4.7 2.6 9.5c0 1.2.3 2.2.8 3.1l-1.7 1.7 1.3 1.3 1.7-1.7c.9.5 1.9.8 3.1.8 4.8 0 7-4.4 6.2-13z"/><path fill="none" stroke="var(--card)" stroke-width="1.1" stroke-linecap="round" d="M12.4 3.4L5.1 10.7"/></svg></td>
<td class="i-kasvis"><svg width="26" height="26" viewBox="0 0 16 16"><path fill="currentColor" d="M14 1.6C6.9 1.4 2.6 4.7 2.6 9.5c0 1.2.3 2.2.8 3.1l-1.7 1.7 1.3 1.3 1.7-1.7c.9.5 1.9.8 3.1.8 4.8 0 7-4.4 6.2-13z"/><path fill="none" stroke="var(--card)" stroke-width="1.1" stroke-linecap="round" d="M12.4 3.4L5.1 10.7"/></svg></td>
</tr>
<tr>
<td class="name">sekalaiset</td>
<td><svg width="12" height="12" viewBox="0 0 16 16"><path fill="var(--liha)" d="M8 8V1.4A6.6 6.6 0 0 1 14.6 8z"/><path fill="var(--kana)" d="M8 8h6.6A6.6 6.6 0 0 1 8 14.6z"/><path fill="var(--kala)" d="M8 8v6.6A6.6 6.6 0 0 1 1.4 8z"/><path fill="var(--kasvis)" d="M8 8H1.4A6.6 6.6 0 0 1 8 1.4z"/></svg></td>
<td><svg width="14" height="14" viewBox="0 0 16 16"><path fill="var(--liha)" d="M8 8V1.4A6.6 6.6 0 0 1 14.6 8z"/><path fill="var(--kana)" d="M8 8h6.6A6.6 6.6 0 0 1 8 14.6z"/><path fill="var(--kala)" d="M8 8v6.6A6.6 6.6 0 0 1 1.4 8z"/><path fill="var(--kasvis)" d="M8 8H1.4A6.6 6.6 0 0 1 8 1.4z"/></svg></td>
<td><svg width="18" height="18" viewBox="0 0 16 16"><path fill="var(--liha)" d="M8 8V1.4A6.6 6.6 0 0 1 14.6 8z"/><path fill="var(--kana)" d="M8 8h6.6A6.6 6.6 0 0 1 8 14.6z"/><path fill="var(--kala)" d="M8 8v6.6A6.6 6.6 0 0 1 1.4 8z"/><path fill="var(--kasvis)" d="M8 8H1.4A6.6 6.6 0 0 1 8 1.4z"/></svg></td>
<td><svg width="26" height="26" viewBox="0 0 16 16"><path fill="var(--liha)" d="M8 8V1.4A6.6 6.6 0 0 1 14.6 8z"/><path fill="var(--kana)" d="M8 8h6.6A6.6 6.6 0 0 1 8 14.6z"/><path fill="var(--kala)" d="M8 8v6.6A6.6 6.6 0 0 1 1.4 8z"/><path fill="var(--kasvis)" d="M8 8H1.4A6.6 6.6 0 0 1 8 1.4z"/></svg></td>
</tr>
</table>
</div>
<div class="panel">
<h2>On the board, at 16&nbsp;px</h2>
<div class="board">
<span class="pill big"><span class="i-liha"><svg width="18" height="18" viewBox="0 0 16 16"><path fill="currentColor" fill-rule="evenodd" d="M8 1.8c3.4 0 6.1 2.3 6.1 5.1 0 1.7-1 3.2-2.5 4.1-.4 1.9-1.9 3.2-3.6 3.2-3.4 0-6.1-2.5-6.1-5.6C1.9 5 4.6 1.8 8 1.8zm2.7 7.5a1.6 1.6 0 1 0-3.2 0 1.6 1.6 0 0 0 3.2 0z"/></svg></span>Lihapullat <span class="n">12</span></span>
<span class="pill"><span class="i-kana"><svg width="16" height="16" viewBox="0 0 16 16"><g fill="none" stroke="currentColor" stroke-width="2.6" stroke-linecap="round"><path d="M7.8 8.2l3.1-3.1"/></g><circle cx="5.4" cy="10.6" r="3.6" fill="currentColor"/><circle cx="12.2" cy="3.8" r="2.1" fill="currentColor"/><circle cx="13.4" cy="5.6" r="1.7" fill="currentColor"/></svg></span>Kanacurry <span class="n">8</span></span>
<span class="pill"><span class="i-kala"><svg width="16" height="16" viewBox="0 0 16 16"><path fill="currentColor" fill-rule="evenodd" d="M14.4 8c-1.8 2.7-4.4 4.2-7 4.2-1.3 0-2.6-.4-3.6-1.1L1.4 13.2V2.8l2.4 2.1c1-.7 2.3-1.1 3.6-1.1 2.6 0 5.2 1.5 7 4.2zm-3.6-1.1a.95.95 0 1 0 0 1.9.95.95 0 0 0 0-1.9z"/></svg></span>Lohikeitto <span class="n">9</span></span>
<span class="pill"><span class="i-kasvis"><svg width="16" height="16" viewBox="0 0 16 16"><path fill="currentColor" d="M14 1.6C6.9 1.4 2.6 4.7 2.6 9.5c0 1.2.3 2.2.8 3.1l-1.7 1.7 1.3 1.3 1.7-1.7c.9.5 1.9.8 3.1.8 4.8 0 7-4.4 6.2-13z"/><path fill="none" stroke="var(--card)" stroke-width="1.1" stroke-linecap="round" d="M12.4 3.4L5.1 10.7"/></svg></span>Hernekeitto <span class="n">4</span></span>
<span class="pill"><svg width="16" height="16" viewBox="0 0 16 16"><path fill="var(--liha)" d="M8 8V1.4A6.6 6.6 0 0 1 14.6 8z"/><path fill="var(--kana)" d="M8 8h6.6A6.6 6.6 0 0 1 8 14.6z"/><path fill="var(--kala)" d="M8 8v6.6A6.6 6.6 0 0 1 1.4 8z"/><path fill="var(--kasvis)" d="M8 8H1.4A6.6 6.6 0 0 1 8 1.4z"/></svg>Tortillat <span class="n">4</span></span>
</div>
</div>
<div class="panel">
<h2>What it replaces</h2>
<div class="dotrow"><i class="dot d-liha"></i><span>Lihapullat</span></div>
<div class="dotrow"><i class="dot d-kana"></i><span>Kanacurry</span></div>
<div class="dotrow"><i class="dot d-kala"></i><span>Lohikeitto</span></div>
<div class="dotrow"><i class="dot d-kasvis"></i><span>Hernekeitto</span></div>
<div class="dotrow"><i class="dot d-sek"></i><span>Tortillat</span></div>
</div>
</div>
<script>
const h=document.documentElement,b=document.getElementById('t'),m=['dark','light'];
b.onclick=()=>{const n=m[(m.indexOf(h.dataset.theme)+1)%2];h.dataset.theme=n;b.textContent='Teema: '+(n==='dark'?'tumma':'vaalea');};
</script>
</body>
</html>