Plant Dashboard ("Lindsay's Plants") — Design Spec¶
Date: 2026-06-17 Status: Approved for planning Author: Louis (via Claude)
Goal¶
A brand-new, standalone Home Assistant dashboard — not a tab on the Command Dashboard — dedicated to indoor-plant watering, designed for Lindsay on an iPhone in portrait. It must look and feel polished (she is exacting about UX/UI), reusing the Midnight Glass design language established by the Desk Companion dashboard. The dashboard is glance-only: she opens it, the colors and rings tell her everything, and an optional drawer lets her tune each plant's thresholds. No editing on the main view, no textual "needs water" summary.
Design Principles¶
- The ring says it all. Each plant is a tile with one moisture ring. Its fill is the absolute moisture (0–100%, objective); its color is plant-subjective — red/yellow/green derived from that plant's own thresholds. A 40% Monstera can be green while a 40% fiddle is red.
- No summary line. Drop any "N plants need water" banner. Color carries the urgency.
- She owns the thresholds. A bottom drawer lets Lindsay set each plant's red and yellow percentages with a dual-handle slider. Green is implicitly "above yellow."
- Glance-only main view. Reading, not interacting, is the default. The only interaction is opening the settings drawer.
Plant Roster¶
Seven indoor plants, each with live ThirdReality Zigbee soil sensors. Canonical moisture entity is sensor.<slug>_moisture (a duplicate _soil_moisture exists and reports the same value — do not use it). Slugs and seeded default thresholds (by plant type):
| Plant | Slug | Moisture entity | Default Red % | Default Yellow % |
|---|---|---|---|---|
| Bird of Paradise | bird_of_paradise | sensor.bird_of_paradise_moisture | 25 | 35 |
| Fiddle Leaf 1 | fiddle_leaf_1 | sensor.fiddle_leaf_1_moisture | 30 | 40 |
| Fiddle Leaf 2 | fiddle_leaf_2 | sensor.fiddle_leaf_2_moisture | 30 | 40 |
| Maury River Fiddle Leaf | maury_river_fiddle_leaf | sensor.maury_river_fiddle_leaf_moisture | 30 | 40 |
| Monstera 1 | monstera_1 | sensor.monstera_1_moisture | 20 | 30 |
| Monstera 2 | monstera_2 | sensor.monstera_2_moisture | 20 | 30 |
| Monstera 3 | monstera_3 | sensor.monstera_3_moisture | 20 | 30 |
Defaults are seeded values only — Lindsay tunes them in the drawer. Each plant also has _battery and _temperature sensors available but out of scope for v1 (glance-only; no battery/temp on the main view).
Architecture¶
Dashboard registration¶
A new YAML-mode dashboard file plant_dashboard.yaml, registered in configuration.yaml under the existing lovelace.dashboards: block (alongside desk-companion). HA requires the URL path to contain a hyphen, so the path is plant-care (not /plants).
lovelace:
mode: storage
dashboards:
desk-companion:
# ...existing...
plant-care:
mode: yaml
title: Plants
icon: mdi:flower-tulip
show_in_sidebar: true
filename: plant_dashboard.yaml
Deployed as a config file via CI → triggers a full ha core restart (same path as desk_companion.yaml).
Helpers (created in configuration.yaml)¶
- 14×
input_number—plant_<slug>_redandplant_<slug>_yellowfor all 7 plants. Each:min: 0,max: 100,step: 1,mode: slider,unit_of_measurement: "%",icon: mdi:water-percent. Initial values per the roster table. - 1×
input_boolean—plant_settings_open(drawer open/closed toggle),icon: mdi:tune.
These are real entities, so they participate in automations/state and survive restarts. They are referenced by the dashboard's conditional drawer and by the rings' color logic.
View layout (single panel view)¶
One view, not panel mode (we want vertical scroll on a phone). Midnight Glass navy radial-gradient background applied via card_mod on the view (same gradient token as Desk Companion). Top-to-bottom:
- Header — a
mushroom-title-card-style glass header: "Plants" title + subtle subtitle (e.g. last-updated). No urgency count. - Gallery — a 2-column grid (
layout-cardgridorgridcard withgrid-template-columns: 1fr 1fr) of 7 plant tiles (custom:button-card). Odd count → last tile sits alone in the left column, acceptable. - Settings entry tile — a full-width glass
button-card"⚙ Adjust watering levels ▾" that togglesinput_boolean.plant_settings_open. - Settings drawer — a
conditionalcard (visible whenplant_settings_openison) containing 7 per-plant rows.
Plant tile (the core component)¶
Each tile is a custom:button-card, aspect ratio ~1:1.15, glass border, overflow: hidden. Structure (matching design-mockup-v2.html):
- Background: the plant's photo at
/local/plant-photos/<slug>.jpgas abackground-image, with a bottom-to-transparent dark fade overlay for text legibility. Until real photos exist, fall back to a per-plant illustration placeholder: a hue-varied radial gradient + a large low-opacity 🌿 glyph. (Photo presence is a static choice in the card config, swapped per plant as photos arrive — no runtime file-existence check.) - Moisture ring (top-right corner): a
custom_fieldrendering an HTML element whose background isconic-gradient(<color> 0 <m>%, rgba(255,255,255,.2) <m>%), with an inner dark disc showing<m>%. Built with button-card[[[ ]]]JS templates: m=Math.round(states['sensor.<slug>_moisture'].state)red=states['input_number.plant_<slug>_red'].state,yellow=…_yellow- color:
m < red ? '#ef4444' : m < yellow ? '#fbbf24' : '#34d399' - Name overlaid bottom-left, white, text-shadow for legibility.
- Trend sparkline: a small axis-less
custom:apexcharts-card(sparkline mode) directly beneath the name showing the last 48h ofsensor.<slug>_moisture. Fixed pixel height (per apexcharts learnings,%heights don't resolve). Single smoothed line, no fill needed, muted cyan stroke (#7fd4e8). One apexcharts card embedded per tile (7 total).
Tile tap target: tapping a tile does nothing destructive (glance-only). Optional: tap_action: none to avoid the more-info dialog, keeping it calm.
Settings drawer rows¶
conditional on input_boolean.plant_settings_open == on. Inside, one row per plant (custom:button-card or glass container) containing:
- Plant name + live moisture readout (
now <m>%). - A
custom:range-slider-card(Gasp96, installed via HACS custom repository) bound to that plant's twoinput_numbers: - low handle →
input_number.plant_<slug>_red - high handle →
input_number.plant_<slug>_yellow - The card's track shows the red→yellow→green zones (verify at build time; if the card cannot color zones, render a thin
button-cardzone-preview bar above the slider usinglinear-gradient(90deg, #ef4444 0 R%, #fbbf24 R% Y%, #34d399 Y% 100%)). - A one-line legend: "Water below R% · Soon below Y% · Happy above Y%".
The drawer constraint red ≤ yellow is enforced softly (legend + visual); v1 does not hard-clamp the inputs. (Future: an automation could clamp yellow ≥ red.)
Dependencies (custom cards)¶
All Lovelace resources must be registered (HACS auto-registers on install):
| Card | Source | Status |
|---|---|---|
custom:button-card | RomRider | installed (used by Desk Companion) |
custom:apexcharts-card | RomRider | installed (/local/custom-cards/apexcharts-card.js, v2.1.1) |
custom:card-mod | thomasloven | installed |
custom:range-slider-card | Gasp96 (custom repo) | installed 2026-06-17 |
custom:layout-card (if used for grid) | thomasloven | verify installed; else use core grid card |
Photos¶
- Path:
/config/www/plant-photos/<slug>.jpg→ served at/local/plant-photos/<slug>.jpg. - v1 ships with illustration placeholders (hue-varied gradient + 🌿). Real photos are dropped in later by
scp-ing Lindsay's images to/config/www/plant-photos/and flipping each tile's background to the photo path — no dashboard restart needed for/local/assets (browser cache aside). - This is a manual, per-plant follow-up; not a blocker for shipping.
CI / Testing Wiring¶
plant_dashboard.yaml is a new deployable HA config file. Required changes mirror how desk_companion.yaml was wired in:
.github/workflows/ci.yml- Add
plant_dashboardto thechangesjob path-filter regex (the^(automations|configuration|…|desk_companion)\.yaml$alternation). - Add
plant_dashboard.yamlto theHA_FILESlist so the deploy jobscps it. It is a non-automations config → restart mode. tests/conftest.py— addplant_dashboard.yamlto the set of filesall_referenced_entities()scans, so everysensor.<slug>_moisture/input_number.*/input_boolean.*reference is validated against the entity snapshot.- Entity snapshot / allowlist
- The 14
input_numberand 1input_booleanhelpers do not exist intests/fixtures/entities.txtuntil after the config deploys. To keep the test suite green on the same PR, add them totests/fixtures/entities_allow.txtwith a justifying comment ("created by configuration.yaml in this change; snapshot after deploy"). - After the change deploys, run
make snapshotand commit the refreshedentities.txt, then remove the temporary allowlist entries. - The 7
sensor.<slug>_moistureentities already exist inentities.txt— no action. make test/make lintmust pass locally before push (yamllint on the new file, entity-ref check).
User Manual¶
Update docs/garden.md (the household-facing MkDocs page for plants) with a "Plant Dashboard" section: what the rings mean (fill = wetness, color = that plant's needs), how to open the dashboard, and how to adjust watering levels in the drawer. The MkDocs --strict build must still pass (valid internal links/anchors). This is part of the same change per the repo's "update the manual with the feature" rule.
Out of Scope (v1 / future)¶
- Battery and soil-temperature display on tiles.
- Outdoor plants / Rachio irrigation / Ecowitt weather (separate Garden project).
- Push notifications for thirsty plants (an automation already exists:
automation.garden_low_plant_moisture_alert— unchanged here). - Hard-clamping yellow ≥ red via automation.
- "Mark as watered" button / watering history.
- Real plant photos (manual follow-up after launch).
Success Criteria¶
- New dashboard appears in the sidebar at
/plant-care, titled "Plants", renders correctly on an iPhone in portrait. - 7 tiles, each with a moisture ring whose fill = live moisture and color = per-plant threshold logic, plus a 48h trend sparkline.
- No "needs water" summary text anywhere.
- Settings drawer toggles open/closed; each plant has a working dual-handle slider that writes its red/yellow
input_numbers; ring colors update live as thresholds change. make testpasses; CI deploys the file; manual page updated andmkdocs build --strictpasses.