Skip to content
154 changes: 154 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,160 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).

## [Unreleased]

### Phase 3.5 — Dashboard rewrite (multi-plan ranked view)

Throws away the Amber-vs-current-plan two-comparator dashboard
(2447 LOC) and rebuilds it as a multi-plan ranked-alternatives view
keyed off the Phase 3.2 / 3.3 / 3.4 sensors. Visual seed lifted from
`assets/dashboard-v3-apple.html` — dark default, Outfit + IBM Plex
Mono, ambient radial bg, semantic accent tokens (no per-provider
colours).

#### Added

- **Full rewrite of `custom_components/pricehawk/www/dashboard.html`**
(~1250 LOC, down from 2447). New card hierarchy per plan section 5.1:
- NAV bar (brand + connection status pill + clock + theme toggle).
- HERO row: current-cost card + savings-vs-best-alt card (with
projected-annual extrapolation).
- PERIOD TABS: `[Today][Week][Month][3 Month][Year]` — clicking a
tab swaps the entity binding for every rollup card to the matching
`_today` / `_week` / `_month` / `_3month` / `_year` sensor in
one tick. Active tab persists to `localStorage['pricehawk-window']`
so re-opens land on the user's last view.
- RANKED ALTERNATIVES table rendered from
`sensor.pricehawk_ranked_alternatives.attributes.alternatives[]`
(already sorted by cheap-rank score in `summarize_for_sensor`).
Click a row → drill-in card slides up below.
- DRILL-IN CARD: peak rate / daily supply / customer type / plan ID
/ cheap-rank score, plus a "Pin as Named Comparator" button that
deep-links to `/config/integrations/integration/pricehawk` (HA
doesn't support per-step deep-linking; locked in plan section 9
REVISIT 4).
- DATA HEALTH FOOTER: `sensor.pricehawk_backfill_status` state
(state-coloured: green=complete, amber=running, red=failed) +
`days_loaded` + `ranked_alternatives.last_run` as relative +
absolute time + alternatives count.
- **Empty-state UI for first-run users** (plan section 5.3 surprise #3):
when `backfill_status.days_loaded < 7`, hero rollup values are
replaced with an "Accruing… [n/365]" pill instead of showing a
misleading `$0.00`. Surfaces clearly that we don't have enough
history yet.
- **CSP `connect-src` extended** to include `ws://*.local:*` +
`wss://*.local:*` so the dashboard works on Ryan's HA Green at
`homeassistant.local` (plan section 5.3 surprise #1). Existing
`localhost` + `*.ui.nabu.casa` entries preserved.
- **`assets/DESIGN.claude.md` — new PriceHawk Dashboard section**
noting divergence: PriceHawk is a dark data-dashboard inside HA's
sidebar, not a warm-canvas editorial site. Inherits typographic
rationale (humanist sans + mono numerics) and the card-as-surface
model + accent-discipline rule, but uses its own token palette.
The rest of the Claude marketing-site spec stays intact.

#### Changed

- **WebSocket auth + URL detection preserved verbatim** from the prior
dashboard:
- `location.protocol === 'https:' ? 'wss://' : 'ws://'` for the WS
URL (AEGIS rule: never hardcode `ws://`).
- Token sourced from URL params first, then `window.parent
.hassConnection`, then `localStorage.hassTokens`, then
`window.parent.localStorage.hassTokens` (AEGIS rule: never
hardcode the token).
- **Per-provider colour tokens deleted** (`--amber-primary`,
`--globird-primary`). Replaced with `--accent-positive` /
`--accent-negative` / `--accent-neutral` / `--accent-warn` — matches
the Phase 3.0 pivot away from provider-specific branding.
- **`dashboard_config.setup_panel_iframe` cache-busting unchanged** —
the existing `?v=<version>.<epoch>` query param survives the
rewrite (it's appended to the URL, doesn't touch dashboard.html
itself). Verified by smoke test; no code change.

#### Removed

- CSV import card, backfill-trigger button, Amber-API winner card,
GloBird TOU strip, Amber forecast strip, sparkline chart, grid-power
gauge, two-provider rate chart, ZeroHero status card — all replaced
by the ranked-alternatives + rollup-sensor model.

#### Notes

- **No new JS framework, no build step.** Vanilla JS only, same
constraint as the prior dashboard. All CSS + JS inlined; no CDN
fetches beyond the Google Fonts stylesheet that the prior dashboard
already used.
- **30s setInterval re-render** for the ranked + footer cards so
relative timestamps ("ran 27s ago / 3h ago") tick forward without
waiting on a state_changed event. Cheap (<1ms per tick on HA Green).
- **XSS hardening**: all CDR-sourced strings (plan_id, display_name,
brand, customer_type) pass through `escapeHtml()` before innerHTML
insertion. Defensive — current registry payloads don't contain
HTML-ish characters, but future ones might.
- **Manual UAT only** for this commit (per plan section 6.3 table —
`3.5 | none | manual on Ryan's HA + JS console`). Local smoke test:
HTML parses cleanly via `html.parser`; JS extracted + run under
Node `--check` + mock-DOM render harness exercising all 5 period
windows + accruing branch + empty-ranked branch + drill render
without throwing.

### Phase 3.4 — Named comparator drill-in

Lets the user pin ONE CDR plan from the ranked alternatives list as a
"named comparator" that runs tick-by-tick (every 30s) alongside the
current plan, instead of only refreshing at the daily rollover.
Surfaces as 5 new rolling-window cost sensors plus a new OptionsFlow
step.

#### Added

- **OptionsFlow `named_comparator` step** — dropdown of the current
ranked alternatives (sourced from `coordinator.data["ranked_alternatives"]`
+ the per-day `_ranking_plan_cache`) plus a "(clear pin)" sentinel.
Aborts with `no_ranked_alternatives` when either the ranked list or
the plan cache is empty (covers the post-install + post-midnight-
cache-reset edge cases). Aborts with `plan_not_in_cache` when the
user's selection no longer maps to a cached body (concurrent eviction).
- **`CONF_NAMED_COMPARATOR_PLAN_ID` + `CONF_NAMED_COMPARATOR_PLAN`** —
new option keys. We persist the FULL `PlanDetailV2` body (not the
summarised form from `cdr.ranking.summarize_for_sensor`) because the
evaluator needs the `tariffPeriod` data the summary deliberately omits.
- **`coordinator.build_named_comparator_provider`** — module-level pure
helper extracted for unit-testability (same justification as
`build_backfill_plan_set`). Called from both `__init__` AND
`rebuild_engine` so a fresh pin lands on the next
`OptionsFlowWithReload` cycle without an HA restart.
- **Coordinator registers the named provider under the literal
`"named"` key** in `_providers`. The existing tick loop ticks it
every 30s, and the daily rollover writes a `"named"` column into
`daily_cost_history` — no new tick path, no new locks.
- **`NamedComparatorRollupSensor` × 5** — `today | week | month |
3month | year` rolling-window cost sensors that read the `"named"`
key from `daily_cost_history`. Registered only when the user has
pinned a plan, so users who haven't opted in don't see five
permanently-unavailable entities. Subclass of the Phase 3.3
`PeriodRollupSensor` base.
- **`strings.json` + `translations/en.json`** — new step + menu entry +
2 abort reasons + 5 entity name/description blocks.
- 14 new tests: 10 in `tests/test_config_flow_phase_3.py` exercising
the new pure-logic `plan_named_comparator_step` decision tree
(full-body persistence guard, plan_not_in_cache branch, dedupe,
default fallback when prior pin evicted), 4 in
`tests/test_coordinator_helpers.py` pinning the
`build_named_comparator_provider` lifecycle.

#### Notes

- **Lock interaction with ranking lock: none.** The named comparator
joins the existing tick loop unchanged. The OptionsFlow step reads
`_ranking_plan_cache` without holding the ranking lock — safe
because the worst-case torn read resolves to the existing abort
path and re-prompts the user.
- **Persistent through ranking churn**: if the pinned plan drops out
of the cheap-rank top-K two weeks later (rate changes), the named
pin keeps showing — it's stored in options, not derived from
ranking. Backfill includes it via `build_backfill_plan_set` so
historical reads are continuous.

### Phase 3.2 — Universal HA-history backfill

Replaces the Amber-API-only backfill with a multi-plan replay over the
Expand Down
85 changes: 85 additions & 0 deletions assets/DESIGN.claude.md
Original file line number Diff line number Diff line change
Expand Up @@ -587,3 +587,88 @@ When photography is used (rare — mostly testimonials), avatars crop to perfect
- Form validation states beyond `{component.text-input-focused}` are not extracted — error / success states would need a sign-up or feedback flow to confirm.
- The actual Claude product surface (claude.ai chat interface) shares some tokens with the marketing site but adds many product-specific components (chat bubbles, message tools, file upload chips, conversation history sidebar) that are out of scope for this marketing-surface document.
- The "agent" / "computer use" demo cards on certain pages display animated Claude controlling a browser — the static screenshot doesn't fully capture the animation chrome.

---

## PriceHawk Dashboard (divergence from this spec)

The PriceHawk HA integration dashboard at
`custom_components/pricehawk/www/dashboard.html` deliberately does NOT
follow the Claude marketing-site spec above. PriceHawk is a dark
**data-dashboard** surfaced inside the Home Assistant sidebar iframe,
not a warm-canvas editorial site, and its visual language is
incompatible with the cream/coral/dark-navy trinity documented in this
file.

### Why divergence

- **Surface context**: PriceHawk renders inside HA's chrome alongside
other dark dashboards (Lovelace, Energy, Logbook). A warm-cream canvas
would look broken next to those panels. Most HA users run dark mode
by default; cream-on-cream would be uncomfortable late at night
during high-tariff windows when the dashboard is actually consulted.
- **Information density**: a data-dashboard with 16+ live entity reads
+ a ranked alternatives table needs tabular numerals, mono digits,
and high-contrast accent colours. The editorial typography stack
(serif display + humanist sans) doesn't suit dense tabular layouts.
- **Brand independence**: PriceHawk is an open-source HACS integration,
not an Anthropic product. The Anthropic coral / radial-spike mark
doesn't apply here. PriceHawk has its own logo (orange hawk-head
glyph at `custom_components/pricehawk/icon.png`).

### What PriceHawk DOES inherit from this spec

- **Typographic rationale**: humanist sans (Outfit, here, vs StyreneB)
for UI text; mono with tabular numerics (IBM Plex Mono, here, vs no
mono in the Claude spec) for all money + rate values. The "use mono
for numbers users compare against each other" rule is unbreakable.
- **Card-as-surface model**: rounded `border-radius` (12-16px),
subtle border, optional 1px gradient top-stroke on hover for
affordance. PriceHawk uses `--card-radius: 16px` matching the Claude
spec's `r-lg: 18px` ballpark.
- **Accent-colour discipline**: ONE positive, ONE negative, ONE
neutral. Don't introduce a fourth accent. Claude uses
primary-coral as its single accent; PriceHawk uses
`--accent-positive` (savings green) + `--accent-negative` (loss red)
+ `--accent-neutral` (info blue) + `--accent-warn` (accruing amber)
— four because the dashboard surfaces four distinct semantic states,
not as decoration.

### PriceHawk token map (for reference)

```css
--bg-base: #070B14 // OLED-friendly true black
--bg-surface: #0C1220
--bg-card: rgba(15,23,42,0.6)
--text-primary: #F1F5F9
--text-secondary: #94A3B8
--text-muted: #64748B
--accent-positive: #10B981 // savings, "you save"
--accent-negative: #EF4444 // loss, "you lose"
--accent-neutral: #38BDF8 // info, current plan / pinned baseline
--accent-warn: #F59E0B // accruing, < 7 days of backfill history
--card-radius: 16px
--card-blur: 20px
```

Light theme inverts via `[data-theme="light"]` selector overriding the
same tokens (canvas: `#F5F6FA`, card: `rgba(255,255,255,0.78)`, accents
shift one stop darker for contrast). Theme persists to
`localStorage['pricehawk-theme']`; first-visit defaults to OS
`prefers-color-scheme`.

### Where to look for PriceHawk's full visual treatment

- `assets/dashboard-v3-apple.html` — the v3 visual seed (1478 LOC
dark-theme mockup; ambient radial bg, noise overlay, Outfit + IBM
Plex Mono). Not the deployed dashboard, but the design-language
source-of-truth.
- `custom_components/pricehawk/www/dashboard.html` — the actual
deployed dashboard at `/local/pricehawk/dashboard.html`. Hierarchy:
nav / hero row (current cost + savings) / period tabs
(today|week|month|3month|year) / ranked alternatives table /
drill-in card / data-health footer.

Don't try to reconcile PriceHawk back into the Claude spec.
They are different products and the visual languages are
deliberately separate.
Loading
Loading