feat(view): mint managed lanes to separate cross-mode content per stance (D2 W3-A)
Pure planLaneMinting decides which tracks hold >1 mode's content and which managed lane each item lands on; view.cpp applies it (I_FREEMODE/I_NUMFIXEDLANES/P_LANENAME/ I_FIXEDLANE) under one undo block, driven off the auto-tag detection tick. Manual lanes and their items are never touched. Load-time reconcile rebuilds ownership from durable lane names before reapplying active-mode visibility.
This commit is contained in:
@@ -9,6 +9,8 @@
|
||||
#include <set>
|
||||
#include <utility>
|
||||
|
||||
#include "lane_keys.h" // laneNameForMode — the ONE durable managed-lane-key convention
|
||||
|
||||
// view_mode_model implementation.
|
||||
//
|
||||
// JSON is hand-rolled and self-contained, mirroring bank_model's approach (brief:
|
||||
@@ -156,6 +158,56 @@ std::vector<AutoTag> autoTagNewContent(const std::vector<std::string>& newTrackG
|
||||
return tags;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// lane minting decision
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
LaneMintPlan planLaneMinting(const std::vector<LaneTrack>& tracks) {
|
||||
LaneMintPlan plan;
|
||||
|
||||
for (const LaneTrack& track : tracks) {
|
||||
if (track.trackGuid.empty()) continue;
|
||||
|
||||
// Collect the DISTINCT modes the track's managed-eligible items belong to, in
|
||||
// deterministic (sorted) order so the mint list and lane count are stable across
|
||||
// runs (a set orders by mode id). Items on a manual lane are EXEMPT — never
|
||||
// counted toward the multi-mode test and never reassigned (the managed-only
|
||||
// invariant, upheld at the source of the decision).
|
||||
std::set<std::string> involvedModes;
|
||||
for (const LaneItem& item : track.items) {
|
||||
if (item.guid.empty() || item.modeId.empty()) continue;
|
||||
if (item.onManualLane) continue; // exempt — user's hand-managed lane
|
||||
involvedModes.insert(item.modeId);
|
||||
}
|
||||
|
||||
// Single-mode (or empty) track: whole-track parking (D1) still separates the
|
||||
// stances. NO split, NO mint, NO assignment — this is the load-bearing
|
||||
// "don't lane-split single-mode tracks" rule.
|
||||
if (involvedModes.size() < 2) continue;
|
||||
|
||||
// Multi-mode track: transition to lane-split. One managed lane per involved
|
||||
// mode (durable key = laneNameForMode(mode)), owned by that mode.
|
||||
plan.splits.push_back(LaneMintPlan::TrackSplit{
|
||||
track.trackGuid, static_cast<int>(involvedModes.size())});
|
||||
for (const std::string& mode : involvedModes) {
|
||||
plan.mints.push_back(
|
||||
LaneMint{track.trackGuid, laneNameForMode(mode), mode});
|
||||
}
|
||||
|
||||
// Assign EVERY managed-eligible item onto its mode's lane — including the
|
||||
// pre-existing single-mode items, so a track that just gained a second mode
|
||||
// retroactively lanes all of its content, not only the newly-added item.
|
||||
for (const LaneItem& item : track.items) {
|
||||
if (item.guid.empty() || item.modeId.empty()) continue;
|
||||
if (item.onManualLane) continue; // exempt — never reassigned
|
||||
plan.assigns.push_back(LaneAssign{
|
||||
item.guid, track.trackGuid, laneNameForMode(item.modeId)});
|
||||
}
|
||||
}
|
||||
|
||||
return plan;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// planner helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user