Skip to content

Bags: track currencies per character instead of per profile - #1251

Open
dfrisone wants to merge 2 commits into
EllesmereGaming:mainfrom
dfrisone:bags-currency-per-char
Open

Bags: track currencies per character instead of per profile#1251
dfrisone wants to merge 2 commits into
EllesmereGaming:mainfrom
dfrisone:bags-currency-per-char

Conversation

@dfrisone

@dfrisone dfrisone commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

The bug

Reported by a user who wanted one currency tracked on their main and a different one on the alt that farms it: the bag's currency display was identical on every character. Unticking everything and re-picking, from either the EUI dropdown or Blizzard's own currency tab, always ended with every character showing the same set.

Root cause

The bag footer does not render Blizzard's tracked currencies. It renders the module's own currencyOrder table (the code comment says "decoupled from Blizzard"), and that table lives in the bag profile, so every character on the profile shares it.

The input feeding it is not shared. A TokenFrame.OnTokenWatchChanged callback syncs Blizzard's currency tab, which is per-character, into that one profile-level table. A per-character source writing a shared store can only ever bleed: ticking a currency on one character wrote it into everyone's bag.

That also explains the half of the report that sounded impossible, that Blizzard's own per-character tracking looked broken. EUI never writes that tab, and there is no SetCurrencyBackpack-family call anywhere in the module, only reads. What the user saw was the bag ignoring their per-character selection and showing the shared list instead.

The fix

Tracked currencies move to EllesmereUIDB.bagCurrencyByChar, keyed by name and realm.

Storage is at the DB root, not in the profile. The module already keeps its other per-character data there (characterGold, bagPinnedItems, bagItemAssignments), and it is cleared alongside them by the options page's per-character reset. Profile storage would have been wrong twice over:

  • ApplyProfileData wipes db.profile wholesale before copying an imported snapshot, so importing any shared profile would have erased every character's currencies and substituted the exporter's.
  • Profile exports would have shipped a roster of the author's character names, realms and per-alt currency lists to whoever imported it. That is exactly the leak PRIVATE_ADDON_KEYS exists to plug for the DataBars gold ledger and the QoL upgrade calculator.

Root storage also removes a failure mode the profile version had: a spec-driven profile switch (RepointAllDBs, no reload) could hand a character a freshly created empty table mid-session with nothing to seed it, blanking the footer for the rest of the session. Root data does not move with profiles, so first use happens once per character, ever.

Each character seeds once from the legacy shared table, so an upgrading user keeps exactly what they had and only diverges as they change things. The legacy table is deliberately left in place rather than deleted or migrated away: a per-character migration is not a one-shot, it runs once per character at that character's first login, so the seed source has to survive indefinitely. A character that has not logged in since the update still needs it, or it would open with an empty footer. Nothing writes to it any more, so the seed stays stable. Fresh installs are unchanged and still seed from Blizzard's tracked set.

One accessor owns the location and the seeding. The options dropdown goes through EllesmereUI._BagsCurrencyOrder rather than reaching for db.profile.currencyOrder, so there is a single definition of where this data lives. Because the TOC loads the options file before the module, that call is a runtime namespace lookup rather than a local captured at load time.

The character key is resolved once and cached. It is session-constant and was being rebuilt, two API calls plus a string allocation, on a render path driven by CURRENCY_DISPLAY_UPDATE. It refuses to cache before UnitName and GetRealmName both answer, so an early call returns nil rather than pinning a stub key for the session.

Scope

Two files, +70/−14. No new events, no timers, no API calls added. bagCurrencyByChar is deliberately absent from BAGS_DEFAULTS: StripDefaults and DeepMergeDefaults only walk keys present in the defaults table, so the per-character data is never stripped or masked.

Testing

Confirmed in game: currencies ticked on one character no longer follow to another, existing setups carry over untouched on upgrade, and Blizzard's currency tab now drives only the character it belongs to.

South Park Animation GIF

Reported by a user wanting one currency on their main and another on the alt
that farms it: every character showed the same set, from either the EUI
dropdown or Blizzard's currency tab.

The bag footer renders from the module's own currencyOrder table rather than
Blizzard's tracked set, and that table is profile-level, so it is shared by
every character on the profile. The input feeding it is not: a
TokenFrame.OnTokenWatchChanged callback syncs Blizzard's currency tab, which
IS per-character, into that shared table. A per-character source writing a
shared store can only bleed, which is why ticking a currency on one character
put it in everyone's bag and made Blizzard's own per-character tab look
broken. (EUI never writes that tab; there is no SetCurrencyBackpack-family
call in the module. It only ever reads it.)

Tracked currencies now live under currencyOrderByChar, keyed by name-realm,
which is the same key the profile system already uses for lastSpecByChar.
Storage stays inside the Bags profile so it keeps riding the existing
plumbing: defaults merge, logout strip, export. A profile exported to someone
else simply carries keys their characters do not match, and they seed fresh.

Each character seeds once from the legacy shared table, so an upgrading user
keeps exactly what they had and only diverges as they change things. The
legacy table is deliberately left in place rather than deleted or migrated:
characters that have not logged in since the upgrade still need it to seed
from, and nothing writes to it any more, so the seed stays stable. Fresh
installs are unchanged and still seed from Blizzard's tracked set.

The options dropdown goes through the module's accessor rather than reaching
for db.profile.currencyOrder, so there is one definition of where this data
lives and one place that owns the seeding.
…profile

Self-review catch on the previous commit, which put currencyOrderByChar
inside the bag profile. Two ways that was the wrong home, both with existing
precedent in this very module:

ApplyProfileData wipes db.profile wholesale (for k in pairs(profile) do
profile[k] = nil end) before copying an imported snapshot, so importing any
shared profile would have erased EVERY character's currencies and replaced
them with the exporter's keys.

Profile exports would also have shipped a roster of the author's character
names, realms and per-alt currency lists to anyone importing it. That is
exactly the leak PRIVATE_ADDON_KEYS exists to plug for DataBars' gold ledger
and QoL's upgrade calculator.

The module already keeps its other per-character data at the EllesmereUIDB
root (characterGold, bagPinnedItems, bagItemAssignments), so this joins them
as bagCurrencyByChar and is cleared alongside them by the per-character reset
in the options page. Root storage also removes a failure the profile version
had: a spec-driven profile switch could hand a character a freshly created
empty table mid-session, with nothing to seed it and a blank footer for the
rest of the session. Root data does not move with profiles, so first use
happens once per character, ever.

The character key is now resolved once and cached. It is session-constant and
was being rebuilt, two API calls plus a string, on a render path driven by
CURRENCY_DISPLAY_UPDATE. It refuses to cache before UnitName and GetRealmName
are both available, so an early call returns nil rather than pinning a stub
key for the session.
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