DeckListModel now surfaces the custom zones from the deck tree so
views can render and edit them alongside criteria groups.
The custom-zone bookkeeping that made the model unwieldy is extracted
into DeckListModelCustomZones (deck_list_model_custom_zones.h/.cpp), a
single self-contained unit owning every "what is / where is a custom
zone" decision for the model's shadow tree:
- rebuildTree mirrors each custom zone as a DecklistModelSubZoneNode
under its board zone, cards flat inside (no further grouping).
- The freshly built shadow tree is sorted while the model reset is
still open, so views never observe unsorted intermediate order and
proxies cannot desync.
- Custom zones always sort after criteria groups within a board,
regardless of their names. One shared sortWithCustomZonesLast backs
both the live sortHelper (which remaps persistent indexes from the
movement mapping) and the silent reset-time sortShadowTree.
- addCard inserts flat into a custom zone by name and keeps grouping
by active criteria for board zones. findCardNode resolves cards in
both layouts, legacy top-level zones unchanged.
- New IsCustomZoneRole lets views tell zones apart from groups.
- Empty custom zones survive row removal. Zone rows themselves are
only mutable through the deck tree API.
A new deck_list_model_custom_zones_test suite locks the extracted
shadow-tree logic (type testing, mirroring, name lookup, and the
sort-with-custom-zones-last mapping).
No behavior change.