Skip to content

Commit 663207d

Browse files
committed
Document the new dashboard and cut 0.0.20
The README described the page that existed before this branch, and eight things in it had gone stale - some since 0.0.18: * "searchable, sortable table" - it is a filtered list now * the compact view was said to carry nine columns; Barcode made it ten * "drink-window colouring (green = ready, red = too early or past)" - too-early is blue and the final year is amber * the drink-window sensors were missing from the feature list entirely * the summary card example labelled the diagnostic entity "Connection", which stopped being true in 0.0.18 when it began reporting a timestamp * "About drink-window cards" said to sort by the FROM/TO columns, which no longer exist - and the honest answer is now much better, since the chips and the sensors both do it * the test count said 149 * nothing told a contributor that mypy needs Home Assistant installed, which is the difference between a real check and a silent pass Two of those are pinned by tests now: the README's column count is read against COMPACT_FIELDS, and its state labels are read out of cellar.html itself, so renaming one there fails until the README follows. Both were confirmed to fail against a sabotaged README rather than assumed to work. 0.0.20 carries the dashboard rebuild and the coordinator's hass cleanup. The notes lead with the one visible change: a wine in the last year of its window now reads "Drink this year" in amber rather than red. Red said "too late" while the sensors counted it as ready, so the page and the sensors disagreed about the same bottle. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LLPEGFSy3fLEuXNUPAPWR4
1 parent 077662d commit 663207d

4 files changed

Lines changed: 180 additions & 22 deletions

File tree

README.md

Lines changed: 77 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
# CellarTracker for Home Assistant
55

66
Brings your [CellarTracker](https://cellartracker.com) wine cellar into Home Assistant: summary
7-
sensors for bottle count and cellar value, plus a searchable, sortable dashboard of every bottle.
7+
sensors for bottle count, cellar value and drinking windows, plus a dashboard that filters every
8+
bottle down to the ones you can drink tonight.
89

910
> **Disclaimer**
1011
> This is a personal project. It is not affiliated with, connected to, or endorsed by
@@ -45,11 +46,14 @@ and error semantics; its own `requests`-based transport sets no timeout and is n
4546

4647
- Two summary sensors — total bottle count and total cellar value — with proper device classes,
4748
units and state classes, so both feed Home Assistant's long-term statistics.
49+
- Two drink-window sensors — how many bottles are ready to drink, and how many are past their
50+
window — derived at poll time, so they cost nothing and add no entity per bottle.
4851
- A selectable currency, so the value sensor is denominated correctly.
49-
- A diagnostic status sensor.
52+
- A diagnostic sensor reporting when the cellar last synchronised.
5053
- A REST endpoint exposing full per-bottle detail, and a self-contained dashboard page that
51-
renders it as a searchable, sortable table with drink-window highlighting. The page ships with
52-
the integration and is served from it, so there is nothing to copy into `<config>/www`.
54+
filters it by drinking window, searches it across five fields and shows each bottle's position
55+
in its window. The page ships with the integration and is served from it, so there is nothing
56+
to copy into `<config>/www`.
5357
- Reauthentication: if your password changes, Home Assistant prompts you to re-enter it rather
5458
than silently failing.
5559
- One account per installation, enforced by the config flow, so there is no ambiguity
@@ -108,7 +112,7 @@ state attributes:
108112

109113
```
110114
GET /api/cellartracker/inventory # every bottle, as JSON
111-
GET /api/cellartracker/inventory?view=compact # the same bottles, nine columns
115+
GET /api/cellartracker/inventory?view=compact # the same bottles, ten columns
112116
GET /api/cellartracker/settings # the configured currency and its symbol
113117
```
114118

@@ -127,6 +131,7 @@ Each bottle in the response carries CellarTracker's own column names. The ones t
127131
| `Vintage` | Vintage year, `0` for non-vintage |
128132
| `Producer` | Producer name |
129133
| `Location` / `Bin` | Where the bottle is stored |
134+
| `Barcode` | The bottle's barcode, if you have scanned one |
130135
| `Size` | Bottle size, e.g. `750ml` |
131136
| `Valuation` | Current value, coerced to a float (`0.0` if unparseable) |
132137
| `Price` | What you paid |
@@ -269,9 +274,41 @@ title: My Wine Collection
269274
The page reads your live Home Assistant session from the parent frame, so it needs no token or
270275
credential of its own. Open it embedded in a dashboard, not as a standalone browser tab.
271276
272-
It gives you search across wine name, location and bin; sortable columns; bottle values formatted
273-
in your configured currency; links to each wine on CellarTracker; drink-window colouring (green =
274-
ready, red = too early or past); and light/dark theme following your Home Assistant theme.
277+
### What it gives you
278+
279+
**Four filter chips**, each carrying its count, so a 150-bottle cellar becomes a short list:
280+
281+
| Chip | Shows |
282+
|---|---|
283+
| **All wines** | Everything, including bottles with no recorded window |
284+
| **Ready to drink** | The current year falls inside the drinking window |
285+
| **Past window** | The window ended before this year |
286+
| **Needs aging** | The window has not opened yet |
287+
288+
The Ready and Past counts are the same numbers the sensors report — they are checked against each
289+
other by the test suite, so the chip and the sensor card beside it cannot disagree.
290+
291+
**Search** across wine name, vintage, location, bin and barcode. Every word has to match, so
292+
`chianti 2023` narrows rather than finding nothing.
293+
294+
**Sort** by wine, vintage, value, drink-by year, bin or location, in either direction. Bottles
295+
with nothing recorded for the chosen column sort last rather than heading the list.
296+
297+
**Each bottle** shows its name, vintage, location and bin, its value in your configured currency,
298+
and a bar showing where this year sits inside its drinking window:
299+
300+
| Label | Meaning |
301+
|---|---|
302+
| **Ready** (green) | Inside the drinking window |
303+
| **Drink this year** (amber) | The final year of the window — urgent, not expired |
304+
| **Past window** (red) | The window ended before this year |
305+
| **Needs aging** (blue) | Not open yet |
306+
| **No window** (grey) | CellarTracker has no recommendation for this bottle |
307+
308+
Tapping a bottle opens a drawer with its barcode, full window, value, a **Copy bin** button and a
309+
link to the wine on CellarTracker.
310+
311+
The page follows your Home Assistant light/dark theme, and every control is sized for a thumb.
275312

276313
The card needs no account parameter — one account is supported per installation, so the
277314
endpoints have nothing to disambiguate. A stale `?entry_id=...` left over from a card configured
@@ -300,8 +337,10 @@ entities:
300337
name: Bottles
301338
- entity: sensor.<account>_total_value
302339
name: Cellar value
303-
- entity: sensor.<account>_status
304-
name: Connection
340+
- entity: sensor.<account>_ready_to_drink
341+
name: Ready to drink
342+
- entity: sensor.<account>_last_synchronised
343+
name: Last synchronised
305344
```
306345

307346
### Markdown card with an average
@@ -419,13 +458,18 @@ window. **That is not possible with the entities this integration creates**, bec
419458
per-bottle entities to filter — `auto-entities` works over the entity registry, and bottles are
420459
not in it.
421460

422-
Drink-window filtering happens in the dashboard page instead, which colours `BeginConsume` and
423-
`EndConsume` per bottle: green when the year is in range, red when the bottle is too young or
424-
past its window. Sort by either column to bring the relevant bottles together.
461+
There are two answers that do not need per-bottle entities.
462+
463+
**For a count**, use the sensors: `sensor.<account>_ready_to_drink` and
464+
`sensor.<account>_past_drinking_window` are ordinary numeric entities, so they work in any card,
465+
template or automation.
425466

426-
If you want drink-window data in Lovelace proper, the missing piece is per-bottle entities — see
427-
[Bottle-level data](#bottle-level-data) for why they are not created by default. Please open an
428-
issue if this matters to you; it is a reasonable feature to add behind an opt-in, given the
467+
**For the actual list**, use the dashboard page and its **Ready to drink** chip. That is what the
468+
chips are for, and the counts match the sensors exactly.
469+
470+
If you want the bottles themselves in Lovelace proper, the missing piece is per-bottle entities —
471+
see [Bottle-level data](#bottle-level-data) for why they are not created by default. Please open
472+
an issue if this matters to you; it is a reasonable feature to add behind an opt-in, given the
429473
recorder cost is the user's to accept.
430474

431475
---
@@ -519,16 +563,28 @@ logger:
519563

520564
```bash
521565
pip install -r requirements_test.txt
522-
python -m pytest # 149 tests
566+
python -m pytest
523567
ruff check .
524568
```
525569

526570
The test suite stubs the handful of `homeassistant` symbols the integration imports rather than
527-
depending on `pytest-homeassistant-custom-component`, so it installs in seconds and runs in under
528-
a second. The dashboard tests execute `cellar.html`'s real script under Node, so install Node to
529-
run them — they skip if it is absent.
571+
depending on `pytest-homeassistant-custom-component`, so it installs in seconds and runs in a few.
572+
The dashboard tests execute `cellar.html`'s real script under Node, so install Node to run them —
573+
they skip if it is absent.
574+
575+
Type checking needs Home Assistant itself, which the test suite deliberately does not install:
576+
577+
```bash
578+
python3.13 -m venv .typecheck
579+
.typecheck/bin/pip install -r requirements_mypy.txt
580+
.typecheck/bin/python -m mypy
581+
```
582+
583+
Running `mypy` against an interpreter without Home Assistant is worse than not running it: every
584+
`homeassistant.*` import resolves to `Any`, and the check passes over code it never looked at.
585+
The configuration refuses to do that, so it will fail loudly rather than mislead you.
530586

531-
CI runs the suite on Python 3.12 and 3.13, plus `ruff`, `hassfest` and HACS validation.
587+
CI runs the suite on Python 3.12 and 3.13, plus `ruff`, `mypy`, `hassfest` and HACS validation.
532588

533589
### Cutting a release
534590

custom_components/cellar_tracker/manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,5 @@
1515
"requirements": [
1616
"cellartracker==1.1.1"
1717
],
18-
"version": "0.0.19"
18+
"version": "0.0.20"
1919
}

release_notes/0.0.20.md

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
## The dashboard now answers the question you actually have
2+
3+
A cellar app exists to tell you what to open tonight. The bottle table could not: it listed every wine with two years beside it and left you to do the arithmetic, 151 times, on a phone.
4+
5+
There are four filter chips now, each carrying its count.
6+
7+
| Chip | Shows |
8+
|---|---|
9+
| **All wines** | Everything, including bottles with no recorded window |
10+
| **Ready to drink** | This year falls inside the drinking window |
11+
| **Past window** | The window closed before this year |
12+
| **Needs aging** | The window has not opened yet |
13+
14+
The Ready and Past counts are the same numbers your sensors report, so a chip and the sensor card beside it cannot disagree.
15+
16+
## Every bottle shows where it is in its life
17+
18+
Instead of `FROM 2027 TO 2032`, each bottle gets a bar showing where this year sits inside its window, and a label saying what to do about it:
19+
20+
| Label | Meaning |
21+
|---|---|
22+
| **Ready** | Inside the drinking window |
23+
| **Drink this year** | The final year — urgent, not gone |
24+
| **Past window** | The window closed before this year |
25+
| **Needs aging** | Not open yet |
26+
| **No window** | CellarTracker has no recommendation for this bottle |
27+
28+
Colour is never the only signal, so the labels are readable if you cannot easily tell red from green, or if you are outside in the sun.
29+
30+
## ⚠️ One change you will notice
31+
32+
**A wine in the last year of its window is no longer painted red.** It now reads **Drink this year** in amber.
33+
34+
Red said "too late". The integration's own sensors have always counted that bottle as ready to drink, because the last year of a window is still inside it — so the page and the sensors were telling you different things about the same bottle. They agree now, and the page says what it means.
35+
36+
## Tap a bottle
37+
38+
Bottles expand. Inside: the barcode, the full drinking window, the value, a **Copy bin** button and a link to the wine on CellarTracker.
39+
40+
The bin and location are now on the row itself, too. They used to be hidden on narrow screens — which meant the phone, the one device you actually carry to the rack, was the one that could not show you where the bottle was.
41+
42+
## Search that works
43+
44+
Search now covers wine name, **vintage**, location, bin and **barcode**, and every word has to match. `chianti 2023` finds your 2023 Chiantis. Previously the vintage was not searchable at all and that search found nothing.
45+
46+
## Sorting
47+
48+
Sort by wine, vintage, value, drink-by year, bin or location, in either direction. Bottles with nothing recorded for that column now sort to the bottom rather than heading the list — a shelf of NV bottles no longer buries your oldest vintages.
49+
50+
## Also in this release
51+
52+
* The bottle list is **touch-sized throughout**: nothing you can tap is smaller than a fingertip, and the search box no longer makes iOS zoom when you tap it.
53+
* On a desktop screen each bottle collapses to a single dense line, so you see roughly twice as many at once.
54+
* **Copy bin** tells the truth. Home Assistant is usually reached over plain `http` on your home network, where browsers refuse clipboard access outright — the button now falls back to a method that works there, and says **Could not copy** rather than claiming success if it genuinely cannot.
55+
* The compact inventory API carries the barcode now, so anything you built against `?view=compact` gains a field and loses none.
56+
57+
## Nothing to reconfigure
58+
59+
No new settings, no renamed entities, no changed entity IDs, and the same minimum of **Home Assistant 2024.11**. If your dashboard card points at `/cellartracker/cellar.html` it picks all this up on its own.
60+
61+
## Under the hood
62+
63+
445 tests, up from 359. The dashboard's logic — filtering, searching, sorting, drink-window states — is now tested directly rather than not at all, including a check that runs the page's JavaScript and the integration's Python over the same cellar and compares what they count.
64+
65+
**Full Changelog**: https://github.com/GuvHas/cellartracker/compare/0.0.19...0.0.20

tests/test_dashboard_packaging.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,3 +129,40 @@ def test_docs_do_not_ask_users_to_copy_a_file_hacs_never_installed(name):
129129
assert "custom_components/www" not in DOCS[name], (
130130
f"{name} still points at a path that does not exist after an install"
131131
)
132+
133+
134+
# --------------------------------------------------------------------------
135+
# Claims that go stale silently
136+
# --------------------------------------------------------------------------
137+
def test_the_readme_counts_the_compact_columns_correctly():
138+
"""It said nine for as long as there were nine, and then Barcode arrived."""
139+
from cellar_tracker.const import COMPACT_FIELDS
140+
141+
words = {
142+
9: "nine", 10: "ten", 11: "eleven", 12: "twelve", 13: "thirteen",
143+
14: "fourteen", 15: "fifteen",
144+
}
145+
expected = words.get(len(COMPACT_FIELDS))
146+
assert expected, f"add {len(COMPACT_FIELDS)} to this table"
147+
assert f"the same bottles, {expected} columns" in DOCS["README.md"], (
148+
f"the README should say the compact view has {expected} columns"
149+
)
150+
151+
152+
def test_the_readme_describes_the_states_the_page_actually_shows():
153+
"""The colouring changed in 0.0.20 and the README described the old one.
154+
155+
Read out of the page rather than restated here, so the two cannot drift:
156+
a label renamed in cellar.html has to be renamed in the README too.
157+
"""
158+
import re
159+
160+
page = (COMPONENT / "www" / "cellar.html").read_text()
161+
labels = re.search(r"const STATE_LABEL = \{(.*?)\};", page, re.S)
162+
assert labels, "cellar.html no longer declares STATE_LABEL"
163+
164+
described = set(re.findall(r"'([^']+)'", labels.group(1)))
165+
described.add("Drink this year") # the urgent label, set inline
166+
167+
missing = sorted(label for label in described if label not in DOCS["README.md"])
168+
assert not missing, f"the README never mentions the state(s) {missing}"

0 commit comments

Comments
 (0)