Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Working on Amber Compare & Export

A browser-only tool for Australian electricity bills (Amber Electric vs other plans, bill checks, a solar and battery planner). Read these first:

- [README.md](README.md): what the app does, for users
- [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md): modules, data model, each tab's flow, the planner engine, storage, releases
- [docs/FRONTEND.md](docs/FRONTEND.md): the UI contract (IDs, data attributes, help keys, CSP) and known UI issues
- [docs/REVIEW.md](docs/REVIEW.md): invariants, risk areas, technical debt, test map

## Commands

```bash
npm install
npm run build:css # after changing any Tailwind class in index.html or js/; commit css/app.css
npm run test:unit # Node unit checks (fast)
npm test # unit checks + Playwright (starts scripts/static-server.js on :4173)
npx playwright test tests/planner.spec.js --workers=2 # one spec
npm run serve # http://127.0.0.1:4173
```

If Playwright can't find its browser, point it at an installed Chromium with `launchOptions.executablePath` in a local config, not by editing `playwright.config.js`. Don't leave your own server running on port 4173 during a test run: Playwright reuses it.

## Rules of the codebase

- **No build step for JS, no framework, no ES modules.** Each `js/*.js` is an IIFE adding to `window.Amber`; the load order in `index.html` matters (`app.js` is last). New files need a `<script>` tag with `?v=<version>`, and the unit tests `require` them in order.
- **CSP**: no inline scripts or styles, no `style="…"` in HTML or HTML strings, no third-party hosts. `element.style.x = …` from JS is fine.
- **Tailwind classes must appear literally** in `index.html` or `js/`, or be safelisted in `tailwind.config.js`.
- **IDs, `data-*` hooks and visible wording are used by the tests.** Change them together with `tests/*.spec.js`.
- **Every `data-help="key"` needs an entry in `Amber.HELP`** (`js/help.js`), and new inputs and table headings should get one.
- **Escape anything from data or the user** with `Amber.escapeHTML` before `innerHTML`.
- **Keep pricing in one place.** Compare, month by month, Bill Check, Free? and the planner must agree on the same readings; `tests/unit-planner.js` checks the planner against Compare.
- **Missing readings are never zero; NEM time is UTC+10 interval-end; templates are GST-inclusive** (see REVIEW.md invariants).
- **Calculation modules stay DOM-free** so they run under Node.
- **Writing style for the UI**: plain English for householders, Australian spelling, short sentences, units kWh, kW, c/kWh and $.

## Releasing a change

1. Bump the version everywhere it appears:
- `index.html`: the `<title>`, "Version X." in the header, and every `?v=X`;
- `js/constants.js`: `Amber.APP_VERSION`;
- `tests/unit-time.js`: the version assert.
2. Update `README.md` and `docs/` if behaviour or structure changed.
3. Run `npm run build:css` if classes changed, then `npm test`.
4. Open a pull request. Merge only after the GitHub "Tests" check passes (GitHub Pages serves `main`).
139 changes: 92 additions & 47 deletions README.md

Large diffs are not rendered by default.

228 changes: 228 additions & 0 deletions docs/ARCHITECTURE.md

Large diffs are not rendered by default.

134 changes: 134 additions & 0 deletions docs/FRONTEND.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
# Frontend guide

What the UI is today, what any redesign has to keep working, and where it could be better. Read with [ARCHITECTURE.md](ARCHITECTURE.md) (code structure) and [REVIEW.md](REVIEW.md) (review notes).

## Constraints a redesign must respect

1. **No build step for JavaScript.** Classic scripts loaded in order from `index.html`, sharing `window.Amber`. A framework or bundler would be a project-level decision, not part of a visual refresh. The page must keep working from `file://` and GitHub Pages.
2. **Content Security Policy** (`index.html`): `script-src 'self'; style-src 'self'`. That rules out inline `<script>`, inline `<style>`, `style="…"` attributes in HTML or in HTML strings built by JS, and CDN fonts or scripts. Setting `element.style.width` etc. from JS is allowed, and is how progress bars are sized. Fonts must be self-hosted (today the system font stack is used).
3. **Tailwind is prebuilt.** Classes must appear literally in `index.html` or `js/**/*.js` for `npm run build:css` to include them; classes assembled from pieces at runtime must be listed in `tailwind.config.js` `safelist`. Commit the rebuilt `css/app.css`.
4. **IDs and data attributes are an API.** The JS looks elements up by ID (`$('id')` / `getElementById`) and the Playwright tests use about 135 distinct `#id` selectors and the `data-*` hooks listed below. Rename one only with its JS and tests.
5. **Help keys.** Every `data-help="key"` must exist in `Amber.HELP` (`js/help.js`). A test fails on unknown keys, and another fails if a visible planner input has no help button nearby.
6. **Independent tool.** This isn't Amber Electric's app. Don't adopt Amber's branding (logo, colours, typography) in a refresh. Consider saying "independent, not affiliated with Amber Electric" near the title; it isn't said explicitly today.
7. **Everything stays in the browser.** No analytics, external fonts, maps or embeds; the CSP's `connect-src` allows only `'self'` and `https://api.amber.com.au`.

## Page structure

```
header title, one-line description, version, settings cog (#settingsBtn → #settingsMenu)
#configSection "Dates to look at": #startDate, #endDate, #fetchData (Compare costs), [data-preset] quick picks,
#messageContainer, #progressBar; hidden until there's something to look at
#mainTabs role="tablist": #tab-setup … #tab-plan (data-tab), scrolls sideways on a phone, never wraps
#resultTools GST switch (#gstToggle), Download menu (#downloadCsvButton → #csvDropdownMenu)
main six <section role="tabpanel" id="panel-…" data-panel="…">, one visible at a time
footer #disclaimerShort, <details id="disclaimer">, ko-fi link
```

The layout is a single centred column (`max-w-4xl`) of white cards (`bg-white rounded-lg shadow-md p-6`) on `bg-gray-100`. The accent colour is Tailwind indigo, with green/red for savings and costs, and amber/orange for warnings.

### Tabs

| Tab | Button / panel | What's on it |
|---|---|---|
| Start | `#tab-setup` / `#panel-setup` | `#welcomeCard` (two choice cards: with Amber / thinking about Amber), `#connectedCard`, `#setupSection` (API key `#apiKey`, `#rememberApiKey`, site picker `#siteSelectorRow`, `<details id="amberChargesDetails">` Amber's fixed charges and bill upload), `#usageImportSection` (Amber CSV), NEM12 import (`#nem12File`, `#nem12Build`, `#localSitesList`), `<details id="glossarySection">` jargon buster |
| Compare | `#tab-compare` / `#panel-compare` | Jump links (`[data-jump]`), `#planChooser` (`#planSelector`, `#ratesDetails`: flat / TOU / feed-in windows / demand forms), `#resultsSection` (`#estimateBanner`, `#qualityBanner`, the answer `#results-table-container`), `#allPlansSection`, `#monthlySection`, `#freePowerPointer` |
| Usage | `#tab-check` / `#panel-check` | `#dailyGraphSection` (`#dailyChart`, day selector), `#averageGraphContainer` (`#usagePriceChart`), `#calendarSection`, `#moreStatsSection` |
| Bill? | `#tab-bill` / `#panel-bill` | `#billCheckSection` (upload, `#billCheckBody` line-by-line table) |
| Free? | `#tab-free` / `#panel-free` | `#freePowerEmpty` (nothing to show yet), `#batteryShiftSection` (budgets, battery size, `#batteryShiftTable`, `#batteryShiftMonthly`) |
| Solar+? | `#tab-plan` / `#panel-plan` | `#planDataStatus`, `#planInputs` (home, what you already have, what to work out, `<details id="planRetailers">` company ticks, Roof and panels, Prices and rebates `#planCostInputs`, Changes you're planning), `#planRun`, `#planProgress`, `#planResults`: `#planRecommendation`, `#planExistingCheck`, options `#planOptionsTable` (`#planRankBy`), `#planDetailBody`, `#planNumbersBody`, payback `#planPaybackChart` + `#planPaybackNotes`, `#planAssumptions` |

Tab behaviour (`app.js` `selectTab`, `setupTabs`): `aria-selected`, roving `tabindex`, Left/Right/Home/End keys, and the last tab remembered in localStorage `activeTab`. The dates bar and the GST/Download tools are hidden until there's something to look at (`syncConnectedUi`) and on Solar+?, which always uses the last 12 months. Panels before results show a `[data-results-placeholder]` pointing to Start (`[data-go-start]`).

### Data attributes used as hooks

| Attribute | Used for |
|---|---|
| `data-tab`, `data-panel` | Tab buttons and panels |
| `data-go-tab`, `data-go-start` | Links that switch tab |
| `data-preset` | Date quick picks (`7d`, `thisMonth`, `lastMonth`, `3m`, `90d`, `12m`) |
| `data-jump`, `data-jump-nav` | In-page jump links |
| `data-results-placeholder` | "Nothing here yet" blocks |
| `data-help`, `data-help-label` | Help bubbles (key into `Amber.HELP`, or plain text; label for screen readers) |
| `data-tou-period`, `data-add-tou-window`, `data-remove-window` | TOU plan form |
| `data-date`, `data-month-key` | Calendar days and months |
| `data-local-use`, `data-local-remove`, `data-nem` | Imported households list, NEM12 options |
| `data-bill-reload` | Bill check reload |
| `data-plan-option` | Rows of the planner options table (`-1` = yardstick, `-2` = cheapest with nothing new) |
| `data-plan`, `data-company`, `data-plan-all`, `data-plan-show`, `data-plan-go` | Planner company and plan ticks |
| `data-keen`, `data-curtail`, `data-numbers` | Planner result blocks (the tests read these) |

## Styling system

- `css/input.css` = Tailwind base + a `@layer components` with the app's own classes:
- `.main-tab`: tabs, one line on a phone, bigger from `sm`;
- `.jump-link`;
- `.plan-input`: planner inputs;
- `.plan-multi`: the company ticks dropdown;
- `.calendar-day`, `.day-checkbox-label`;
- `.gst-switch`: a CSS-only toggle;
- `.help-btn`, `.help-row`, `.help-bubble`: help bubbles, with a bigger tap target on coarse pointers;
- `.choice-card`, `.step-num`: the Start tab.
- Everything else is Tailwind utilities written straight into `index.html` and into HTML strings in `app.js` / `planner-ui.js`. Common patterns are repeated rather than shared: card, table (`min-w-full divide-y divide-gray-200 text-sm`, `thead bg-gray-50`, `px-2 py-2` cells), small grey notes (`text-xs text-gray-500`) and buttons (indigo primary, white bordered secondary).
- There's no dark mode and no design tokens beyond Tailwind's default palette.

## JS-built UI

Most result content is built as HTML strings and set with `innerHTML`:

- **`app.js`**:
- the answer table and banners (`displayResults`);
- every plan (`renderAllPlansTable`) and month by month (`renderMonthly`);
- Free? (`drawBatteryShift`), bill check (`renderBillCheck`) and More Stats (`renderMoreStats`);
- imported households (`renderLocalSites`);
- the settings menu (`buildSettingsMenu`) and the modal (`showModal`).
- **`planner-ui.js`**: every Solar+? results card (`renderRecommendation`, `renderExistingCheck`, `renderOptions`, `renderDetail` + `breakdownHtml`, `renderNumbers`, `renderPayback`, `renderAssumptions`), and the company tick panel. Small helpers `th()` and `td()` keep its tables consistent.
- **`calendar.js`**: the calendar grid and its tooltips (appended to `body`).
- **`charts.js`** and `planner-ui.js` `renderPayback`: Chart.js charts (`#dailyChart` and `#usagePriceChart` with zoom and pan via chartjs-plugin-zoom, and `#planPaybackChart` with the $0 line drawn darker).

Text from data or the user goes through `Amber.escapeHTML` (`esc`). Keep that when changing markup.

## Responsive behaviour

- The tab bar stays on one line from 320 px up and scrolls sideways (tested at 320, 360 and 390 px), and the page itself never scrolls sideways (tested).
- Wide tables sit in cards with `overflow-x-auto` and scroll inside the card on a phone. The planner's options table (7 columns) and month table are the widest.
- Planner inputs are grids that collapse to one column (`grid-cols-1 sm:grid-cols-2/3`).

## Accessibility today

- Skip link to the tabs; ARIA tabs with keyboard support; labelled inputs; the GST switch is a real checkbox; help buttons are buttons with `aria-expanded` and can be opened by keyboard and closed with Escape.
- The planner options table rows are `role="button"` with `tabindex="0"`, and Enter/Space select them.
- Charts have an `aria-label` on the canvas. The numbers are also in nearby tables (month table, payback notes), but there's no table alternative for the Usage charts.
- The rating dots have `role="img"` and an `aria-label` ("3 of 4").
- Colour carries meaning in places (green saves, red costs), usually alongside a sign or a word.

## Writing style

The app is written for everyday Australian householders:

- Plain English with short sentences. Say what something means for the reader ("Pays back in 6.7 years"), not jargon ("ROI").
- Australian spelling and units: kWh, kW, c/kWh, $ with commas.
- Every input and table heading gets a help bubble. The jargon buster explains the terms that can't be avoided (feed-in, NEM12, controlled load, demand charge, TOU).
- **Known inconsistency**: percentages are "20%" in most of the app but "20 %" (with a space) in the Solar+? tab. Pick one in a refresh.

## Known UI issues and opportunities

- **Solar+? is long and text-heavy.** Results stack as: recommendation, rating, battery check, options, month table, breakdown, check the numbers, payback, assumptions. Many paragraphs are full sentences of figures. Candidates: a summary strip of 3–4 key numbers, progressive disclosure (`<details>`) for the breakdown, numbers and assumptions, and a sticky "option on show" header.
- **Inputs on Solar+?** are spread over several `<details>` groups. "Prices and rebates" has 18 numeric fields in one grid.
- **Compare's plan form** (flat / TOU / feed-in windows / demand) is dense and shown under the results, and custom plans are edited in place.
- **Tables** are styled inline in several places with small differences. A shared table component (CSS classes in `input.css`) would make them consistent.
- **Charts**: the payback chart's x labels rotate on narrow screens. The Usage charts would benefit from a legend and colour review. Chart colours are hard-coded hex values in `charts.js` and `planner-ui.js`.
- **Visual hierarchy**: every card has the same weight; the main answer on each tab could stand out more.
- **Help bubbles** are everywhere, which helps beginners but adds visual noise. Consider showing them only on hover or focus on wider screens.
- **The settings menu** mixes user actions (clear cache, clear saved plans) with maintainer tools ("Save custom plans", "Show stats"; IDs `saveCustomPlansForRick`, `showStatsForRick`).
- **Loading and progress**: Compare and Solar+? each have their own progress bar and message area.
- **No dark mode**; no print stylesheet, though a print or PDF view of the planner result could be useful to take to installers.

## Checking a UI change

```bash
npm run build:css
npm test # or: npx playwright test tests/planner.spec.js
npm run serve # then open http://127.0.0.1:4173
```

To see results without an Amber key, import a NEM12 file on Start. The Playwright helpers `nem12Year()` and `nem12Battery()` in `tests/planner.spec.js` generate a year of readings for a household without and with solar and a battery. Check at 320, 375 and 1024 px wide, with the keyboard only, and with a long plan name.
Loading
Loading