Skip to content

Fix/audit 2026 10 - #6

Merged
Gamso merged 27 commits into
mainfrom
fix/audit-2026-10
Oct 14, 2026
Merged

Gamso merged 27 commits into
mainfrom
fix/audit-2026-10

Conversation

@Gamso

@Gamso Gamso commented Oct 3, 2026

Copy link
Copy Markdown
Owner

No description provided.

Gamso and others added 23 commits September 27, 2026 16:26
…tions

The dial showed the thermostat presets, but everything else had to live
around it: times pinned to one corner, badges pinned to the other, two
unlabelled dropdowns underneath. Adding the covers-left-open warning made
the crowding obvious — its list of covers only fitted in a hover tooltip,
which does not exist on a touch screen.

The presets become a segmented control, one colour per mode rather than red
for all of them, and everything else becomes a labelled row. Nothing floats
against the card edge any more, and the covers left open are written out.

The two duration settings were "Surcharge" and "Anticipation", which say
nothing about what they do. They are now "Garder mon choix manuel" and
"Anticiper les événements", collapsed to one line showing their value, and
folded open on click to a minus/plus stepper over the same presets as
before. Under the stepper, a sentence states the consequence rather than the
setting: "Télétravail commencera à 20:17 au lieu de 21:17." One opens at a
time and folds back after six seconds.

When nothing is wrong the card says nothing: the alert row exists only while
heat protection is on or covers were left up. The "Thermostat" header goes
too, with the name and show_title options and the editor switch that drove
them — the card sits under its own dashboard heading and repeated the word
for nothing.

Preset colours are read from the select's option_map keys, and the card no
longer depends on that attribute being there: the active preset comes from
current_key when present, and the colour class falls back to the display
label ("Eteint", "Clim", "Fan" and their neighbours map back to a known
mode) rather than producing a class no rule matches. The simulated selects
in the test Home Assistant now publish option_map, option_keys and
current_key, the same three the integration publishes.

Also fixes a trap this uncovered: localize() returns the key when a string
is missing, so every `localize(...) || fallback` in the card was dead and
"Éteint" rendered as "thermostat.off". A t() helper treats an echoed key as
missing, and the missing thermostat.off string is added to both languages.

The dial is removed with its component, and with it _formatRelativeTime and
the two select handlers the steppers replace. It is in the history if the
layout is ever wanted back.

Verified in a browser against the built bundle: presets localised with the
active one marked and each colour rule shipping, settings collapsed by
default and one-at-a-time, the consequence sentences correct, the steppers
calling number.set_value with the neighbouring preset, and no alert markup
at all in the calm state.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
package.json already declares "license": "MIT" and the README says MIT,
but the repository shipped no licence text, which leaves reuse legally
unclear. Same holder and wording as the companion integration.

Audit: T-1 (stack note: missing LICENSE)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
custom-card-helpers was declared as a runtime dependency but never
imported, so it only added install weight and audit noise.

The bundle referenced homeshift-card.js.map while dist/*.map is
git-ignored: HACS users got a 404 in devtools for a map that is never
shipped. The bundle is minified, reproducible from the tagged sources,
and small, so sourcemaps are disabled (rollup and tsconfig) instead of
versioning a .map that would double the dist diff on every change.

Audit: T-1

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The repository had no lint, no test and no type check beyond the build,
which is how the editor key typo and the dead localize fallbacks went
unnoticed. Adds:
- vitest + happy-dom (Lit components render in a DOM without a browser),
- ESLint flat config with typescript-eslint,
- "typecheck" (tsc --noEmit over src and tests), "lint" and "test"
  scripts, used by the upcoming CI workflow.

Audit: T-1

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The visual editor read and wrote "hermostat_mode_tentity" while the card
reads "thermostat_mode_entity": picking a thermostat in the editor saved
a key the card ignored, and an existing YAML config showed an empty
picker. The editor also only covered 8 of the 13 options and kept its
own copy of the config interface, which is how the two drifted apart.

HomeShiftCardConfig and a key/domain/default table now live in
src/types.ts; the card derives its defaults and watched entities from
the table and the editor generates one picker per entry, so a new
option cannot be forgotten in one of them. Missing editor labels are
added in en/fr. Tests assert that every key the editor writes is a key
the card reads, that every entity option has a picker and a label.

Audit: B-1, A-1

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
localize() returned the key itself when a translation was missing, so
every `localize(...) || "default"` in the card was dead code, a key
missing from fr.json showed up raw instead of in English, and the
preview stub displayed "preview.day_mode_state" with no option or arc
segment highlighted (the preview section existed in no dictionary).

Lookup is now: user language, English, optional fallback argument, key.
The dead `||` fallbacks are removed; thermostat modes unknown to the
card fall back to the integration's display value; the preview stub is
built from new preview.* strings and the existing thermostat labels so
its state matches an option. Tests cover the lookup chain, parameters
and en/fr key parity.

Audit: A-3

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Missing entities (B-3): the stub day/thermostat data was used on every
  dashboard, so a typo in day_mode_entity produced a card that looked
  functional. The stub is now reserved for the card picker / editor
  preview (HA's `preview` property); elsewhere the card shows the
  existing, previously unused card.entity_not_found message.
- Next mode (B-4): unavailable was not filtered and an "unknown"
  next_mode_at was truthy, giving an invalid Date and "unknown" in the
  dial centre. A shared isUsable() now filters both sensors and the
  cover times; an unparsable timestamp renders nothing.
- Number dropdowns (B-5): Number("unavailable") gave NaN, highlighted
  the dropdown and set value "NaN"; a value outside the presets (25 min)
  had no option. Values are read with Number.isFinite, an ad hoc option
  is injected for off-preset values, the dropdown is disabled while the
  entity is unavailable, and both dropdowns share one renderer (the
  override dropdown is now also hidden when its entity is not set).

Audit: B-3, B-4, B-5

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… code

The card re-rendered every 30 seconds although entity changes already
trigger renders and the only time-dependent output is the Today /
Tomorrow prefix, which changes at midnight. A single timeout now fires
just after midnight and reschedules itself; it is still cleared in
disconnectedCallback.

Removed, never called or never rendered: _formatRelativeTime,
onOffButtonClick, the .off-row / .center-button / hui-card styles, the
slider's _onSvgClick / _getPercentageFromEvent, its angle constants,
@query svg and its unused hass / entityId properties, and the
card.next_scan / card.in / card.past strings. noUnusedLocals and
noUnusedParameters are enabled so dead private members fail the build.

Audit: P-1

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
toLocaleTimeString([], ...) used the browser locale and ignored
hass.locale.time_format, so a user who chose 24 h in their Home
Assistant profile could still see "02:05 PM" (and the date used the
browser language). Time and date now follow hass.locale: forced 12/24 h,
"system" = browser locale, default = HA language convention.

Audit: A-4

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Dial: the SVG had no role, no tab stop and no keyboard handling. Each
  mode is now a role="radio" group (accessible name = localized label,
  aria-checked = active) inside a labelled radiogroup, with a roving
  tabindex; arrows/Home/End move the focus, Enter/Space switch the mode.
  Arrows deliberately do not switch, so browsing the modes does not
  fire a thermostat change per key press.
- Badges: the covers-left-open list only lived in a title tooltip
  (cursor: help), unreachable on touch screens, and the heat protection
  tooltip was dead because of pointer-events: none (B-6). Both badges
  are now buttons with an aria-label that toggle the detail text inline.
- The three dropdowns get localized aria-labels.

The clickable cover-time rows are turned into buttons by the covers
feature commit that follows.

Audit: A-2, B-6

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
getStubConfig() wrote cover_entity: cover.homeshift_daily_covers, but the
integration has no cover platform. Every card added from the picker got
clickable time rows that called cover.open_cover on a missing entity.
The key is removed from the stub (and from the devcontainer dashboard),
and the rows only act when the configured cover exists and is not
unavailable.

Audit: B-2

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The integration now exposes button.homeshift_open_covers and
button.homeshift_close_covers (when daily covers are configured). The
opening / closing time rows become real <button>s that press them:
keyboard-focusable, localized aria-label including the scheduled time,
active only when the button entity exists and is not unavailable (a
button's "unknown" state just means never pressed).

- New optional keys open_covers_entity / close_covers_entity, defaulting
  to the integration buttons, in the config type, the visual editor
  (button domain) and the README.
- Legacy cover_entity keeps priority when set (cover.open_cover /
  close_cover, previous behaviour); documented in README and editor.
- While the call is in flight the row shows "Sending…" (minimum 600 ms,
  aria-busy, both rows disabled to avoid double presses); a rejected
  call is reported through hass-notification instead of an unhandled
  promise rejection.
- No confirmation dialog: the action is reversible with the other row,
  is explicitly labelled, and HA's own button rows do not confirm.
- en/fr strings; devcontainer gets template buttons simulating the
  integration's.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- README: "homershift-card" in the YAML type and the three resource paths
  gave "Custom element doesn't exist" and a 404 on copy-paste. Five
  options were missing from the table (heat_protection_entity,
  cover_open_time_entity, cover_close_time_entity, covers_left_open_entity,
  besides the cover options documented with the feature). The arc is
  ~250°, not 270°; the promised relative "in 1h30" display never existed
  (and the 30 s refresh is gone). Adds the badges, keyboard use, the
  "Entity not found" behaviour and a Development section.
- .github/copilot-instructions.md described a different card (homershift
  tag names, input_select service, 250° constants, the misspelled
  thermostat key); rewritten from the current code.
- Devcontainer: setup.sh copied from /workspaces/homershift_card (does
  not exist), the Dockerfile WORKDIR matched it, dist/ was mounted on
  /config/www/workspaces/homeshift_card while the resource pointed to
  /local/homershift_card/. Everything now uses homeshift_card: dist/ is
  mounted on /config/www/homeshift_card, served at
  /local/homeshift_card/homeshift-card.js, and the redundant copy is
  dropped. Removes a stray "Xiaomi Smart Pet Fountain Card" hint.

Audit: D-1

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Nothing guarded the repository: the editor key typo and the dead
localize fallbacks would have been caught by a trivial test. On every
push to main and every pull request the workflow now runs npm ci,
typecheck, lint, the Vitest suite and the build, fails if the committed
dist/ differs from the fresh build, and runs hacs/action for a plugin.

Audit: T-1

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Rebuilt from the sources of this branch (no sourcemap reference any
more), so the CI "dist matches the sources" check passes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
HACS validation reads the license from the default branch through the
GitHub API, so it must exist on main for pull requests to pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
feat(card): rebuild the card as a labelled list with collapsible dura…
PR #5 rebuilt the card as a labelled list and removed the circular
slider. The list layout is kept as the visual reference and the audit
fixes of this branch are carried over onto it:

- circular-slider.ts deleted as on main; the thermostat modes are the
  segmented native buttons of the list, labelled as a group, with
  aria-pressed, disabled while the select is unavailable (A-2).
- Missing day / thermostat entity: entity_not_found outside the card
  picker preview, localized stub data inside it (B-3, A-3).
- isUsable() for the next-mode and cover-time sensors (B-4); a duration
  setting whose number is unknown/unavailable reads "Unavailable", is not
  highlighted and cannot be opened, so no NaN (B-5); a value outside the
  presets is shown as is.
- Times follow the HA profile (A-4); the 30 s re-render of PR #5 is
  replaced by the midnight timer (P-1).
- localize() with fallback instead of the card-local t() helper (A-3).
- ENTITY_FIELDS drives defaults, watched entities and the editor; the
  editor drops show_title like main and keeps thermostat_mode_entity
  (B-1, A-1); no cover entity in the stub (B-2).
- Manual open/close: cover times are <button>s pressing the HomeShift
  buttons (cover_entity first when set), with "Sending..." feedback and
  a hass-notification on failure.
- Covers-left-open chip: past three covers it shows a count and becomes
  a button revealing the names on tap (A-2, B-6).
- Translations: union of both sides, minus the keys neither side reads
  any more (card.title, card.off, card.next_scan, override_duration,
  early_switch, editor.show_title). Tests check parity and unused keys.
- Dial tests replaced by tests on the preset buttons, settings, chips.

dist/ is left at this branch's bundle and rebuilt in a later commit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The list layout's stepper looked up the first preset >= the current
value and moved one index from there. From a value between two presets
(25 min set on the entity page) "+" therefore skipped 30 and jumped to
1h. "+" now goes to the first preset above the value and "-" to the
last one below it.

The presets were also carried as { label, value } pairs whose labels
the list never displays; they are plain minute lists now.

Audit: B-5 (values outside the presets), P-1 (dead code)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The list layout labelled its stepper buttons "-" and "+": a screen
reader announced "minus, button" with no hint of which setting it
changes. They are now "Decrease: <setting>" / "Increase: <setting>"
(en/fr), and the value between them is a polite live region so the new
duration is read out after each step.

Audit: A-2

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
After the merge of the list layout (PR #5) the README, the Copilot
instructions and the devcontainer dashboard still described the
circular selector, the badges beside it, the minutes dropdowns and the
show_title option, all gone. They now describe the rows (segmented
thermostat modes, day mode, next mode, collapsible steppers, cover
times as open/close-now buttons, alert chips), document that `name` is
accepted but no longer displayed, and the devcontainer card uses the
cover button and covers-left-open entities.

Audit: D-1

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Rebuilt from the merged sources: list layout of PR #5 with the audit
fixes, the manual cover buttons and the stepper fixes. The circular
slider is no longer bundled.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A "Cover automation" row lists every cover HomeShift drives (read from
sensor.homeshift_covers_inhibited). Pick a duration — 4 h to 7 days, or
until resumed — then pause covers one by one; each paused cover shows when
it comes back and can be resumed, or all at once. While covers are paused,
a calm chip names them and reopens the row. A refused call is shown as
Home Assistant's notification toast.

Calls homeshift.inhibit_covers / homeshift.resume_covers. The row is hidden
when the sensor is missing, so older integrations are unaffected.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016uVhrB9KRnmbFZRPHNHUev
@Gamso
Gamso force-pushed the fix/audit-2026-10 branch from 52b77cd to 64652fd Compare October 10, 2026 06:12
Gamso and others added 4 commits October 11, 2026 09:30
A Schedules bar at the foot of the card slides a panel up over it: one
tab per day mode with the 24 h timelines of the schedules tagged with
it, grouped by room, and a pencil that swaps them for the scheduler-card
list of the tab. The panel is as tall as its content, anchored to the
foot of the card, and scrolls when the schedules are taller than it.

The bar appears once the Scheduler integration holds a schedule tagged
with a day mode; show_schedules: false hides it and schedule_entities
narrows it. Also aligns the gap under the thermostat modes with the
card padding.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t data

Home Assistant was started in the background from postCreateCommand,
which VS Code kills: start.sh is now the container's main process and
restarts it. onboard.py creates the admin / admin account on first
launch; setup.sh installs Scheduler and scheduler-card v4.0.19 (v4.0.11
froze the browser when opening a schedule), seeds sample schedules
tagged with the day modes, simulated thermostats and a HomeShift
dashboard. Covers use the modern template syntax, *.sh keep LF endings.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Gamso
Gamso merged commit 842c8dd into main Oct 14, 2026
3 of 6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant