Skip to content

Stream Deck: size the Row-1 builder window from placed keys — a builder can be selected while shown on none #1465

Description

@amrmelsayed

Problem: a builder can be selected while rendering on no key

Row-1 selection pages by a hardcoded constant, not by how many builder keys exist. ROW1_WINDOW_SIZE = 4 (apps/streamdeck/src/store.ts:28), builderWindowStart() returns floor(cursor.builder / 4) * 4, and windowedBuilder(slotIndex) returns builders()[windowStart + slotIndex]. The page step is fixed at four and is independent of how many BuilderAction keys the user actually placed.

The consequence is a correctness bug, not a sizing preference. Place three builder keys instead of four and every builder at index ≡ 3 (mod 4) renders on no key at all — while the Select dial still walks onto it, so it becomes the selected builder, drives Row 2's entire palette and both review dials, and shows no accent ring anywhere on the board. The reviewer is acting on a builder the deck never displays. (There is no gap only while the fleet is no larger than the number of placed slots.)

It is also wrong for most hardware: Mini is 3×2, Standard 5×3, XL 8×4, and the SD+ keypad 4×2, so a constant of four fits exactly one device.

Why now

The trigger is concrete rather than hypothetical: #1463 adds an Open Architect key that the owner intends to place on a Row-1 key (safe, because its Main mode is selection-independent), which leaves three builder slots against a fixed page of four — the exact configuration that hides every fourth builder. The fix belongs to Row-1 windowing, not to the architect key, so #1463 ships with the fixed-4 behaviour documented and this issue carries the change.

Feasibility (verified against @elgato/streamdeck@2.1.0)

There is no profile-structure APIplugin/profiles.d.ts states verbatim that plugins "may only switch to profiles distributed with the plugin, as defined within the manifest, and cannot access user-defined profiles", and switchToProfile is the only profile call. So the layout cannot be read directly.

It can be derived from the lifecycle, which is the workable path: every visible BuilderAction fires willAppear carrying KeyAction.coordinates ({column, row}) and its device, and streamDeck.devices exposes Device.size ({columns, rows}) and type.

Proposed shape

  • Window size = the number of currently visible BuilderAction instances, replacing the constant.
  • Slot order derived by sorting visible instances by (row, column), which could retire the manual slot PI field or keep it as an override.
  • Recompute on willAppear / willDisappear, debounced so page-load settling does not thrash, and skipping instances whose coordinates are undefined (multi-action).
  • Semantics are per visible page, which is correct: the window should match what is on screen.

Scope

store.ts (window math), actions.ts (BuilderAction lifecycle and slot resolution), builder-action.html (PI), cursor paging, and tests. Pairs naturally with the larger-profile thread (#1381). Protocol: PIR — the payoff is on hardware, across different key counts and at least one non-SD+ device if available.

Credit

Researched and proposed by the pir-1463 builder, surfaced by the owner during that lane's plan-approval discussion; SDK claims and the windowing math independently verified against source before filing.

Metadata

Metadata

Assignees

No one assigned

    Labels

    area/streamdeckStream Deck plugin — apps/streamdeck, Elgato channel (profiles, Maker Console, packaging)

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions