Conversation
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* origin/main: feat: add_user and delete_user take lists (#1509) # Conflicts: # custom_components/lock_code_manager/__init__.py
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #1514 +/- ##
=======================================
Coverage 99.19% 99.20%
=======================================
Files 66 66
Lines 8499 8555 +56
Branches 522 524 +2
=======================================
+ Hits 8431 8487 +56
Misses 68 68
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
* origin/main: fix: editing an unmanaged code left every PIN on the card in cleartext (#1519) build(deps-dev): bump aioesphomeapi in the homeassistant group (#1512) build(deps-dev): bump @types/node from 26.2.0 to 26.3.0 (#1513) # Conflicts: # custom_components/lock_code_manager/www/generated/lock-code-manager.js
* origin/main: fix: a deleted user no longer leaves a ghost card behind (#1524) # Conflicts: # custom_components/lock_code_manager/www/generated/lock-code-manager.js
#1524 fixed the subscription on main, where a user is removed through the delete_user action. v6 adds a second route with no LCM code in it at all -- Home Assistant's native delete on the entry's page, which is where the add-user card's 'Manage users in settings' link sends people. Same guard covers it, because it is re-checked on every update rather than keyed on how the removal happened. This pins that. Entire-Checkpoint: 6c08a5ce1ddb
* origin/main: fix: a stalled provider operation is reported instead of staying silent (#1525) # Conflicts: # tests/providers/test_base.py
Review of #1514. One service call is several entry writes -- a subentry per user, then the entry -- and Home Assistant schedules an update listener task per write. They overlap. Only the earliest task finds a diff, because it caches the new configuration before its first await; the rest see the cached view, return immediately, and set `settled` on the way out. So the waiter in _async_write_and_settle was released by a pass that did nothing while the one building the entities was still going -- the exact return-before-ready its docstring exists to prevent. Count passes in flight and settle only when the count returns to zero. The downstream symptom is a race a test cannot pin: which pass wins depends on scheduling, and the assertion passes either way. Verified the overlap with a trace (enter 1, enter 2, exit 2, exit 1) and tested the guarantee directly: with a slow pass and a fast one overlapping, settled stays clear until the slow one finishes. Both mutants that break the count are killed by that test. Entire-Checkpoint: e28f6f376382
Review of #1514. EntryConfig.from_entry took users from subentries only. Migration runs when an entry is set up, so an entry that is disabled -- or has not loaded yet -- keeps its pre-v5 shape, with users and their numbers still on the entry and no subentries at all. Read that way it holds nothing, and it is still asked: get_managed_slots consults every entry on a lock for the numbers it holds, so a new entry could be issued the same numbers the disabled one already has, and the unmanaged-code sweep would raise a fixable "delete this code" repair for each of them. Fall back to reading the entry mapping, through the same converter the migration uses, so the two cannot disagree about what a slot-keyed entry means. Told apart by shape rather than entry.version. Shape is what this reads and the version is a proxy for it -- one the test fixtures do not keep, since they build subentries onto entries left at version 1 (see user_subentries in tests/common.py, which says as much). The shape is unambiguous because migration strips the legacy keys as it moves users out: an entry with none of them has nothing to fall back to, and an entry that still has them has not been migrated. Entire-Checkpoint: 2a25d0d52b56
Review of #1514. When users moved off the options form into their own subentries, the form lost the capacity check along with them. It kept the unclaimed-MQTT check and nothing else, so a lock that cannot hold the entry's existing slot numbers was accepted here and refused later, at write time, one slot at a time, as a connectivity warning. The users are not on this form, but the numbers they hold are exactly what a lock added here has to be able to hold. Reauth already checks this when it swaps a lock in; the options flow now does the same for the locks it is given, and reports the refusal the same way. Entire-Checkpoint: 90f73c003cbe
|
Ran a full code review of this branch and pushed fixes for the three findings, each as its own commit with a test that fails without it and passes with it (mutation-checked, not just covered).
|
Breaking change
Four actions removed. They addressed a lock slot or a device instead of a person:
lock_code_manager.set_usercodeset_credentiallock_code_manager.clear_usercodeclear_credentiallock_code_manager.set_slot_conditionset_conditionlock_code_manager.clear_slot_conditionclear_conditionThe
lock_code_manager_lock_state_changedevent is removed. Uselock_code_manager_credential_used, which names the person rather than the slot number they occupy. Field-by-field mapping is on the wiki.Users are now config subentries. A v4 → v5 migration moves them on load. Nothing is renamed and nobody is renumbered, so every entity keeps its ID, its settings and its history. The Configure screen now edits only the locks; each user gets their own add, edit and delete dialog on the entry's page, with their entities and device grouped under them.
The Condition Linker blueprint takes a Name instead of a Slot number. An automation already created from it must be reconfigured, or simply recreated — it is a one-shot.
Proposed change
The through-line is that a person is the unit Lock Code Manager works in, and the slot number is internal bookkeeping. Every surface that leaked that number is gone: the four slot-addressed actions, the lock-shaped event, and the
slot_assignmentside table.Users move into config subentries, so each person's slot number lives in their own record and the configuration can no longer disagree with itself about who holds which.
async_write_entry_configis the single write path, matching users by slot number — not by name, which is what a rename changes.Verified against a real instance
Deployed to a production instance holding two v4 entries and restored from a full backup afterwards. Both migrated to v5, users landed in subentries with their numbers intact, and all 31 entities plus all 3 slot devices came up bound to the right person — no duplicates, no orphans, no errors.
That run caught two things the suite could not, both fixed here:
async_add_entitiesdrag an existing device into a subentry. Every upgrading instance is in that position, since its devices predate subentries; the suite creates devices fresh during setup, already bound, so only a real upgrade reproduces it. The migration now moves each one withasync_update_device.dialog-data-entry-flowships only in lazily-loaded chunks, and no URL starts a subentry flow. The add-user card keeps its own dialog and links to the page that hosts the native ones.Defects this exposed
Five bugs that were latent before this branch:
data, so it saw zero managed slots and would have raised a repair for every one of its own codes on any v3 upgrade.from_entryreads the entry's locks options-first, so writing onlydatawas invisible whenever options were staged — and, showing no diff, never woke the listener that clears them.condition_linkerwent on callingset_slot_conditionwith a green suite, because nothing type-checks a blueprint. There is now a test that every action a blueprint calls exists inservices.yamlwith its required fields supplied.Type of change
Additional information
set_credential/clear_credentialshipped in 5.x; this removes theset_usercode/clear_usercodepair they replace, which is the half that had to wait for a major.lock_code_manager_lock_state_changedevent is gone.prekclean.