Frappe's tab model does not know subtabs exist. Tab.set_active() strips
`show active` off every pane except the one it activates, and Frappe calls it on
every refresh_sections() — which runs on every refresh_field() and every value
change. So editing any field wiped the open subtab: its content appeared for a
fraction of a second and vanished.
Clicking a subtab now registers its top-level ancestor as the active tab, so
Frappe's own bookkeeping stays coherent and keeps re-activating the right tab, and
Tab.set_active is wrapped to re-assert the rest of the chain once Frappe is done.
The whole ancestor chain has to be restored, not just the leaf: subtabs nest two
deep in several declarations, where the top pane holds the first subtab bar, the
middle pane holds the second and the leaf holds the content — three panes active
at once, which Frappe's one-tab-at-a-time model will never do on its own.
This replaces the MutationObserver, which was fighting the same battle from the
wrong end: it could not tell a genuine tab switch from a refresh re-asserting the
tab already open.
Verified: 70 subtabs across 11 doctypes stay visible through refresh_field() and
refresh_sections().
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Rename custom_subtabs.js/.css to .bundle.js/.bundle.css so esbuild emits
content-hashed filenames → automatic cache-busting (no more nginx 1-year
stale-asset issue on edits). setup_custom_subtabs is defined and called
within the file (verified self-contained), so esbuild's IIFE wrap is safe.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The subtab bar lives inside the parent tab pane. When the doctype's field_order
places the subtab content panes BEFORE the parent pane (e.g. E-Taxes Settings
"Data": customers_tab/suppliers_tab come before data_tab), the activated content
renders above the bar and pushes it off-screen — looking like the subtabs vanished.
Parents whose children come after them (e.g. "Mappings") were unaffected.
On subtab click, move the active content pane to sit right after the parent pane
so the bar always renders above it.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
clone(true) copied Frappe's per-nav-link click handler (tab.js -> set_active)
onto the subtab buttons, so every subtab click also ran set_active(), which
deactivates the parent tab pane that physically contains the subtab buttons.
Our handler then had to undo it by reconstructing the parent pane id from the
(translated) label — fragile.
Drop the copied handler on cloned subtab links, and restore the parent pane
by its reliable data-parent-id instead of label string-building.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
tab_hierarchy stores raw (English) Tab Break labels, but Frappe renders
tab text through __(), so under AZ/RU the DOM text never equals the
hierarchy key and subtabs silently stay flat (e.g. E-Taxes Settings,
Bank Integration Profile). Doctypes with native-AZ labels were unaffected.
Compare DOM tab text against tr(key) at the matching boundary; keep all
internal bookkeeping (childToParentMap, data-subtab-name) in English so
clicks and parent-chain reveal stay consistent across languages.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Problem:
- boot_session_handler was failing silently for some DocTypes
- Direct access to field.js_parent_subtab caused AttributeError
- DocTypes with errors prevented entire hierarchy from being built
Solution:
- Added try-except around DocType processing loop
- Changed field.js_parent_subtab to getattr(field, 'js_parent_subtab', None)
- Errors are logged but don't break the entire boot process
Result:
- All DocTypes with subtabs now properly included in tab_hierarchy
- "Declaration of value added tax" and similar DocTypes now work correctly
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Problem:
- When switching to a tab without subtabs, the subtab text was hidden
- But the container remained visible with border and took up space
- Invisible buttons were still clickable and could switch to previous subtabs
- Cursor changed to pointer over the invisible container
Solution:
- Changed logic to hide/show entire .sub-tab-container instead of individual .sub-tab elements
- Now container is completely removed from layout when not active
- Prevents phantom clicks and visual artifacts
Changed in custom_subtabs.js (lines 192, 194):
- Before: $(this).find(".sub-tab").show/hide()
- After: $(this).show/hide()
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Added frappe.reload_doc() after modifying docfield.json
- Added frappe.clear_cache() to ensure changes are applied
- Now patch automatically syncs JSON changes to database
- Fixed issue where js_parent_subtab field existed in JSON but not in DB
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>