feat(vt): round 3 item 10 — Delete action for Meta/Route/Loot profiles

Round 2 step 5 gave the settings Profiles tab a Delete action; Meta, Route,
and Loot never got the same verb. Added MossTankMetaProfileStore.Delete,
MossTankRouteProfileStore.Delete, and MossTankLootProfileStore.Delete
(same contract as Settings: remove the selected named profile's real file,
fall back to By char; refuse for By char itself, which has nothing to
delete — see each store's ClearCurrent for that case), wired through
MossTankPanel.DeleteMetaProfile/DeleteRouteProfile/DeleteLootProfile to
three new "Delete" buttons in mosstank.xml (Route tab row, the Meta tab's
button row, and the Loot rule editor's button row).

MossTankMarkupContractTests' interactive-control count moves 191 -> 194
for the three new buttons.

Mutation: reverted all four .cs files and mosstank.xml to HEAD (keeping
only the new/changed tests) — the test project failed to even COMPILE
(DeleteRouteProfile/DeleteMetaProfile/DeleteLootProfile do not exist on
MossTankPanel), confirming the six new behavioral tests
(DeleteRouteProfile/DeleteMetaProfile/DeleteLootProfile, each with a
successful-delete and a refuse-by-char case) and the markup-count update
all depend on this commit's code.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-09-07 01:57:29 +02:00
parent 43482c1380
commit fbb584436b
7 changed files with 213 additions and 3 deletions

View file

@ -238,6 +238,29 @@ internal sealed class MossTankLootProfileStore
WriteUtl(fileName, profile);
}
/// <summary>
/// Round 3 item 10: deletes the currently selected named loot profile's
/// real <c>.utl</c> file, then falls back to <see cref="ByCharacter"/>.
/// Refuses for <see cref="ByCharacter"/> itself — there is no file to
/// delete, only a reset (see <see cref="ClearCurrent"/>), matching
/// <see cref="MossTankProfileStore.Delete"/>'s own contract.
/// </summary>
public bool Delete(out string notice)
{
if (_selected.Equals(ByCharacter, StringComparison.OrdinalIgnoreCase))
{
notice = "'By char' is the built-in loot profile and cannot be deleted.";
return false;
}
string fileName = _selected;
if (VtankStorage.IsAvailable)
VtankStorage.Delete(fileName);
_selected = ByCharacter;
WriteBinding();
notice = $"Deleted loot profile {StripUtl(fileName)}.";
return true;
}
public void ClearCurrent(List<LootRule> target, LootSettings? settings = null)
{
target.Clear();

View file

@ -261,6 +261,30 @@ internal sealed class MossTankMetaProfileStore
return true;
}
/// <summary>
/// Round 3 item 10: deletes the currently selected named Meta profile's
/// real <c>.af</c> file, then falls back to <see cref="ByCharacter"/>.
/// Refuses for <see cref="ByCharacter"/> itself — there is no file to
/// delete, only a reset (see <see cref="ClearCurrent"/>), matching
/// <see cref="MossTankProfileStore.Delete"/>'s own contract.
/// </summary>
public bool Delete(out string notice)
{
if (_selected.Equals(ByCharacter, StringComparison.OrdinalIgnoreCase))
{
notice = "'By char' is the built-in Meta profile and cannot be deleted.";
return false;
}
string fileName = _selected;
if (VtankStorage.IsAvailable)
VtankStorage.Delete(fileName);
_selected = ByCharacter;
_pendingLegacyBareName = null;
WriteBinding();
notice = $"Deleted Meta profile {fileName}.";
return true;
}
public MetaProfile ClearCurrent()
{
var empty = new MetaProfile();

View file

@ -594,6 +594,7 @@ internal sealed partial class MossTankPanel
public Action CopyLootProfile => () =>
CreateLootProfileCore(copyCurrent: true);
public Action ClearLootProfile => ClearLootProfileCore;
public Action DeleteLootProfile => DeleteLootProfileCore;
public Action CloseLootEditor => () => _lootEditorVisible = false;
public Action<int> SelectLootRule => SelectLootRuleCore;
public Action<string> SetLootExpressionDraft => value =>
@ -756,6 +757,7 @@ internal sealed partial class MossTankPanel
public Action CopyRouteProfile => () =>
CreateRouteProfileCore(copyCurrent: true);
public Action ClearRouteProfile => ClearRouteProfileCore;
public Action DeleteRouteProfile => DeleteRouteProfileCore;
public Action SetFollowTarget => CaptureFollowTarget;
// ── Meta profile / editor ────────────────────────────────────────────
@ -826,6 +828,7 @@ internal sealed partial class MossTankPanel
public Action CreateMetaProfile => () => CreateMetaProfileCore(copyCurrent: false);
public Action CopyMetaProfile => () => CreateMetaProfileCore(copyCurrent: true);
public Action ClearMetaProfile => ClearMetaProfileCore;
public Action DeleteMetaProfile => DeleteMetaProfileCore;
// ── Profiles tab ─────────────────────────────────────────────────────
public IReadOnlyList<string> MacroProfileNames => _profiles.AvailableNames;
@ -1425,6 +1428,17 @@ internal sealed partial class MossTankPanel
_lootEditorNotice = $"Cleared {_lootProfiles.Selected}.";
}
private void DeleteLootProfileCore()
{
if (!_lootProfiles.Delete(out string notice))
{
_lootEditorNotice = notice;
return;
}
LoadLootProfile();
_lootEditorNotice = notice;
}
private void LoadLootProfile()
{
if (!_lootProfiles.LoadCurrent(
@ -1784,6 +1798,17 @@ internal sealed partial class MossTankPanel
_routeNotice = $"Cleared {_routeProfiles.Selected}.";
}
private void DeleteRouteProfileCore()
{
if (!_routeProfiles.Delete(out string notice))
{
_routeNotice = notice;
return;
}
LoadRouteProfile();
_routeNotice = notice;
}
private void LoadRouteProfile()
{
if (!_routeProfiles.LoadCurrent(_navigationSettings, _host.Automation.Spells))
@ -2380,6 +2405,17 @@ internal sealed partial class MossTankPanel
_metaNotice = $"Cleared Meta profile {_metaProfiles.Selected}.";
}
private void DeleteMetaProfileCore()
{
if (!_metaProfiles.Delete(out string notice))
{
_metaNotice = notice;
return;
}
LoadMetaProfile();
_metaNotice = notice;
}
private void LoadMetaProfile()
{
_metaProfile = _metaProfiles.LoadCurrent();

View file

@ -228,6 +228,30 @@ internal sealed class MossTankRouteProfileStore
return true;
}
/// <summary>
/// Round 3 item 10: deletes the currently selected named route's real
/// <c>.af</c> file, then falls back to <see cref="ByCharacter"/>.
/// Refuses for <see cref="ByCharacter"/> itself — there is no file to
/// delete, only a reset (see <see cref="ClearCurrent"/>), matching
/// <see cref="MossTankProfileStore.Delete"/>'s own contract.
/// </summary>
public bool Delete(out string notice)
{
if (_selected.Equals(ByCharacter, StringComparison.OrdinalIgnoreCase))
{
notice = "'By char' is the built-in route profile and cannot be deleted.";
return false;
}
string fileName = _selected;
if (VtankStorage.IsAvailable)
VtankStorage.Delete(fileName);
_selected = ByCharacter;
_pendingLegacyBareName = null;
WriteBinding();
notice = $"Deleted route profile {Strip(fileName)}.";
return true;
}
/// <summary>
/// Resets only the fields this store owns (Mode/Waypoints/FollowTarget)
/// — Enabled/Priority/MinimumDistanceMeters/FollowAroundCorners/

View file

@ -158,6 +158,9 @@
onclick="{CreateRouteProfile}" />
<button x="560" y="64" w="84" h="22" text="Clear route"
onclick="{ClearRouteProfile}" />
<button x="650" y="64" w="70" h="22" text="Delete"
onclick="{DeleteRouteProfile}"
tooltip="Delete the selected named route's real .af file. Not available for By char." />
<toggle x="4" y="92" w="150" h="20" text="Enable Looting"
checked="{LootEnabled}" onclick="{ToggleLooting}" />
<menu x="156" y="90" w="116" h="22" items="{LootProfileNames}"
@ -457,9 +460,12 @@
onclick="{CopyMetaProfile}" />
<button x="378" y="0" w="48" h="21" text="Clear"
onclick="{ClearMetaProfile}" />
<toggle x="446" y="2" w="112" h="20" text="Enable Meta"
<button x="428" y="0" w="66" h="21" text="Delete"
onclick="{DeleteMetaProfile}"
tooltip="Delete the selected named Meta profile's real .af file. Not available for By char." />
<toggle x="500" y="2" w="112" h="20" text="Enable Meta"
checked="{MetaEnabled}" onclick="{ToggleMeta}" />
<label x="566" y="4" text="{MetaStateText}" color="#FFE8DEC3" />
<label x="620" y="4" text="{MetaStateText}" color="#FFE8DEC3" />
<list x="4" y="26" w="776" h="68" rowheight="17"
items="{MetaRows}" selected="{SelectedMetaRuleIndex}"
@ -533,6 +539,9 @@
onclick="{CopyLootProfile}" />
<button x="494" y="0" w="58" h="21" text="Clear"
onclick="{ClearLootProfile}" />
<button x="560" y="0" w="64" h="21" text="Delete"
onclick="{DeleteLootProfile}"
tooltip="Delete the selected named loot profile's real .utl file. Not available for By char." />
<button x="684" y="0" w="92" h="21" text="Back"
onclick="{CloseLootEditor}" />
<list x="4" y="24" w="520" h="82" rowheight="17"