fix(m9/T30): empty-state guard keys off resolved fields (top-level \$ref); shift list raw-text on item removal

This commit is contained in:
Joseph Doherty
2026-06-18 13:06:59 -04:00
parent 6bc2bb5430
commit 95b8caf284
2 changed files with 134 additions and 7 deletions
@@ -18,7 +18,7 @@
values.
*@
@if (_shapes.Count == 0)
@if (_topLevelFields.Count == 0)
{
<div class="text-muted small fst-italic">No parameters declared.</div>
}
@@ -399,18 +399,47 @@ else
await Emit();
}
// After a removal, item paths above the removed index shift down by one; clear
// their stale raw-text/error entries so the re-rendered rows read fresh.
// After a removal at removedIndex, paths for items above that index shift down
// by one. Mirror the value-list RemoveAt by: (1) for each index > removedIndex
// found in a dict, re-key it as index-1; (2) clear the now-vacated top slot
// (the original highest index). Items below the removed index are unaffected.
private void ShiftListState(string path, int removedIndex)
{
ShiftDict(_rawText, path, removedIndex);
ShiftDict(_parseErrors, path, removedIndex);
}
private static void ShiftDict(Dictionary<string, string> dict, string path, int removedIndex)
{
var prefix = $"{path}[";
foreach (var key in _rawText.Keys.Where(k => k.StartsWith(prefix, StringComparison.Ordinal)).ToList())
// Collect keys under this list prefix, parse the item index, and build the
// shift map in one pass so in-place mutation does not interfere with iteration.
var toShift = new List<(string Key, int ItemIndex, string Suffix)>();
foreach (var key in dict.Keys)
{
_rawText.Remove(key);
if (!key.StartsWith(prefix, StringComparison.Ordinal)) continue;
// Extract the integer after the '['.
var rest = key.AsSpan(prefix.Length); // e.g. "2]" or "2].field"
var closePos = rest.IndexOf(']');
if (closePos < 0) continue;
if (!int.TryParse(rest[..closePos], System.Globalization.NumberStyles.Integer,
System.Globalization.CultureInfo.InvariantCulture, out var idx)) continue;
// Only touch items ABOVE the removed index; items at lower indices are stable.
if (idx <= removedIndex) continue;
var suffix = rest[(closePos + 1)..].ToString(); // everything after ']'
toShift.Add((key, idx, suffix));
}
foreach (var key in _parseErrors.Keys.Where(k => k.StartsWith(prefix, StringComparison.Ordinal)).ToList())
// Apply: remove old key, write shifted key. Higher indices first so we never
// overwrite a key that is itself still pending a shift (would happen if we
// iterated lowest-to-highest and the same path base appeared at consecutive
// indices). Processing highest → lowest avoids the collision.
foreach (var (key, idx, suffix) in toShift.OrderByDescending(t => t.ItemIndex))
{
_parseErrors.Remove(key);
var value = dict[key];
dict.Remove(key);
var shiftedKey = $"{path}[{idx - 1}]{suffix}";
dict[shiftedKey] = value;
}
}