Skip to content

feat: add public external module registration API#455

Open
xanahopper wants to merge 3 commits into
EllesmereGaming:mainfrom
xanahopper:feature/external-modules-api
Open

feat: add public external module registration API#455
xanahopper wants to merge 3 commits into
EllesmereGaming:mainfrom
xanahopper:feature/external-modules-api

Conversation

@xanahopper

Copy link
Copy Markdown

Why

I plan to port ElvUI_WindTools into EllesmereUI_WindTools, and that requires a supported way for third-party addons to integrate with EllesmereUI as native modules without relying on internal allowlists or fragile implementation details.

This PR adds a versioned extension surface so external modules can register their own sidebar entry, options pages, and lifecycle callbacks while keeping the host options UI protected from module errors.

What Changed

  • Added EllesmereUI.API_VERSION as the compatibility contract for external modules.
  • Added EllesmereUI_ExternalModules.lua and loaded it from EllesmereUI.toc.
  • Added EllesmereUI.RegisterModuleCallback and EllesmereUI.FireModuleCallback for host lifecycle events.
  • Added EllesmereUI.RegisterExternalModule(spec) for third-party addons to register:
    • sidebar metadata
    • options page config
    • page builder callbacks
    • reset/cache-restore callbacks
    • API version requirements
  • Added caller-folder validation so an addon can only register itself.
  • Wrapped external module callbacks with xpcall so module failures surface through the normal error handler without breaking the EllesmereUI options panel.
  • Added an External sidebar group for registered third-party modules.
  • Marked external modules as profile-sync exempt to avoid misleading sync UI.
  • Fired a ProfileChanged callback when EllesmereUI profiles are switched so external modules can rebuild cached profile state.
  • Split the internal module config writer into _RegisterModuleConfig so both official modules and validated external modules can share the same registration path.

Note

This PR was generated with AI assistance, and has been manually tested and reviewed.

Test Case like this:

e59dc0d86f2bca282bc0454b8bb61785

Foundational infrastructure for the External Module API. No behavior
change for existing modules; only adds opt-in surfaces.

* EllesmereUI.API_VERSION = 1 -- constant external modules check against
  via spec.apiVersion (reject/mismatch path comes with RegisterExternalModule).
* EllesmereUI_ExternalModules.lua -- new file. Hosts the callback bus
  today; will host RegisterExternalModule in a follow-up commit. Lives
  on its own chunk so EllesmereUI.lua (already at the 200-upvalue limit)
  gains zero new locals.
* EllesmereUI.RegisterModuleCallback / FireModuleCallback -- minimal
  event bus. Every listener xpcall'd through the global error handler
  so one bad callback cannot block the host lifecycle.
* EllesmereUI.SwitchProfile fires "ProfileChanged" at both exit paths
  (sync-reload prompt and normal switch). Covers manual switch, the
  SetProfile alias, and PLAYER_SPECIALIZATION_CHANGED mid-session.
  Login-time PreSeedSpecProfile deliberately does NOT fire (external
  modules read the right values via OnEnable).
Third-party addons can now register as native EllesmereUI modules with
their own sidebar entry, options page, and tab list.

* EllesmereUI._RegisterModuleConfig -- internal allowlist-free writer,
  extracted from RegisterModule's post-allowlist body. Defined as a
  method (not a local) so EllesmereUI.lua's main chunk gains zero new
  upvalues (still at the Lua 5.1 200-upvalue limit). Behavior for
  official modules is unchanged: same code path, just relocated.

* EllesmereUI.RegisterExternalModule(spec) -- public API in
  EllesmereUI_ExternalModules.lua. Pipeline:
    1. Validate required spec fields (folder/display/apiVersion/pages/
       buildPage).
    2. Verify the caller's folder (via debugstack) matches spec.folder
       -- prevents addon A from spoofing addon B's identity.
    3. API version handshake against EllesmereUI.API_VERSION: reject
       if module requires newer, warn if targets older, accept on match.
    4. Inject into ADDON_ROSTER, _addonInfoByFolder, and a dedicated
       "external" ADDON_GROUPS entry (lazy-created, labeled "External",
       routed through EllesmereUI.L for community translation).
    5. Mark _syncExempt[folder] = true -- external modules don't
       implement the host profile-sync protocol; showing the sync icon
       would mislead users.
    6. Wrap buildPage / onPageCacheRestore / onReset in xpcall so a
       buggy module cannot brick the options UI. buildPage errors
       render a red-text notice on the page wrapper instead of crashing.

* spec schema is documented in the file header. Notably, the sidebar
  row has no left icon (info.icon is reserved for future use), and
  comingSoon/maintenance mirror the official module semantics (greyed
  row, no power toggle).

Idempotent: re-registration of the same folder replaces in-place rather
than appending duplicates.

Mid-session refresh (when the panel is already open) is intentionally
NOT triggered: RefreshSidebarStates is a file-local declared further
down in EllesmereUI.lua and unreachable from this scope without
restructuring forward declarations. External modules should register
at OnEnable (PLAYER_LOGIN), same as official modules do.
@xanahopper xanahopper force-pushed the feature/external-modules-api branch 2 times, most recently from cf11fca to b460f36 Compare June 25, 2026 13:21
@xanahopper

Copy link
Copy Markdown
Author

hello~ can this PR be merged?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant