Repository navigation
feat: a credential use shows in the logbook of the lock it was used on, and the user card lists recent uses - #1796
Merged
Merged
Conversation
…n, and the user card lists recent uses
Every use is written as a manual logbook entry under its target ("Alice used
a PIN to unlock"), so it appears in the lock's own activity next to the state
change it explains. Clicking "Last used" on the user card now expands the
user's recent uses, read from the recorder's history of their credential-used
event entity, with a link through to the full history.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Entire-Checkpoint: 1d5a779241a6
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #1796 +/- ##
==========================================
- Coverage 98.89% 98.87% -0.02%
==========================================
Files 72 73 +1
Lines 10299 10396 +97
Branches 569 599 +30
==========================================
+ Hits 10185 10279 +94
- Misses 114 117 +3
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
…ist from review The frontend never shows a logbook entry's own name when the entry has an entity_id: the Logbook panel puts the entity's name there and the entity's activity view shows the message alone. So the person goes in the message: "Unlocked by Alice with a PIN", or "Alice used a PIN" when the device did not say what it did. The card labels kinds with Home Assistant's own event_type translations (formatEntityAttributeValue) instead of a copied table, reuses rows already read when re-expanding, drops the list when the card switches to another user, and lets the row's own text be its accessible name. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Entire-Checkpoint: 3b1b9cd0dc4d
…use in the logbook async_fire_credential_used now takes the context of whatever reported the use and fires both the bus event and the logbook entry with it. The use_credential action passes the service call's context, so the logbook links the entry to the automation run (or person) that reported it. A use a lock observed passes None: the lock's state change has the lock integration's context, and sharing it would tell the logbook the unlock caused the use. The parameter is required so every caller decides. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Entire-Checkpoint: f07172769779
…ntry directly The card's list now subscribes to history/stream while it is open instead of reading the history once and splicing live states in by hand. The stream sends what the recorder holds, then what it had not yet committed, then each new state, so uses that arrive before a commit, while the card was off screen, or across a reconnect are no longer missed. The hand-kept cache, the request counter and the current-state splice are gone. Kept rows are trimmed to what the list shows. The stream closes on collapse, when the card leaves the page, when the card switches user, and when the slot comes to name someone else. The logbook entry is fired as EVENT_LOGBOOK_ENTRY directly instead of through logbook.async_log_entry, which fires exactly that. That drops the import of the logbook (and its recorder and frontend modules) from every provider, and drops the after-dependency that held this integration's setup behind the recorder's. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Entire-Checkpoint: 5243fc6ceaee
- A closed stream's late messages are dropped by stream identity, not by entity ID, so collapsing and re-expanding on the same user cannot mix them in. - When the slot comes to name someone else, the open list closes instead of reopening a stream the row may never show; a stream that cannot open shows the error message rather than "Loading…" forever. - The stream asks for state changes only: every use moves the state, and the entity's attribute-only writes are left out. - mergeUseRows owns which rows are uses; toCredentialUses only names them. - A credential kind with no phrase yet reads as its value in the logbook message instead of raising inside the provider callback. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Entire-Checkpoint: acf01879bd18
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Proposed change
A forum user asked how to see who unlocked which door, and when. The data was already recorded: the user's
credential_usedevent entity holdstarget,credential_typeandoperationfor every use. But Home Assistant didn't show it anywhere they'd look. The lock's activity said "No cause was recorded", and clicking Last used on the user card opened a more-info dialog that showed only the event type ("pin").1. Each use goes into the logbook of what it was used against.
async_fire_credential_used(the one function every use goes through, whether a lock reported it oruse_credentialwas called) now also fires a manual logbook entry (EVENT_LOGBOOK_ENTRY, withdomain=lock_code_managerandentity_id=target). The lock's activity, the lock's device page and the Logbook filtered to that lock all show entries like "Unlocked by Alice with a PIN", or "Alice used a PIN" when the device didn't say what it did. The person goes in the message because the frontend never shows a logbook entry's ownnamefield when the entry has anentity_id. The Logbook panel shows the entity's name in that place (logbook-entry-model.ts,display?.primary ?? entry.name), and the entity's own activity view shows the message alone (name-detail="none").Why a manual entry instead of a
logbook.pythat describes our own event:async_determine_event_types). For a lock, that's Z-Wave JS or Matter, never us.entity_idordevice_idkey in the event data, and our payload hastarget.logbook_entryis the one event type the logbook includes for any entity named in itsentity_id.Precedent, for the record:
logbook_entry.This doesn't fill in "No cause was recorded" on the lock's own state row. The frontend builds the cause from shared context IDs, and the lock integration sets that context. The use shows up as its own row next to the state change.
Context. The
use_credentialaction passes the service call's context to both the bus event and the logbook entry. A keypad automation that reports a use shows up in the logbook as that entry's cause, and automations reacting to the bus event trace back to it. Uses a lock observed passNone: the lock's state change already has the lock integration's context, and sharing it would tell the logbook the unlock caused the use.contextis a required argument, so every caller has to choose.2. The user card lists recent uses. Clicking Last used now expands the user's recent uses, up to 10. Each row shows what the credential was used against, the kind of credential, what the device did, and when. Full history underneath opens the more-info dialog, which is what clicking the row used to do.
history/streamfor the event entity (last 30 days, attributes included). This is what Home Assistant's own more-info history uses. It sends what the recorder holds, then the states it hadn't committed yet, then each new state as it happens. So no use is missed, whether it was still waiting on a recorder commit, happened while the card was off screen, or came across a reconnect. Nothing new is stored.target, are skipped.sourceentity is never read, since its state can be the code that was typed.event_typetranslation (hass.formatEntityAttributeValue), so the list names a kind the same way the entity's history does.aria-label, so its visible text (the lock and the time) is what screen readers announce.Messages are English, not translated. LCM ships only
en.json, and the cards are hard-coded English, so translating at write time would serve no one.Why the entry is fired directly.
EVENT_LOGBOOK_ENTRYis fired directly rather than throughlogbook.async_log_entry, which fires exactly the same event. Importing the logbook would pull its recorder and frontend modules into every provider. Depending on it would hold LCM's setup behind the recorder's, which can be slow during a database migration. The recorder stores the event whether or not the logbook is loaded yet.Review findings not addressed here
Four
/code-reviewrounds. Round 3 replaced round 2's hand-kept cache withhistory/stream. Round 4 found nothing high-severity; its fixes tidied the stream's lifecycle.I ran
/code-reviewand fixed the commit race, the flicker on reload, rows from before 6.0, the label mismatch and the enum comparison. Left as they are:Testing
tests/test_logbook.py: end-to-end with the real recorder and logbook, queryinglogbook/get_eventsby the lock's entity ID, the same way its activity view does. Covers a use the lock reported, a use reported throughuse_credentialagainst a cover, and that nothing is filed under the source. Also checks every pairing of kind and operation, and that no two kinds read the same. Mutation check: filing undersourceinstead oftargetfails.ts/credential-uses.test.ts: the history parser.ts/slot-card.dom.test.ts: clicks the rendered row and reads the list, including a use that arrives while the list is open and a failed history read. Mutation checks: removing the expand, or removing the step that keeps the current state, fails.tests/test_frontend_contract.py: the card's labels matchstrings.json.-n auto, three real-time pacing tests fail from load and pass on their own; they don't involve credential uses.yarn buildprints an existing type error ints/generate-view.ts(pinActiveEntity), a file this PR doesn't touch.Type of change
Additional information
🤖 Generated with Claude Code