mirror of
https://github.com/got-feedBack/feedBack.git
synced 2026-07-22 12:52:29 +00:00
Sharing earned Feats on the (forthcoming) public wall is strictly opt-in,
default OFF, with a binding data-minimization contract.
- Onboarding (static/v3/profile.js): a new opt-in step (now a 5-step wizard)
after song-directory / before paths — publishes only display name + earned
Feats, never songs/skills/scores; off by default.
- Settings (plugins/achievements/settings.html, System tab via
settings.category): the same toggle + a "Remove me from the wall" button
(POST remove-me — wipes local synced state offline + enqueues removal).
- Core (server.py): achievements_enabled (bool, default false) in
_default_settings + /api/settings validation + _RESETTABLE_SETTINGS_KEYS;
mirrored to localStorage in app.js loadSettings().
- Data-minimization gate: engine.build_wall_payload is the single explicit-dict
serializer; key-set is EXACTLY {display_name, player_hash, achievement_id,
unlocked_at}, achievement_id always a Feat id. Enqueue is gated on
opted-in AND profile identity (reused player_hash); competency never
enqueues (integration law).
Verified natively: settings round-trip + validation + remove-me; opted-in
activity enqueues exactly one 4-field Feat payload; Playwright confirms the
5-step wizard + opt-in card (default unchecked), zero console errors.
29 plugin tests + new settings tests pass.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
69 lines
3.3 KiB
HTML
69 lines
3.3 KiB
HTML
<!-- Achievements plugin — Privacy panel (mounts under the Settings "System" tab
|
|
via settings.category). Owns the wall opt-in toggle (bound to the core
|
|
`achievements_enabled` setting) + the self-serve "Remove me from the wall"
|
|
action. Default OFF — nothing publishes until the user opts in. -->
|
|
<div class="text-sm text-gray-300 space-y-4" data-ach-privacy>
|
|
<div>
|
|
<p>Your <strong>Achievements</strong> and <strong>Feats of Power</strong> live on your
|
|
<em>Profile</em> page and are local & private by default.</p>
|
|
</div>
|
|
|
|
<label class="flex items-start gap-3 cursor-pointer">
|
|
<input type="checkbox" id="setting-achievements-enabled"
|
|
class="mt-1 h-4 w-4 rounded border-gray-600 bg-gray-800 text-sky-500 focus:ring-sky-500">
|
|
<span>
|
|
<span class="font-medium text-gray-200">Share my Feats of Power on the public wall</span>
|
|
<span class="block text-gray-400">Publishes only your display name and the rare
|
|
<strong>Feats</strong> you earn (activity milestones) — never songs, skills, or scores.
|
|
You can turn this off and remove yourself at any time.</span>
|
|
</span>
|
|
</label>
|
|
|
|
<div>
|
|
<button type="button" id="ach-remove-me"
|
|
class="px-3 py-1.5 rounded-md text-sm border border-red-500/40 text-red-300 hover:bg-red-500/10 transition">
|
|
Remove me from the wall
|
|
</button>
|
|
<span id="ach-remove-status" class="ml-2 text-xs text-gray-400"></span>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
(function () {
|
|
var root = document.currentScript && document.currentScript.previousElementSibling;
|
|
// The panel re-injects on each Settings entry; bind once per element.
|
|
var toggle = document.getElementById('setting-achievements-enabled');
|
|
if (!toggle || toggle.dataset.wired === '1') return;
|
|
toggle.dataset.wired = '1';
|
|
|
|
// Hydrate from the authoritative server setting.
|
|
fetch('/api/settings').then(function (r) { return r.ok ? r.json() : {}; }).then(function (d) {
|
|
toggle.checked = d && d.achievements_enabled === true;
|
|
}).catch(function () { /* offline — leave unchecked */ });
|
|
|
|
toggle.addEventListener('change', function () {
|
|
var on = !!toggle.checked;
|
|
try { localStorage.setItem('achievementsEnabled', on ? '1' : '0'); } catch (_) {}
|
|
fetch('/api/settings', {
|
|
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ achievements_enabled: on }),
|
|
}).catch(function () { /* best-effort; revert on failure */ });
|
|
});
|
|
|
|
var removeBtn = document.getElementById('ach-remove-me');
|
|
var status = document.getElementById('ach-remove-status');
|
|
if (removeBtn) {
|
|
removeBtn.addEventListener('click', function () {
|
|
removeBtn.disabled = true;
|
|
if (status) status.textContent = 'Removing…';
|
|
fetch('/api/plugins/achievements/remove-me', { method: 'POST' })
|
|
.then(function (r) {
|
|
if (status) status.textContent = r.ok ? 'Removed. Your Feats stay on your Profile.' : 'Could not reach the server — try again.';
|
|
})
|
|
.catch(function () { if (status) status.textContent = 'Offline — queued; it will sync when you reconnect.'; })
|
|
.finally(function () { removeBtn.disabled = false; });
|
|
});
|
|
}
|
|
})();
|
|
</script>
|