Skip to content

Vote Rewards: dedupe provider list on load + idx-aware Vue key#52

Closed
AdaInTheLab wants to merge 1 commit into
mainfrom
fix/dedupe-vote-providers
Closed

Vote Rewards: dedupe provider list on load + idx-aware Vue key#52
AdaInTheLab wants to merge 1 commit into
mainfrom
fix/dedupe-vote-providers

Conversation

@AdaInTheLab
Copy link
Copy Markdown
Collaborator

Summary

PR #50 prevented new duplicate provider entries from forming (the Newtonsoft-append bug). But admins whose settings JSON got poisoned while that bug was active still carry the doubled list in their persisted state — the deserializer faithfully restores both entries every boot.

Visible symptoms (Ada's test box right now):

  • `/vote` prints `": provider disabled"` followed by `": granted ..."` — the foreach iterates both entries
  • Settings tab renders two provider blocks for the same key; Vue's v-for warns about colliding `:key` values

Fix

1. `EnsureDefaultProviders` dedups by Key on every load. The entry with the strongest "configured" signal wins:

```
enabled + has-api-key > enabled-only > has-api-key > anything
```

A real config never gets displaced by an empty stub. Logs a single line when it dropped any rows so admins can see it ran.

2. Vue `:key` is now `${provider.key}-${idx}`. Defense in depth — the backend dedup means duplicates shouldn't reach the UI anymore, but the v-for shouldn't be the thing that breaks if they do.

Self-heals

No manual cleanup needed. Restart the server, watch for `VoteRewards: deduped 1 duplicate provider entry/entries on load.` in the log, refresh the panel — single provider block with your real config preserved.

Files

File What
`Features/VoteRewardsFeature.cs` `EnsureDefaultProviders` now dedups by Key with a configured-priority tiebreaker
`frontend/src/views/SettingsView.vue` v-for `:key` includes idx

Test plan

  • Pull, build, deploy to dev
  • Restart 7D2D service
  • Server log shows `VoteRewards: deduped 1 duplicate provider entry/entries on load.`
  • Server log shows `providers configured=1, enabled=1` (was `=2, enabled=1`)
  • Hard-refresh panel — Vote Rewards tab shows ONE provider block with the real API key
  • `/vote` chat command prints exactly one provider line, not two

🤖 Generated with Claude Code

PR #50 prevented new duplicates forming (the Newtonsoft-append bug),
but admins whose state was poisoned while that bug was active still
carry the doubled list in their persisted JSON. Symptoms after loading:

- /vote prints "<key>: provider disabled" + "<displayname>: granted ..."
  side by side because the foreach iterates both entries
- Settings tab shows two provider blocks; Vue's v-for warns about
  duplicate :key values and the diffing reconciles unpredictably

Two-part fix:

1. EnsureDefaultProviders now deduplicates by Key on every load. The
   entry with the strongest "configured" signal wins (enabled+apiKey >
   enabled > apiKey > anything else), so a real config never gets
   displaced by an empty stub. Logs a one-line note when it dropped
   any rows so admins can see it ran.

2. SettingsView.vue's v-for key is now `${provider.key}-${idx}` so
   duplicate provider keys can't collide in Vue's diffing. Defense in
   depth — the backend dedup means duplicates shouldn't reach the UI
   anymore, but the v-for shouldn't be the thing that breaks if they do.

Self-heals existing poisoned state on the next server boot.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@AdaInTheLab
Copy link
Copy Markdown
Collaborator Author

Superseded by #53 (which carries the same dedup commit + adds 2-col layout). Closing in favor of the unified PR.

AdaInTheLab added a commit that referenced this pull request Apr 30, 2026
* Vote Rewards: dedupe provider list on load, idx-aware Vue key

PR #50 prevented new duplicates forming (the Newtonsoft-append bug),
but admins whose state was poisoned while that bug was active still
carry the doubled list in their persisted JSON. Symptoms after loading:

- /vote prints "<key>: provider disabled" + "<displayname>: granted ..."
  side by side because the foreach iterates both entries
- Settings tab shows two provider blocks; Vue's v-for warns about
  duplicate :key values and the diffing reconciles unpredictably

Two-part fix:

1. EnsureDefaultProviders now deduplicates by Key on every load. The
   entry with the strongest "configured" signal wins (enabled+apiKey >
   enabled > apiKey > anything else), so a real config never gets
   displaced by an empty stub. Logs a one-line note when it dropped
   any rows so admins can see it ran.

2. SettingsView.vue's v-for key is now `${provider.key}-${idx}` so
   duplicate provider keys can't collide in Vue's diffing. Defense in
   depth — the backend dedup means duplicates shouldn't reach the UI
   anymore, but the v-for shouldn't be the thing that breaks if they do.

Self-heals existing poisoned state on the next server boot.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Vote Rewards: 2-col tab layout (config left, audit right)

The Vote Rewards tab was rendering everything in a single column inside
the left half of the available width — large empty space sat to the
right. Mirrors the Discord tab pattern: a 2-col grid at the tab level,
with the configuration cards (master toggle, providers, save) on the
left and the audit feed on the right.

Grid is 3fr / 2fr because the provider config card is wider than the
audit table needs. Collapses to single column at <=1100px (earlier than
the 768px tablet breakpoint) since the 3:2 split gets cramped on
narrow-desktop screens before mobile.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant