Release Notes — v0.2.14 (July 6, 2026)


1. Tab-Preserving Form Save Response for Detail Views

When saving a form on a BaseDetailView with tab groups, the HTMX response previously returned main_object_form.html — a flat form partial with no tab structure. This caused the #form-container innerHTML replacement to destroy the Alpine.js tab bar and all tab-pane layout. A new dedicated template now preserves the tab structure in the save response.

Problem

  • Form action URL included ?edit=1, causing get_template_names() to return main_object_form.html regardless of whether the view had tab groups.
  • main_object_form.html renders all fieldtabs in a single flat accordion — no tab bar, no tab panes.
  • The submit_main_object_form_form() JS replaced #form-container innerHTML with the flat response, destroying the Alpine.js tab component entirely.
  • After save, the user saw a flat form without tabs and had to reload the page to restore the tab layout.

Fix

New Template: core/partials/detail_form_tabs_partial.html

A new partial template that wraps form fields and inline sections inside the Alpine.js tab structure, identical to the tab portion of detail_partial.html. Includes:

  • Alpine.js tab bar (x-data="{ tab: '{{ tab_groups.0.key }}' }") with x-show panes
  • fieldtabs_accordion.html rendered per-group inside tab panes
  • inline_items_section.html rendered per-group for tab-assigned inlines
  • TomSelect initialization script (initTomSelects())

Template Resolution (base.py:364-370)

BaseDetailView.get_template_names() now checks get_tab_groups() when edit=1:

python
def get_template_names(self):
    if self.request.GET.get("edit") == "1":
        if self.get_tab_groups():
            return ["core/partials/detail_form_tabs_partial.html"]
        return [self.main_object_form_template_partial]
    return [self.template_name]
  • Tabs exist + edit=1 → detail_form_tabs_partial.html (tab structure preserved)
  • No tabs + edit=1 → main_object_form.html (flat form, existing behavior)
  • No edit=1 → self.template_name (full detail page, unchanged)

Backward Compatibility

  • Views without get_tab_groups() (or returning []) continue using main_object_form.html — no behavioral change.
  • Views WITH tab groups now get a tab-preserving response — a fix, not a breaking change.

Files changed: 2 (1 new template, 1 view).


2. HTMX Processing After Form Save — Inline List Pages Not Loading

After form save, inline items rendered via inline_items_section.html were not loading their content. The #form-container innerHTML replacement inserted new hx-get / hx-trigger="load" elements, but HTMX's MutationObserver did not process them because innerHTML assignment does not guarantee mutation observer firing in all browsers. An explicit htmx.process() call was missing.

Fix

Template: core/partials/detail_partial.html:104-106

Added htmx.process(targetElement) immediately after innerHTML replacement, before initTomSelects():

javascript
targetElement.innerHTML = html;
if (typeof htmx !== 'undefined') {
    htmx.process(targetElement);
}
if (typeof initTomSelects === 'function') {
    initTomSelects(targetElement);
}

Pattern Consistency

This matches the existing pattern used in: - journal/partials/journal_batch_detail.html:164 — if (typeof htmx !== 'undefined') htmx.process(target); - core/partials/inline_update_partial.html:202 — htmx.process(target); - core/partials/inline_details_partial.html:168 — if (typeof htmx !== 'undefined') htmx.process(target);

Processing Order

htmx.process() scans the new DOM for hx-* attributes and triggers hx-trigger="load" elements — this is what loads inline list pages via HTMX lazy loading. Running before initTomSelects() ensures HTMX does not interfere with the custom widget DOM modifications.

Files changed: 1 template.


3. TomSelect (M2M Widget) Initialization in Tabbed Form Template

The new detail_form_tabs_partial.html was missing the inline <script> tag that initializes custom TomSelect (MultiSelectWidget) instances on M2M form fields. All other form partials include this script for consistency.

Fix

Template: core/partials/detail_form_tabs_partial.html:33

Added the standard TomSelect initialization script at the end of the template:

html
<script>if(typeof initTomSelects==='function'){initTomSelects();}</script>

This matches the pattern in main_object_form.html:11, inline_update_partial.html:215, inline_details_partial.html:175, and all other form partials.

Dual Initialization Paths

TomSelect widgets are initialized via two paths depending on context:

Context Mechanism
AJAX form save (innerHTML injection) initTomSelects(targetElement) called explicitly in detail_partial.html JS after injection
Full page load (server-rendered) Inline <script> tag at end of template

The inline script ensures M2M widgets are initialized when the tabbed edit form is loaded as a full page (e.g., initial navigation with ?edit=1).

Files changed: 1 template.


4. Version Bump

  • setup.py: Version bumped from 0.2.13 → 0.2.14.

Summary

Area Description Files
Tab-Preserving Save New detail_form_tabs_partial.html template; get_template_names() resolves to tabbed template when tab groups exist and edit=1 2
HTMX Processing Fix Added htmx.process(targetElement) after innerHTML replacement for inline list page loading 1
TomSelect Init Added initTomSelects() script to tabbed form template; M2M widgets now initialize in tabbed edit views 1
Versioning Version bumped to 0.2.14 1

Total: 1 new template, 3 modified files, 4 files changed.


Note: All changes are uncommitted and untested. The user is solely responsible for testing and validating all outputs before committing or deploying.