Skip to content

feat: the card shows a user, not a slot number - #1458

Merged
raman325 merged 7 commits into
v3from
feat/user-centric-card
Aug 20, 2026
Merged

feat: the card shows a user, not a slot number#1458
raman325 merged 7 commits into
v3from
feat/user-centric-card

Conversation

@raman325

Copy link
Copy Markdown
Owner

Proposed change

The slot card was the last place a slot number faced the user. It is now custom:lcm-user, takes name instead of slot, and its editor asks for a user.

Not a second card

The existing card is 1,450 lines, and slot appears in three of them — all message payloads, none of them rendering. A separate user card would have been three payloads' worth of difference against ~3,500 lines of card, editor and styles duplicated forever. So lcm-user is the implementation, and custom:lcm-slot stays registered as a subclass that warns and defers, so existing dashboards keep working until it's removed.

Backend

subscribe_code_slot, set_slot_condition and clear_slot_condition now accept name as well as slot, matched slugified the way a config entry title already is — a caller holding only the slug an entity ID was built from can still name its user.

Two refusals rather than guesses, because this path writes to a lock:

  • Ambiguous slugs. Ada-Lovelace and Ada Lovelace are two users under the name rules and one slug under this. The command fails and names both rather than putting a PIN on the wrong person. (Deliberately stricter than entry-title matching, where first match wins.)
  • A user with no slot yet. assignment.slot() returns int | None — mypy caught this — because a user can exist before allocation numbers them. There's nothing on any lock to address.

Resolution happens at the websocket boundary: a slot number is still what the coordinator, entities and providers key on, so that's where the two vocabularies meet.

One detail worth reviewing

A card carrying both a name and a slot number would keep showing whoever held that number, whatever the name said. The editor therefore drops the number when a name is set, and the card picks one addressee in a single place rather than deciding per command.

Type of change

  • Dependency upgrade
  • Bugfix (non-breaking change which fixes an issue)
  • New integration (thank you!)
  • New feature (which adds functionality to an existing integration)
  • Breaking change (fix/feature causing existing functionality to break)
  • Code quality improvements to existing code or addition of tests

Additional information

custom:lcm-slot is deprecated here and scheduled for removal in a future release; the slot key on the websocket commands goes with it.

Checklist

  • The code change is tested and works locally. Python: 1791 passed, 100% coverage. TypeScript: 720 passed. Build clean.
  • There is no commented out code in this PR.
  • Tests have been added to verify that the new code works.

🤖 Generated with Claude Code

https://claude.ai/code/session_01XDxiHpQJkRKWctS9BfQmbY

Copilot AI lite review requested due to automatic review settings August 20, 2026 13:30

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@github-actions github-actions Bot added python Pull requests that update Python code javascript Pull requests that update javascript code code-quality Pull requests that improve code quality enhancement New feature or request labels Aug 20, 2026
@codecov

codecov Bot commented Aug 20, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 99.05%. Comparing base (f62f172) to head (dce7b35).
✅ All tests successful. No failed tests found.

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##               v3    #1458      +/-   ##
==========================================
- Coverage   99.05%   99.05%   -0.01%     
==========================================
  Files          60       60              
  Lines        7501     7477      -24     
  Branches      489      484       -5     
==========================================
- Hits         7430     7406      -24     
  Misses         71       71              
Flag Coverage Δ
typescript 94.94% <100.00%> (-0.04%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
ts/slot-card.styles.ts 100.00% <ø> (ø)
ts/slot-card.ts 98.06% <100.00%> (-0.04%) ⬇️

... and 3 files with indirect coverage changes

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

raman325 and others added 5 commits August 20, 2026 10:41
The three commands a card sends -- subscribe_code_slot,
set_slot_condition, clear_slot_condition -- took a slot number and
nothing else. They now take a name as well, which is what every other
surface moved to. The slot number stays so existing callers keep
working, and is expected to go.

Names are matched slugified, the way a config entry title already is: a
caller holding only the slug an entity id was built from can still name
its user.

Slugifying collapses more than the name rules do, though -- "Ada-Lovelace"
and "Ada Lovelace" are two users under those rules and one slug under
this. Where that happens the command is refused and names both, rather
than picking one and writing somebody else's credential. A user the
allocator has not numbered yet is refused for the same reason: there is
nothing on any lock to address.

Resolution happens at the websocket boundary rather than deeper down: a
slot number is still what the coordinator, the entities and the provider
key on, so this is where the two vocabularies meet.

clear_slot_condition gained a failure path it did not have before, so it
gained the error handling to match.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XDxiHpQJkRKWctS9BfQmbY
Entire-Checkpoint: 0f359e698aca
The slot card was the last place a slot number faced the user. It is
registered as custom:lcm-user now, takes `name` instead of `slot`, and
the editor asks for a user rather than a number.

Not a second card. The old one was 1,450 lines of which `slot` appeared
in three -- all message payloads, none of them rendering -- so a separate
implementation would have been three payloads' worth of difference and
3,500 lines of duplication to maintain. custom:lcm-slot stays registered
as a subclass that warns and defers, so dashboards holding one keep
working until it is removed.

A card carrying both a name and a slot number would keep showing whoever
held that number regardless of the name, so the editor drops the number
when a name is set, and the card sends one or the other from a single
place rather than deciding per command.

The strategy passes the user's name down to the section, which passes it
to the card; where a user has no name yet the slot number still goes
through, because something has to address them.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XDxiHpQJkRKWctS9BfQmbY
Entire-Checkpoint: cf120f3d01cf
subscribe_code_slot was the only one of the three commands without the
service-error wrapper, so a name that did not resolve escaped as an
ERROR log line rather than an answer. The editor dispatches on every
keystroke, so typing a name produced one per character.

set_slot_condition sniffed its own message text for "not found", which
none of the resolver's wordings contain, so the same input came back as
an unknown error there and a not-found from its sibling. It now catches
the resolver explicitly. clear_slot_condition's try was dead -- the
decorator above it already did exactly that.

The card picker was broken: the stub config named a real entry with an
empty name, and only the literal id "stub" suppressed subscribing, so
adding the card subscribed for nobody and rendered an error where the
preview goes. A card with no addressee is a stub now, whatever entry it
names, and naming nobody is no longer an error at all -- that belongs in
the editor, not where a preview belongs. A name that is not a string
still is one.

_addressee tested `!== undefined` where both other producers test
truthiness, so an empty or null name sent an unusable addressee and
ignored a perfectly good slot number. Clearing the name in the editor no
longer takes the slot number with it, leaving the card addressing
nothing.

Also drops the lcm-slot-editor registration: the deprecated card
inherits getConfigElement from the user card, so nothing ever asked for
it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XDxiHpQJkRKWctS9BfQmbY
Entire-Checkpoint: f74d3b7f7a74
The card was addressed by the user's name, and the card has an inline
name editor. Renaming a user on the card left its own stored config
naming somebody who no longer exists: the live subscription survived,
having resolved the slot once, so nothing looked wrong until the next
page load, when the card came back dead. Strategy-generated cards heal
themselves on every render; a hand-placed one does not, and the editor
had deliberately dropped the slot number that used to hold it together.

That is the mistake this project has made before, one layer up. A stored
identifier must not be a value the user can change, and the name is now
the most changeable value there is.

So a card is addressed by an entity of its user. Entity IDs are unique
by construction and this integration's do not move on a rename -- unique
IDs keep the slot number, which is the whole reason a rename costs
nothing. The commands take user_entity_id, named apart from entity_id
because on the condition commands that already means the condition
entity itself.

It also settles the ambiguity the name matching introduced: two users
whose names collapse to one slug broke both their generated cards, since
the resolver refuses rather than guess. Entity IDs cannot collide, so
the generated dashboard no longer has the problem to have.

The name stays for anyone writing a card by hand, where it is far easier
to type than an entity id, and the editor still clears the other
addressees when one is set -- a card carrying two would show whoever the
other one pointed at.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XDxiHpQJkRKWctS9BfQmbY
Entire-Checkpoint: 2ef94448e4a4
The loop that picks an entity per slot had no test reaching it: every
fixture passed an empty `entities` list, so the map was always empty and
sections went out addressed only by name. It dropped this file from 98.6%
line coverage to 94.9% and nothing failed, because the fallback is the
name and the name still worked.

Covers both branches: a slot with entities gets the first one seen, which
is what keeps the choice stable across renders rather than dependent on
registry order, and a slot with none is left to its name and number --
reachable while a slot is being set up, when the configuration knows the
user before any entity for them exists.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XDxiHpQJkRKWctS9BfQmbY
Entire-Checkpoint: 709ca09d8ddc
@raman325
raman325 force-pushed the feat/user-centric-card branch from 05a3521 to ecc2813 Compare August 20, 2026 14:43
raman325 and others added 2 commits August 20, 2026 11:23
* origin/v3:
  feat: the entity-ID rename is a notification, not a repair (#1460)
The header read "Slot 1 · All Locks", which was the last place a slot
number faced the user -- and once the card began addressing itself by an
entity the number was not even set, so it rendered "Slot undefined".

Taking the title out left an icon and a state chip alone in a bordered
bar, saying the same thing twice: the card already tints its background
by state and the chip already names it. So the bar is gone. The icon
moved to the head of the row that carries the user's name, where it reads
as the card's avatar rather than a second state indicator, and the chip
sits at the end of that row.

The name was already there, already editable, already larger than the
title above it. It is the card's heading now, in the literal sense too --
dissolving the bar took the <h2> with it, and a screen reader lost the
card's title until it was put back on the name.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XDxiHpQJkRKWctS9BfQmbY
Entire-Checkpoint: 344fb26915cd
@github-actions github-actions Bot removed the python Pull requests that update Python code label Aug 20, 2026
@raman325
raman325 merged commit e17eadc into v3 Aug 20, 2026
16 checks passed
@raman325
raman325 deleted the feat/user-centric-card branch August 20, 2026 16:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

code-quality Pull requests that improve code quality enhancement New feature or request javascript Pull requests that update javascript code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants