The boxes are not in the Algolia catalogue and their category pages look empty
because the grid calls every option 'Georgia Box' — each option's real name is
only in its own select-basket popup, so listing reads them there.
POST /s/submit/select-basket carries a scope the add-on endpoints do not:
popup-toggle is 'do' for the next delivery or 'so' for the standing order. The
select uses 'do', because a mistaken permanent change is the worse one to undo.
Also makes the popup regexes whitespace-tolerant. The site writes both
openPopup("x","y") and openPopup("x", "y"), and the strict form silently
matched nothing on the basket pages.
6.7 KiB
Changelog
All notable changes to this project are documented here.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
[Unreleased]
Added
actions.py: the write layer — skip a delivery, donate a box, add/remove cart items, subscribe/unsubscribe with a frequency, and add a vacation hold over a date range (distinct from skipping: three weeks away is one hold, not three skips). Parsers for current subscriptions and scheduled holds.tools/compat.pyplus a dailycompat.ymlworkflow: records every assumption this integration makes about freshharvest.com and asserts it against the live site, refreshing the matrix in README and opening an issue on drift.FreshHarvestClient.async_fetch, restored as the shared authenticated-fetch primitive that both the snapshot read and every write action build on.todo.fresh_harvest_box: the order as a to-do list, so Home Assistant's own conversation agent can add and remove items with no bespoke voice code. Adding resolves the name against the site's search index first.switch.fresh_harvest_skip_next_order(a switch, not a button, so the state is readable and reversible) andbutton.fresh_harvest_donate_next_order(a button, because donating cannot be undone).sensor.fresh_harvest_subscriptionsandsensor.fresh_harvest_vacation_holds, each listing the detail in attributes.- A
freshharvest_actionevent fired after every write action, carrying action/success/target/detail so automations can notify on the outcome.
Fixed
-
Subscription parsing matched
.account-item-multi-fields, which is the heading row, so an account with a live subscription reported zero. Cells are now picked by semantic class. Regression covered intests/test_actions.py. -
Restore (un-skip) via
POST /s/submit/restore-delivery, wired toswitch.turn_off. The restore popup only exists once an order is actually skipped, which is why it could not be found until one was. Verified end to end against a live order: skipped Aug 18, restored it, confirmed the account returned to its previous state.
Fixed
-
Subscription parsing matched
.account-item-multi-fields, which is the heading row, so an account with a live subscription reported zero. Cells are now picked by semantic class. Regression covered intests/test_actions.py. -
async_fetchtreated popup bodies and AJAX replies as full pages. Those are fragments with no navigation, so the signed-in heuristic read every one as logged out, re-authenticated pointlessly and then failed. They now passis_page=False. This blocked skip entirely. -
The to-do list mixed produce-box contents with add-ons. Only add-ons can be added and removed — the box is chosen, not assembled — so listing produce invited deletes with no endpoint behind them. The entity is now
todo.fresh_harvest_add_ons; box contents stay read-only onsensor.*_next_delivery_items. -
Produce box switching:
POST /s/submit/select-basket, exposed asselect.fresh_harvest_produce_boxwith all ten boxes. Switching a box is not adding an add-on — you change which box arrives, not what is inside it — so it is a select, where add-ons are a to-do list.
Known gaps
- Entities are deliberately NOT exposed to the conversation agent yet.
Notes
- Actions default to
dry_run=Trueand report exactly what they would submit. Nothing has been executed against a live account yet.
[0.3.0] - 2026-08-03
Prepared for public release as a HACS custom repository.
Added
- Every cost component is now its own entity rather than an attribute: subtotal, box price, add-ons, tax, and delivery fee for the next delivery, plus a total and free-delivery-remaining for the open order.
binary_sensor.fresh_harvest_order_open, which turns off when the cutoff passes — the last moment to change the box.sensor.fresh_harvest_delivery_day.- Parsing for the driver tip, potential Bounty savings, and the free-delivery threshold, with the threshold carried across orders because the progress bar only renders on carts below it.
LICENSE(MIT),hacs.json, and a CI workflow running hassfest, the HACS action, and the test suite.tests/test_translations.py, which cross-checks every entity'stranslation_keyagainst both translation files and fails on an orphan or a missing name.
Changed
- Entities share a
FreshHarvestEntitybase and declare ascope(account,next_order,open_order), so a description states only the field it reads instead of repeating None handling. manifest.jsondocumentation and issue-tracker URLs now point at GitHub; they previously pointed at a private forge that no installer could reach.- Fixture cart identifiers replaced with placeholders.
[0.2.0] - 2026-08-03
Added
sensor.fresh_harvest_next_delivery_add_ons, the combined cost of the add-ons excluding the produce box.add_ons_totalandbox_priceattributes on the item-count sensor.- A test asserting
add_ons_total + box_priceequals the portal's own subtotal, so a mis-parsed price cannot pass silently.
Changed
add_onsattribute entries now carry quantity, unit, and extended price (4 Complete Recovery Smoothie 15.2 fl oz — $17.96) rather than a bare name. Templates reading these strings will need updating.- The test fixture's totals are now internally consistent, so the subtotal reconciliation is a real invariant rather than copied numbers.
[0.1.0] - 2026-08-03
Added
- Config flow taking freshharvest.com portal credentials.
- Session client implementing the two-step login handshake against
/s/popup/loginand/s/submit/login, including the per-sessionLoginSecurityandSubmitTokenanti-replay fields, with one automatic re-authentication when a session lapses. - Dashboard parser reading delivery day, next arrival date, both upcoming
carts, produce-box contents, add-ons, and order totals from a single
GET /p/dashboard/details. - Update coordinator polling every 6 hours, surfacing auth failures as
ConfigEntryAuthFailedso Home Assistant prompts for re-authentication. - Five sensors: next delivery date, next delivery total, next delivery item count, open order delivery date, and shopping window.
- Parser tests covering totals, contents, the locked/open distinction, money parsing, and year rollover on undated cart tabs.
- English translations under
translations/en.json, which is where custom integrations read entity and config-flow strings from. - Example dashboard view under
examples/dashboard-view.yaml: a three-section tab with a delivery countdown, order tiles, and the full box contents.