From 957505209a47c9d61b7bee9bec0b3150fc12dc52 Mon Sep 17 00:00:00 2001 From: flan Date: Mon, 3 Aug 2026 20:59:04 +0000 Subject: [PATCH] Cut 0.4.0: document the control entities and ship the dashboard MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rebuilds the README entity docs around the distinction that kept causing confusion — the produce box is chosen, its contents are read-only, and only add-ons are a list you edit — and re-exports the dashboard view now running, including the date labels that stop the to-do list and the totals reading as a contradiction. --- CHANGELOG.md | 24 ++++ README.md | 37 ++++-- custom_components/freshharvest/manifest.json | 10 +- examples/dashboard-view.yaml | 122 ++++++++++++------- 4 files changed, 136 insertions(+), 57 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f0f56f8..b5718b7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,30 @@ All notable changes to this project are documented here. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.4.0] - 2026-08-03 + +Control, not just reporting: the box can now be managed from Home Assistant. + +### Added + +- Skip and restore, donate, add/remove add-ons, subscribe/unsubscribe, + vacation holds, and produce-box switching — every reversible one verified by + a live round trip that returned the account to its prior state. +- Entities for each: a produce-box select, a skip switch, a donate button, an + add-ons to-do list the built-in conversation agent can drive, and sensors for + subscriptions and vacation holds. +- A `freshharvest_action` event after every write, carrying action/success/ + target/detail so automations can notify on the outcome. +- A four-section dashboard view with the controls in place, exported to + `examples/dashboard-view.yaml`. + +### Notes + +- Donating is the one action never executed: it cannot be undone, so it will be + proven the first time there is a box actually worth giving away. +- Entities are not exposed to the conversation agent by default; that is the + operator's call. + ## [Unreleased] ### Added diff --git a/README.md b/README.md index ad2184b..7b04c72 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,26 @@ The next delivery and the *open* order are usually two different deliveries. Once an order passes its cutoff it locks for packing, and the cart you can still edit is the following week's — so both are exposed separately. +Three things arrive in a delivery and only one is a list you edit: + +| | What it is | Entity | +| --- | --- | --- | +| **Produce box** | Chosen, not assembled. Ten options. | `select.fresh_harvest_produce_box` | +| **Box contents** | Fresh Harvest fills it; read-only. | `produce` attr of `..._next_delivery_items` | +| **Add-ons** | Yours to add and remove. | `todo.fresh_harvest_add_ons` | + +### Controls + +| Entity | Notes | +| --- | --- | +| `select.fresh_harvest_produce_box` | Switches the **next delivery only**, not the standing order | +| `switch.fresh_harvest_skip_next_order` | Skip, and turn back off to restore | +| `button.fresh_harvest_donate_next_order` | Donates the box. **Not reversible** | +| `todo.fresh_harvest_add_ons` | Add/remove items; names resolve via the site's search index | + +Home Assistant's built-in conversation agent can drive the to-do list through +`HassListAddItem`, so no bespoke voice work is needed. + ### The order arriving next | Entity | Example | @@ -45,11 +65,6 @@ edit is the following week's — so both are exposed separately. | `sensor.fresh_harvest_next_delivery_fee` | `5.99` | | `sensor.fresh_harvest_next_delivery_items` | `11` | -`next_delivery_items` carries the contents as attributes: `produce`, `add_ons` -(each with quantity, unit and extended price), `produce_count`, `add_ons_count` -and `box`. The totals sensor carries `driver_tip` and `bounty_savings`, which -are optional or promotional rather than charges. - ### The order you can still change | Entity | Example | @@ -61,15 +76,21 @@ are optional or promotional rather than charges. | `sensor.fresh_harvest_shopping_window` | `Shop tomorrow` | `binary_sensor.fresh_harvest_order_open` is the one to automate on: it turns off -when the cutoff passes, which is the last moment to add anything to the box. -`shopping_window` reads `closed` when nothing is changeable — distinct from -unknown. +when the cutoff passes, the last moment to change the box. ### The account | Entity | Example | | --- | --- | | `sensor.fresh_harvest_delivery_day` | `Tuesdays` | +| `sensor.fresh_harvest_subscriptions` | `1`, with each standing order in attributes | +| `sensor.fresh_harvest_vacation_holds` | `0`, with ranges in attributes | + +## Events + +Every write action fires `freshharvest_action` with `action`, `success`, +`target` and `detail`, so an automation can notify on an add succeeding or a +skip failing. ## Consistency guarantees diff --git a/custom_components/freshharvest/manifest.json b/custom_components/freshharvest/manifest.json index e520b6d..507caea 100644 --- a/custom_components/freshharvest/manifest.json +++ b/custom_components/freshharvest/manifest.json @@ -1,12 +1,16 @@ { "domain": "freshharvest", "name": "Fresh Harvest", - "codeowners": ["@sudolulo"], + "codeowners": [ + "@sudolulo" + ], "config_flow": true, "documentation": "https://github.com/sudolulo/ha-freshharvest", "integration_type": "service", "iot_class": "cloud_polling", "issue_tracker": "https://github.com/sudolulo/ha-freshharvest/issues", - "requirements": ["beautifulsoup4>=4.12"], - "version": "0.3.0" + "requirements": [ + "beautifulsoup4>=4.12" + ], + "version": "0.4.0" } diff --git a/examples/dashboard-view.yaml b/examples/dashboard-view.yaml index a28e9ea..b80d3b4 100644 --- a/examples/dashboard-view.yaml +++ b/examples/dashboard-view.yaml @@ -1,11 +1,16 @@ # Fresh Harvest dashboard view. # -# Four sections: the next delivery, its cost broken down per line, what is in -# the box, and the order that can still be changed. +# Four sections: the next delivery, what is in the box, the controls, and the +# add-ons you can still edit. +# +# The split matters. The produce box is CHOSEN (a select), its contents are +# read-only, and only add-ons are add/removable (a to-do list). The to-do list +# edits the OPEN order while the totals describe the ARRIVING one, which is why +# each is labelled with its date -- unlabelled, they read as a contradiction. # # To use it, open your dashboard, choose "Edit dashboard" -> "Raw configuration -# editor", and paste this under `views:`. It needs the entities this -# integration creates and nothing else — no custom cards. +# editor", and paste this under `views:`. It needs only the entities this +# integration creates -- no custom cards. type: sections max_columns: 4 @@ -44,37 +49,6 @@ sections: name: Items icon: mdi:basket-check color: light-green -- type: grid - cards: - - type: heading - heading: Cost Breakdown - heading_style: title - icon: mdi:receipt-text-outline - - type: tile - entity: sensor.fresh_harvest_next_delivery_box_price - name: Produce box - icon: mdi:package-variant - color: green - - type: tile - entity: sensor.fresh_harvest_next_delivery_add_ons - name: Add-ons - icon: mdi:cart-plus - color: purple - - type: tile - entity: sensor.fresh_harvest_next_delivery_subtotal - name: Subtotal - icon: mdi:calculator - color: grey - - type: tile - entity: sensor.fresh_harvest_next_delivery_tax - name: Tax - icon: mdi:bank - color: grey - - type: tile - entity: sensor.fresh_harvest_next_delivery_fee - name: Delivery fee - icon: mdi:truck-outline - color: grey - type: markdown content: |- {%- set fee = states('sensor.fresh_harvest_next_delivery_fee') | float(-1) -%} @@ -112,24 +86,31 @@ sections: - type: grid cards: - type: heading - heading: Still Open + heading: Manage heading_style: title - icon: mdi:cart-arrow-right + icon: mdi:tune-variant - type: tile - entity: binary_sensor.fresh_harvest_order_open - name: Can still change - icon: mdi:pencil-outline - color: amber + entity: select.fresh_harvest_produce_box + name: Produce box + icon: mdi:package-variant-closed + color: green + features: + - type: select-options - type: tile - entity: sensor.fresh_harvest_open_order_delivery - name: Delivery - icon: mdi:calendar-arrow-right + entity: switch.fresh_harvest_skip_next_order + name: Skip next order + icon: mdi:calendar-remove color: amber - type: tile entity: sensor.fresh_harvest_shopping_window name: Shopping window icon: mdi:clock-alert-outline color: orange + - type: tile + entity: sensor.fresh_harvest_open_order_delivery + name: Open order + icon: mdi:calendar-arrow-right + color: amber - type: tile entity: sensor.fresh_harvest_open_order_total name: Running total @@ -145,8 +126,57 @@ sections: {%- elif left > 0 -%} The **{{ when }}** order needs **${{ '%.2f' | format(left) }} more** for free delivery. {%- endif -%} + - type: tile + entity: button.fresh_harvest_donate_next_order + name: Donate this box + icon: mdi:hand-heart + color: pink + hide_state: true - type: markdown content: |- - Once an order passes its cutoff it locks for packing, and the following week's cart opens. + Donating gives the whole box away and **cannot be undone**. - [Change this order →](https://freshharvest.com/p/dashboard/manage-orders) + [Manage on freshharvest.com →](https://freshharvest.com/p/dashboard/manage-orders) +- type: grid + cards: + - type: heading + heading: Add-ons & Standing Orders + heading_style: title + icon: mdi:cart-plus + - type: markdown + content: |- + {%- set d = states('sensor.fresh_harvest_open_order_delivery') -%} + {%- if d not in ['unknown', 'unavailable', 'none'] -%} + Editing the **{{ (d | as_datetime).strftime('%b %-d') }}** order — the one still open. + {%- else -%} + No order is open for changes right now. + {%- endif -%} + - type: todo-list + entity: todo.fresh_harvest_add_ons + display_order: alpha_asc + - type: tile + entity: sensor.fresh_harvest_next_delivery_add_ons + name: Arriving box add-ons + icon: mdi:cart-plus + color: purple + - type: tile + entity: sensor.fresh_harvest_subscriptions + name: Subscriptions + icon: mdi:autorenew + color: cyan + - type: markdown + content: |- + {%- set items = state_attr('sensor.fresh_harvest_subscriptions', 'items') or [] -%} + {%- if items -%} + **Repeating every week** + {% for i in items %} + - {{ i }} + {%- endfor %} + {%- else -%} + _No standing orders beyond the box._ + {%- endif -%} + - type: tile + entity: sensor.fresh_harvest_vacation_holds + name: Vacation holds + icon: mdi:airplane + color: blue