From 4763c06aa6d0f1462b612e5bfcc9a75398a78622 Mon Sep 17 00:00:00 2001 From: Vitaly Popuzin Date: Thu, 30 Jul 2026 17:16:51 +0200 Subject: [PATCH 1/2] feat: add PBExplorerUiEventsResult and relocate ExplorerUi enum Adds the PBExplorerUiEventsResult component (ecs_component_id 1220) so scenes can observe when fullscreen explorer panels open and close, and relocates the ExplorerUi enum out of the restricted-actions API into decentraland/sdk/components/common/explorer_ui.proto. The relocation is required, not cosmetic. js-sdk-toolchain's RPC API generator (scripts/rpc-api-generation/index.ts:203) hardcodes decentraland/sdk/components/common/ as the only path whose proto types collapse to a single nominal TypeScript identity. With the enum left in restricted_actions.proto a scene would hold two incomparable ExplorerUi types -- one from ~system/RestrictedActions, one from @dcl/ecs. After the move, restricted_actions.gen.ts and explorer_ui_events_result.gen.ts both import ExplorerUi from the same generated module. The move and the new component must ship together: relocating the enum without a component proto importing it leaves it ungenerated in @dcl/ecs and re-declared in apis.d.ts, which is a regression. Wire compatibility is untouched -- field number 1, values 0-6, and wire types are all unchanged. buf breaking with WIRE_JSON (the policy configured in proto/buf.yaml) passes. Expected red check: validate-compatibility runs scripts/check-proto-compabitility.sh, which compares fully-qualified type names textually and is stricter than the repo's configured buf policy. It reports a source-level break for the `ui` field. The break is real but has zero consumers -- openExplorerUi merged 2026-07-28 and the latest stable @dcl/sdk (7.25.0, published 2026-07-27) does not contain the API, so only next/experimental tarballs are affected. A team override is requested rather than suppressing the check. Co-Authored-By: Claude --- .../kernel/apis/restricted_actions.proto | 15 ++--------- .../sdk/components/common/explorer_ui.proto | 14 ++++++++++ .../explorer_ui_events_result.proto | 26 +++++++++++++++++++ public/sdk-components.proto | 1 + 4 files changed, 43 insertions(+), 13 deletions(-) create mode 100644 proto/decentraland/sdk/components/common/explorer_ui.proto create mode 100644 proto/decentraland/sdk/components/explorer_ui_events_result.proto diff --git a/proto/decentraland/kernel/apis/restricted_actions.proto b/proto/decentraland/kernel/apis/restricted_actions.proto index 9ebfad06..2a8c839f 100644 --- a/proto/decentraland/kernel/apis/restricted_actions.proto +++ b/proto/decentraland/kernel/apis/restricted_actions.proto @@ -3,6 +3,7 @@ package decentraland.kernel.apis; import "decentraland/common/vectors.proto"; import "decentraland/sdk/components/common/avatar_mask.proto"; +import "decentraland/sdk/components/common/explorer_ui.proto"; message MovePlayerToRequest { decentraland.common.Vector3 new_relative_position = 1; @@ -65,18 +66,6 @@ message EmptyResponse { } message StopEmoteRequest { } -// Identifies which fullscreen explorer panel OpenExplorerUi targets. -// EU_SETTINGS holds 0 so an unset `ui` field defaults to the least-intrusive panel. -enum ExplorerUi { - EU_SETTINGS = 0; - EU_MAP = 1; - EU_BACKPACK = 2; - EU_CAMERA_REEL = 3; - EU_COMMUNITIES = 4; - EU_PLACES = 5; - EU_EVENTS = 6; -} - // Verdict of an OpenExplorerUi request (enum rather than a bool so new outcomes stay expressible). enum OpenExplorerUiResult { UNSPECIFIED = 0; @@ -88,7 +77,7 @@ enum OpenExplorerUiResult { } message OpenExplorerUiRequest { - ExplorerUi ui = 1; + decentraland.sdk.components.common.ExplorerUi ui = 1; // Extension point for future per-panel parameters, as a oneof so each panel gets its // own optional param message without touching existing fields. diff --git a/proto/decentraland/sdk/components/common/explorer_ui.proto b/proto/decentraland/sdk/components/common/explorer_ui.proto new file mode 100644 index 00000000..6abf3598 --- /dev/null +++ b/proto/decentraland/sdk/components/common/explorer_ui.proto @@ -0,0 +1,14 @@ +syntax = "proto3"; +package decentraland.sdk.components.common; + +// Identifies which fullscreen explorer panel OpenExplorerUi targets. +// EU_SETTINGS holds 0 so an unset `ui` field defaults to the least-intrusive panel. +enum ExplorerUi { + EU_SETTINGS = 0; + EU_MAP = 1; + EU_BACKPACK = 2; + EU_CAMERA_REEL = 3; + EU_COMMUNITIES = 4; + EU_PLACES = 5; + EU_EVENTS = 6; +} diff --git a/proto/decentraland/sdk/components/explorer_ui_events_result.proto b/proto/decentraland/sdk/components/explorer_ui_events_result.proto new file mode 100644 index 00000000..db78c870 --- /dev/null +++ b/proto/decentraland/sdk/components/explorer_ui_events_result.proto @@ -0,0 +1,26 @@ +syntax = "proto3"; +package decentraland.sdk.components; + +import "decentraland/sdk/components/common/id.proto"; +import "decentraland/sdk/components/common/explorer_ui.proto"; + +option (common.ecs_component_id) = 1220; + +// PBExplorerUiEventsResult transports events for when fullscreen explorer panels are opened or closed. +message PBExplorerUiEventsResult { + // Emitted when a fullscreen explorer panel is opened. + message UiOpened {} + + // Emitted when a fullscreen explorer panel is closed. + message UiClosed {} + + decentraland.sdk.components.common.ExplorerUi ui = 1; // The panel that the event refers to + uint32 timestamp = 2; // The scene tick when the event occurred + + // Extension point: generic window events occupy field numbers 10-19. + // Future per-panel feature events (e.g., BackpackItemSelected) start at 20. + oneof event { + UiOpened opened = 10; + UiClosed closed = 11; + } +} diff --git a/public/sdk-components.proto b/public/sdk-components.proto index f4198460..f1a19d05 100644 --- a/public/sdk-components.proto +++ b/public/sdk-components.proto @@ -14,6 +14,7 @@ import public "decentraland/sdk/components/billboard.proto"; import public "decentraland/sdk/components/camera_mode_area.proto"; import public "decentraland/sdk/components/camera_mode.proto"; import public "decentraland/sdk/components/engine_info.proto"; +import public "decentraland/sdk/components/explorer_ui_events_result.proto"; import public "decentraland/sdk/components/gltf_container.proto"; import public "decentraland/sdk/components/gltf_node_modifiers.proto"; import public "decentraland/sdk/components/gltf_container_loading_state.proto"; From f7de7960acccecf163ee074f664073722d5c0466 Mon Sep 17 00:00:00 2001 From: Vitaly Popuzin Date: Thu, 30 Jul 2026 19:51:02 +0200 Subject: [PATCH 2/2] docs: constrain the PBExplorerUiEventsResult extension point `ui` and `event` are independent axes, so every pair of the two is representable. The previous comment invited per-panel feature events into the same oneof at field 20+, which would have made pairs such as `ui = EU_MAP` carrying a backpack event perfectly legal, and pushed the burden of recognising and ignoring them onto every consumer. State the rule instead: a variant belongs in this oneof only if it is meaningful for every value of `ui`. Panel-specific events get their own result component, where the component identity carries the panel and the contradiction cannot be expressed. That also matches how the rest of the protocol scales -- PBUiDropdownResult, PBUiInputResult, PBTriggerAreaResult, PBVideoEvent and PBAudioEvent are separate components rather than variants of one shared event bus. Comment-only change; the wire format is untouched. --- .../sdk/components/explorer_ui_events_result.proto | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/proto/decentraland/sdk/components/explorer_ui_events_result.proto b/proto/decentraland/sdk/components/explorer_ui_events_result.proto index db78c870..69f5c0c6 100644 --- a/proto/decentraland/sdk/components/explorer_ui_events_result.proto +++ b/proto/decentraland/sdk/components/explorer_ui_events_result.proto @@ -6,7 +6,9 @@ import "decentraland/sdk/components/common/explorer_ui.proto"; option (common.ecs_component_id) = 1220; -// PBExplorerUiEventsResult transports events for when fullscreen explorer panels are opened or closed. +// PBExplorerUiEventsResult transports the lifecycle events of fullscreen explorer panels — a panel was +// opened, a panel was closed. It is a grow only value set appended to the scene root entity, so every +// event of a tick is delivered and none overwrites another. message PBExplorerUiEventsResult { // Emitted when a fullscreen explorer panel is opened. message UiOpened {} @@ -17,8 +19,11 @@ message PBExplorerUiEventsResult { decentraland.sdk.components.common.ExplorerUi ui = 1; // The panel that the event refers to uint32 timestamp = 2; // The scene tick when the event occurred - // Extension point: generic window events occupy field numbers 10-19. - // Future per-panel feature events (e.g., BackpackItemSelected) start at 20. + // Extension point, deliberately narrow: a variant belongs here only if it is meaningful for every + // value of `ui`, because `ui` and `event` are independent axes and every combination of the two is + // representable. An event that belongs to one panel alone (an item was worn in the backpack, a photo + // was taken in the camera reel) is not added here — it gets its own result component, where the + // component identity carries the panel and no contradictory pair can be expressed at all. oneof event { UiOpened opened = 10; UiClosed closed = 11;