feat(frontend): Make UI sound effects optional (mute toggle + persist) #487
Labels
No labels
agent:fix_bugfix
agent:fix_conflicts
agent:fix_security
agent:gap_analysis
agent:implement
agent:implement
agent:implement
agent:open_issues
agent:ready
agent:research
agent:security_audit
agent:verify
architecture
backend
blocker:hybrid
blocker:launch
blocker:limit-orders
blocker:v2
block:log_only
block:security
bug
ci
contracts
correctness
deploy
dev
devops
docs
documentation
duplicate
e2e
enhancement
epic
feature
frontend
functional-completion
gas
good first issue
governance
help wanted
high-risk
hooks
hybrid
indexer
infra
infrastructure
integrators
invalid
launch-blocker
limit-orders
localnet
localterra
low priority
missing-implementation
needs-design
ops
performance
priority
high
priority
medium
product
qa
QA
question
ready
ready
research
scripts
security
security-hardening
smartcontracts
tech-debt
testing
ux
UX
v2
verification
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
code/cl8y-dex-terraclassic#487
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
User feedback: click / UI sounds provide useful action registration for some users, but others find them annoying. Sounds are currently always on with no mute preference. Add an opt-out (default ON) so users can disable all UI SFX without removing the feature for everyone else.
Current codebase
UI audio is centralized in
frontend-dapp/src/lib/sounds.ts:frontend-dapp/public/sounds/(button-press,hover,success,error)HTMLAudioElements;play()resetscurrentTime, sets volume (buttonPress0.2, others 0.4), and swallows play errorssounds.playButtonPress(),playHover(),playSuccess(),playError()Playback is opt-in per call site (no global click listener / shared Button wrapper). Roughly:
playButtonPressplayHoverWalletModal.tsx)playSuccess/playErroronSuccess/onErrorcallbacksThere is no mute flag, localStorage key, env gate, React context, or Settings page for sounds. Closest global UX preference pattern is theme (
cl8y-dex-theme) inLayout.tsxviaThemeSegmentedControl. Boolean prefs elsewhere use'1'/'0'helpers (e.g.utils/swapSettingsAdvanced.ts).QA docs (
QA_TEMPLATE.md§10.3) expect sounds on; recent passes sometimes SKIP manual sound checks. Component tests mock@/lib/sounds; there is no unit coverage ofsounds.tsitself and no E2E for audio.Why a new implementation is needed
play()avoids touching ~33 production import sites and keeps mute consistent for press, hover, success, and error.Constraints / guardrails
sounds.tsplay()(or equivalent), not duplicated at each call site.cl8y-dex-*key and the existing boolean'1'/'0'helper pattern; SSR/window-guard safe.sounds.play*()call sites; silent buttons by omission stay silent.Layout(desktop header + mobile More sheet); must be keyboard-accessible with clearariastate.prefers-reduced-motion: reduceas muted unless the user explicitly re-enables sounds — if implemented, document precedence in the util.Relevant files
Implement / extend
frontend-dapp/src/lib/sounds.tsfrontend-dapp/src/utils/soundPreferences.tsSOUNDS_ENABLED_STORAGE_KEY,readSoundsEnabled,writeSoundsEnabledfrontend-dapp/src/components/common/Layout.tsxfrontend-dapp/src/components/common/ThemeSegmentedControl.tsxQA_TEMPLATE.md§10.3Patterns to mirror
frontend-dapp/src/utils/swapSettingsAdvanced.ts'1'/'0'frontend-dapp/src/utils/expertMode.ts/stores/dex.tsAssets (unchanged unless replacing clips)
frontend-dapp/public/sounds/button-press.wavfrontend-dapp/public/sounds/hover.wavfrontend-dapp/public/sounds/success.wavfrontend-dapp/public/sounds/error.wavRegression surface (call sites import
@/lib/sounds)Shell/common:
AppShellNavLink.tsx,Layout.tsx,Modal.tsx,CopyButton.tsx,AmountBalanceActions.tsx,TradeOnboardingStrip.tsxWallet:
WalletButton.tsx,WalletModal.tsx,WalletDropdownMenuItems.tsxTrade/swap:
TradeOrderTicket.tsx,TradeMarketOrderPanel.tsx,TradeWorkspaceDisclosure.tsx,SwapAdvancedSettings.tsx,ExpertModeModal.tsx,TraderPositionsTable.tsxCharts/portfolio/legal + pages: chart overlay/menu, portfolio sections,
RiskAcknowledgementModal.tsx,SwapPage,PoolPage,TradePage,LimitOrdersPage,ChartsPage,TraderPage,PortfolioPage,TiersPage,MintPage,CreatePairPageHooks: limit-order cancel / update price / expired claim mutations
Recommended direction
soundPreferences.tswith keycl8y-dex-sounds-enabled, defaulttruewhen missing/invalid.play()when!readSoundsEnabled().Layoutbeside theme (desktop + mobile More).play()no-op when muted (mockHTMLAudioElementor spyAudio.prototype.play).QA_TEMPLATE.md§10.3 with mute/unmute paths.Acceptance criteria
sounds.play*()path (nav, modals, wallet hover, mutation success/error).sounds.ts.QA_TEMPLATE.mdincludes mute and unmute verification steps.@/lib/soundsstill pass.Test plan (all paths)
Unit
readSoundsEnabled() === true.'0'→ false;'1'→ true; garbage → treat as default true (or documented fallback).writeSoundsEnabled(false/true)round-trips via localStorage.playButtonPressattemptsaudio.play(spy).playButtonPress/playHover/playSuccess/playErrordo not callaudio.play.play*reflects new value.Manual / QA (browser)
localStorage: click nav, theme, modal close → press sounds play; open wallet modal and hover options → hover sound; complete a dry success path if available or use Simulated Wallet swap → success sound; force a failed tx if feasible → error sound.currentTime=0behavior without throwing.Automated regression
make test-frontend(or scoped vitest for new util + sounds tests).Test plan — attack, hack, and abuse vectors
Audio prefs are client-only, but still verify:
cl8y-dex-sounds-enabledAudioinstance creation beyond the existing four singletons.sounds.play*while mutedaudio.play()/sounds/*.wavvia compromised static hostpublic/sounds/.storageevent sync is nice-to-have, not required.Verification criteria
Done when:
make test-frontend.Out of scope
<button>Manual UI verification (#487)
Verified locally with
make dev(Vite @ 127.0.0.1:5173). Screenshots of the altered shell controls:487-desktop-light-sound-on.png487-desktop-sound-muted.png487-desktop-sound-on.png487-header-pref-group.png487-mobile-more-sound-off.png487-mobile-more-sound-on.pngChecks observed:
localStorage cl8y-dex-sounds-enabled=0.soundPreferences,soundsmute gate,SoundEffectsToggle— all green.mentioned in commit
6097243b51mentioned in merge request !1027
Implementation status (MR !1027)
Branch
feat/487-sound-mute-toggle— https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/merge_requests/309Acceptance criteria
cl8y-dex-sounds-enabled)sounds.play*()path (central gate inplay())aria-pressed= sounds enabledQA_TEMPLATE.md§10.3 mute/unmute cases@/lib/soundstests unaffected (no call-site changes)Not done / out of scope (per issue)
prefers-reduced-motion: reduceauto-mute stretchstorageevent sync (documented as MVP acceptable)Screenshots of altered shell controls are in the prior note on this issue.
mentioned in commit
8491a798a9Icon chrome update (MR !1027)
Replaced Dark / Light / Sound text buttons with compact moon / sun / speaker flat icons.
487-icons-header-light.png487-icons-header-dark.png487-icons-header-muted.png487-icons-mobile-more.pngmentioned in commit
481355d500Footer ribbon + denser header (MR !1027)
Moved
EnvironmentRibboninto the footer on all breakpoints and tightened sticky header vertical spacing.487-footer-ribbon-desktop.png487-footer-ribbon-mobile.pngmentioned in commit
d226b04bee