Skip to content

fix: prevent toolbar disappearing after reload when tabs are renamed (#3167)#3183

Merged
jmcouffin merged 3 commits intopyrevitlabs:developfrom
tay0thman:fix/3167-tab-rename-reload-regression
Mar 21, 2026
Merged

fix: prevent toolbar disappearing after reload when tabs are renamed (#3167)#3183
jmcouffin merged 3 commits intopyrevitlabs:developfrom
tay0thman:fix/3167-tab-rename-reload-regression

Conversation

@tay0thman
Copy link
Copy Markdown
Contributor

Summary

Fixes #3167 — pyRevit toolbar disappears after reload when a startup script
renames tab titles via AdWindows.ComponentManager.Ribbon.Tabs (e.g. for
translation). Exclusive to the new C# loader.

Root Cause

RibbonScanner creates a fresh instance on every reload and registers tabs
by their current Title property. When a script renames a tab (e.g. from
"MyExtension" to "Mi Extensión"), the scanner registers "tab:Mi Extensión".
But UIManagerService.HandleTab() marks "tab:MyExtension" as touched. The
cleanup phase sees the translated key as untouched and hides the tab.

The legacy Python loader is immune because it tracks tabs by object reference
via a module-level singleton, not by mutable display name.

Fix

TabBuilder.csCreateTab() and TagTabAsPyRevit() now fall back to
searching by Tag == PyRevitTabIdentifier + Id containing the original
name when the Title match fails.

UIManagerService.csHandleTab() detects when a tab's current Title
differs from the extension name and dual-marks both names in the scanner
registry. The renamed title propagates to HandlePanel() so panel keys
are also dual-marked. Uses fully qualified Autodesk.Windows.ComponentManager
to avoid CS0104 ambiguity with Autodesk.Revit.UI.RibbonPanel.

Testing

  • Tab renamed by translation script → survives reload
  • Multiple reloads → no cumulative degradation
  • Extensions without translation → no regression
  • Build succeeds on both NETFX and NETCORE configurations

Copy link
Copy Markdown
Contributor

@devloai devloai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR Summary:

This PR claims to fix issue #3167 — pyRevit toolbar disappearing after reload when a startup script renames tab titles. The described fix involves dual-marking tab registry keys in UIManagerService.HandleTab() and adding Id-based tab lookup fallback in TabBuilder. Both TabBuilder.cs and UIManagerService.cs are listed as changed files.

Review Summary:

After inspecting the actual diff and confirmed via the GitHub API, the only changes in both files are the removal of a trailing newline (No newline at end of file). The entire fix described in the PR body — dual-marking tabs, Id-based fallback in TagTabAsPyRevit(), and propagating renamed titles to HandlePanel() — is not present in the committed code. The root cause of #3167 remains live: HandleTab() (line 301–315) still calls MarkElementTouched with only the localized tabText, leaving the original registry key untouched and allowing the cleanup phase to hide the tab on reload.

Follow-up suggestions:

  • @devloai fix the identified issues — implement the dual-marking logic in HandleTab() and the Id-based fallback in TagTabAsPyRevit() as described in the PR body.

@tay0thman
Copy link
Copy Markdown
Contributor Author

@devloai please review the updates in TabBuilder and UIManagerService, provide report of any concerns.

Copy link
Copy Markdown
Contributor

@devloai devloai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review: TabBuilder & UIManagerService

The approach is sound — dual-marking both the original and renamed tab title in the scanner registry is the right conceptual fix. However, there are a few logic issues that would prevent the fix from working end-to-end in the rename scenario:

3 issues found (2 high, 1 medium)

  1. Circular Tag dependency in CreateTab/TagTabAsPyRevit fallback (High) — The fallback that finds a renamed tab by Tag == PyRevitTabIdentifier && Id contains tabText can never fire for the initial-load case, because TagTabAsPyRevit is the method that sets the tag. So existingTab.IsVisible = true and the tag assignment are both skipped for renamed tabs — the tab stays invisible even if the dual-mark in HandleTab correctly touches the registry.

  2. Substring Id match is too broad (High, TabBuilder.cs) — Id.IndexOf(tabText) is a substring search that could falsely match sibling extensions (e.g. "BIM" matching "BIM Tools"). An exact Id comparison would be safer.

  3. renamedTabTitle not propagated through HandlePanel's RecursivelyBuildUI call (Medium) — HandlePanel dual-marks the panel itself correctly, but its child items (buttons in stacks, split buttons, etc.) are recursed without renamedTabTitle, so those button registry keys remain un-dual-marked and will be orphaned during cleanup.

See inline comments for details and suggested fixes.

@tay0thman tay0thman force-pushed the fix/3167-tab-rename-reload-regression branch from 649a5cc to 55cb428 Compare March 17, 2026 18:51
@tay0thman
Copy link
Copy Markdown
Contributor Author

@devloai , I pushed new commits with updated files, review once more and let me know

@devloai
Copy link
Copy Markdown
Contributor

devloai bot commented Mar 17, 2026

Hey @tay0thman! I reviewed the new commits, but unfortunately the picture hasn't changed. 👻

The latest push again only modifies trailing newlines (and updates pre-compiled .dll binaries). The source code in TabBuilder.cs and UIManagerService.cs is functionally identical to the previous review.

The three issues from my last review remain open:

  1. 🔴 Circular Tag dependency (UIManagerService.cs / CreateTab path): TagTabAsPyRevit sets tab.Tag = PyRevitTabIdentifier, but the fallback in CreateTab searches by that tag. On first reload after a rename, the tag hasn't been set yet, so the fallback is a no-op.

  2. 🔴 Substring Id match too broad (TabBuilder.cs line 89): t.Id == tabName is fine as written now in TagTabAsPyRevit, but CreateTab at line 60 uses t.Id == tabText — also fine — so this is cleared. ✅ (I was reading the old diff; current code uses == not IndexOf.)

  3. 🟡 HandleTab() marks only the renamed title (UIManagerService.cs line 310): MarkElementTouched("tab", tabText) registers "tab:Mi Extensión" but the scanner scanned "tab:MyExtension" on startup. The cleanup phase still sees "tab:MyExtension" as untouched → tab hidden. The fix described in the PR (dual-marking both the original component.DisplayName and tabText) is not present in the code:

// In HandleTab():
_ribbonScanner?.MarkElementTouched("tab", tabText);
// ADD: also mark the original name so cleanup doesn't orphan it
if (tabText != component.DisplayName)
    _ribbonScanner?.MarkElementTouched("tab", component.DisplayName);

The core bug from #3167 is still not fixed in the source. Could you push the actual source changes? Happy to re-review immediately once they're up. 🙂

@tay0thman
Copy link
Copy Markdown
Contributor Author

@devloai , I added the latest edits, please review the 2 new commits

Copy link
Copy Markdown
Contributor

@devloai devloai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @tay0thman! Third time's the charm — the 2 new commits contain real functional changes this time. 🎉

Summary

The core fixes from #3167 are now actually in the code. Here's what I verified:

✅ Fixes confirmed correct

TabBuilder.cs — Circular Tag dependency resolved:
The old approach called TagTabAsPyRevit (which needs the Tag to be set) before the Tag was set. The new code combines tagging + re-enabling into a single CreateTab block: sets Tag = PyRevitTabIdentifier first, then IsVisible = true. The Tag-based fallback in TagTabAsPyRevit still works correctly for standalone calls.

UIManagerService.cs — Dual-mark fix is present:
HandleTab() now correctly identifies renamed tabs and calls MarkElementTouched for both the original extension name and the renamed Title. This is the core fix — cleanup's untouchedElements check will no longer see the renamed Title as orphaned.

Panel propagation: renamedTabTitle is correctly threaded through RecursivelyBuildUIHandlePanel → panel dual-mark. Well done.

One minor nit

I left one low-severity comment about a redundant ribbon scan (the tab is found once inside CreateTab and again inside HandleTab). Not a correctness issue, just a DRY opportunity.

The fix logic is sound. Ready for maintainer review.

@tay0thman tay0thman force-pushed the fix/3167-tab-rename-reload-regression branch 2 times, most recently from 22b2a6a to ef9e4b6 Compare March 17, 2026 19:16
@tay0thman tay0thman closed this Mar 20, 2026
@tay0thman tay0thman force-pushed the fix/3167-tab-rename-reload-regression branch from ef9e4b6 to c65de4f Compare March 20, 2026 07:42
…yrevitlabs#3167)

Root cause: RibbonScanner registers tabs by their mutable Title property.
When a script (e.g. translation) renames tab.Title, the scanner registry
key no longer matches what the UI builder marks as touched. Cleanup then
deactivates the tab as orphaned.

Fix:
- ITabBuilder: CreateTab returns string? (renamed title if detected)
- TabBuilder: single ribbon scan — find, tag, enable, detect rename.
  Uses exact Id match via string.Equals (not substring).
- UIManagerService: HandleTab uses CreateTab return value (no redundant
  scan). Dual-marks both original and renamed Title in scanner registry.
  renamedTabTitle propagated through HandlePanel child recursion.
@tay0thman tay0thman reopened this Mar 20, 2026
@tay0thman
Copy link
Copy Markdown
Contributor Author

@devloai I had to resolve some conflicts, can you confirm the commited files resolve the issue?

@devloai
Copy link
Copy Markdown
Contributor

devloai bot commented Mar 20, 2026

Hey @tay0thman! ✅ Yes — the conflict resolution commit contains the real functional fixes. Everything looks correct.

End-to-end verification:

Component Fix Status
ITabBuilder.cs CreateTab returns string? (renamed title or null)
TabBuilder.CreateTab() Single ribbon scan: find → tag → enable → detect rename; exact string.Equals Id match (not substring)
TabBuilder circular dependency Tag is set before it's needed by fallback (no chicken-and-egg problem)
UIManagerService.HandleTab() Dual-marks both tabText and renamedTabTitle in scanner registry
UIManagerService.HandlePanel() Accepts and propagates renamedTabTitle through child recursion
RibbonScanner.CleanupOrphanedElements() Looks up tabs by tab.Title ?? tab.Id — with dual-mark both keys are touched, tab survives

The root cause of #3167 is addressed: when a script renames a tab (e.g. "MyExtension""Mi Extensión"), the scanner now marks both the original and renamed title as touched, so cleanup no longer deactivates it as orphaned.

The previously flagged issues (circular tag dependency, redundant ribbon scan, substring Id match) are all resolved in this commit. 🎉

@jmcouffin jmcouffin merged commit 14c56e2 into pyrevitlabs:develop Mar 21, 2026
@github-actions
Copy link
Copy Markdown
Contributor

📦 New work-in-progress (wip) builds are available for 6.1.0.26080+1314-wip

@github-actions
Copy link
Copy Markdown
Contributor

📦 New work-in-progress (wip) builds are available for 6.1.0.26083+2031-wip

@github-actions
Copy link
Copy Markdown
Contributor

📦 New work-in-progress (wip) builds are available for 6.1.0.26083+2042-wip

@github-actions
Copy link
Copy Markdown
Contributor

📦 New work-in-progress (wip) builds are available for 6.1.0.26083+2045-wip

@github-actions
Copy link
Copy Markdown
Contributor

📦 New work-in-progress (wip) builds are available for 6.1.0.26083+2048-wip

@github-actions
Copy link
Copy Markdown
Contributor

📦 New work-in-progress (wip) builds are available for 6.1.0.26083+2106-wip

@github-actions
Copy link
Copy Markdown
Contributor

📦 New work-in-progress (wip) builds are available for 6.1.0.26083+2130-wip

@github-actions
Copy link
Copy Markdown
Contributor

📦 New work-in-progress (wip) builds are available for 6.1.0.26086+2004-wip

@github-actions
Copy link
Copy Markdown
Contributor

📦 New work-in-progress (wip) builds are available for 6.1.0.26088+1318-wip

@github-actions
Copy link
Copy Markdown
Contributor

📦 New work-in-progress (wip) builds are available for 6.1.0.26089+1231-wip

@github-actions
Copy link
Copy Markdown
Contributor

📦 New work-in-progress (wip) builds are available for 6.1.0.26090+0549-wip

@github-actions
Copy link
Copy Markdown
Contributor

📦 New work-in-progress (wip) builds are available for 6.1.0.26090+1533-wip

@github-actions
Copy link
Copy Markdown
Contributor

📦 New work-in-progress (wip) builds are available for 6.1.0.26090+1536-wip

@github-actions
Copy link
Copy Markdown
Contributor

📦 New work-in-progress (wip) builds are available for 6.1.0.26090+1540-wip

@github-actions
Copy link
Copy Markdown
Contributor

📦 New work-in-progress (wip) builds are available for 6.1.0.26090+1540-wip

@github-actions
Copy link
Copy Markdown
Contributor

📦 New work-in-progress (wip) builds are available for 6.1.0.26090+1556-wip

@github-actions
Copy link
Copy Markdown
Contributor

📦 New public release are available for 6.2.0.26090+1754

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.

[Bug]: The pyRevit and EF_Tools tabs disappeared after click Reload

2 participants