Skip to content

fix(bags): rebuild the category cache until the profile can be trusted - #1227

Open
dfrisone wants to merge 1 commit into
EllesmereGaming:mainfrom
dfrisone:bags-category-reroot-cache
Open

fix(bags): rebuild the category cache until the profile can be trusted#1227
dfrisone wants to merge 1 commit into
EllesmereGaming:mainfrom
dfrisone:bags-category-reroot-cache

Conversation

@dfrisone

@dfrisone dfrisone commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

The report

Reported by @ercarp on 8.7.3: in the standalone bag addon, custom categories vanish on every /reload or relog. Simple settings like scale and columns are unaffected.

Cause

The category list is a derivation of db.profile, not a view of it:

  • InitCategories() builds it into CategoryManager._categories
  • GetCategories() rebuilds only when that is nil
  • SaveState() reads it back and writes it to the profile

So the cache is the source of truth for the next save. That is why categories die while sliders live: sliders read db.profile fresh every time, categories do not.

EllesmereUI_Lite already documents why only standalone is affected:

a file-scope NewDB in the addon that OWNS the central store runs before WoW loads that addon's SavedVariables ... In the suite this can't happen (children's files execute long after the parent's SVs loaded), but in a STANDALONE build everything is one addon

The db is built inside an orphan table and re-rooted in place at ADDON_LOADED. Nothing told consumers that had happened. Bags caches a list derived from the orphan (defaults-only) profile, and the first SaveState() after any category action stamps that snapshot over the user's real data.

BP() falls back to an empty table, so the early build fails silently instead of erroring. That is why it presents as "categories were never saved" rather than "categories were overwritten".

Fix

  • Lite exposes EUILite.IsDBReady(): unconditionally true in the suite, true in standalone once the owning addon's SavedVariables are live.
  • InitCategories records whether the build saw a trustworthy profile, and GetCategories rebuilds until one did.
  • SaveState refuses to write while the db is not ready, so a future timing regression degrades to a no-op rather than data loss.

Inert in the suite by construction: IsDBReady() is unconditionally true there, so the rebuild condition is satisfied on the first build and the save guard never trips.

Pull, not push

Worth flagging for review because I changed approach after self-review.

The first version registered a Lite callback from the categories file at file scope, to drop the cache when the db was re-rooted. That works in the suite, where Lite is guaranteed to load first. But in a standalone build everything is one addon, and that ordering is precisely what I cannot verify — it is the same assumption that caused the bug in the first place. If the categories file happened to load before the framework, the registration would silently no-op and the fix would do nothing, in the only packaging that needs it.

Reading readiness at GetCategories() time has no such dependency: by the time anything reads categories, the framework is certainly present. A missing framework counts as not-ready, so the answer can only improve.

What I deliberately did not do

The reporter also proposed a fix: remove the file-scope InitCategories() and call it unconditionally from EUI_Bags_Options.lua's PLAYER_LOGIN. His diagnosis was correct and is what put me on the right track, but I did not take that shape.

GetCategories() self-initialises, so removing the explicit call does not remove the hazard, it hands it to whichever caller touches categories first. And it would make the options file responsible for the category module's lifecycle, which is the same class of ordering assumption that caused the bug.

Cat Tail GIF

Reported by @ercarp on 8.7.3: in the STANDALONE bag addon, custom categories
vanish on every /reload or relog.

The category list is a DERIVATION of db.profile: built once into
CategoryManager._categories, rebuilt by GetCategories() only when that is nil,
and read BACK by SaveState() to write the profile. The cache is therefore the
source of truth for the next save, which is why categories die while sliders
live -- sliders read db.profile fresh every time.

EllesmereUI_Lite already documents why only standalone is affected: a
file-scope NewDB there runs before the owning addon's SavedVariables load, so
the db is built inside an orphan table and re-rooted in place at ADDON_LOADED.
In the suite child files execute long after the parent's SVs are live.

Nothing told consumers about that re-root. Bags caches a list derived from the
orphan (defaults-only) profile, and the first SaveState() after any category
action stamps that snapshot over the user's real data. BP() falls back to an
empty table, so the early build fails silently rather than erroring, which is
why it looks like the categories were never written rather than overwritten.

Fix:
  * Lite exposes EUILite.IsDBReady() -- unconditionally true in the suite,
    true in standalone once the owning addon's SavedVariables are live.
  * InitCategories records whether the build saw a trustworthy profile, and
    GetCategories rebuilds until one did.
  * SaveState refuses to write while the db is not ready, so a future timing
    regression degrades to a no-op instead of data loss.

PULL, not push. The first version of this registered a Lite callback from the
categories file at file scope to drop the cache on re-root. That works in the
suite, where Lite is guaranteed loaded first, but in a standalone build
everything is one addon and that ordering is exactly what I cannot verify --
the same assumption that caused the bug. Reading readiness at GetCategories
time has no such dependency, because by the time anything reads categories the
framework is certainly present. A missing framework counts as not-ready, so
the answer can only improve.

Deliberately NOT done: moving the file-scope InitCategories() into a
PLAYER_LOGIN handler. GetCategories() self-initialises, so any early caller
re-caches the stale list; relocating the explicit call would move the bug
rather than remove it, and would put the category module's lifecycle in the
options file.
@dfrisone
dfrisone force-pushed the bags-category-reroot-cache branch from 03ae5c6 to 4914646 Compare August 6, 2026 20:28
@dfrisone dfrisone changed the title fix(bags): rebuild the category cache when a pre-SavedVariables db is re-rooted fix(bags): rebuild the category cache until the profile can be trusted Aug 6, 2026
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