# Sound setting

The person's control for sound: "Sounds: Off / When I'm away / Always", the events as checkboxes with when each is raised and the visible cue it comes with (a sound is never the only signal), and a Play button for each that plays the brand's file or says no sound is set. It stores nothing: the choice goes back through `onChange`, and the app keeps it and passes it on as `useAttention`'s `sound.enabled`, `sound.when` and `sound.events`. Nothing is picked until the person picks it: without a `value` it shows Off and no events. Turning sounds on selects nothing for the person: the control says "Choose the events to hear" until an event is ticked, and the first tick is a click, which is when the browser lets audio play, so that event plays once as a preview. A Play button whose file can't be loaded says so beside the event. The sound files are the brand's `--cd-sound-<event>` tokens; the `sounds` item installs a starter set.

<SoundSetting value={settings.sound} onChange={(sound) => setSettings({ ...settings, sound })} />

- Registry name: `sound-setting` (`@candor/sound-setting`)
- Tier: Pro. Installing it needs a Pro, Premium or Team licence key: the person runs `npx candor-ui key <key>` themselves; never ask for the key in chat or write it to a file.
- Docs: https://candor.themethodic.com/docs/sound-setting

## Install

```bash
npx candor-ui add sound-setting
```

Run `npx candor-ui@latest init` once first (https://candor.themethodic.com/get-started.md). Without the CLI: `npx shadcn@latest add @candor/sound-setting`.
It brings in: button, locale, locked, sound, tokens, use-attention, utils.
npm packages: lucide-react@^1.67.0.

## Example

```tsx
"use client"

import * as React from "react"
import { SoundSetting, soundOff, type SoundChoice } from "@/components/sound-setting"
import { useAttention } from "@/lib/use-attention"
import { soundEvents } from "@/lib/sound"
import { Button } from "@/components/ui/button"

/*
 * Sounds are off until the person picks "When I'm away" or "Always"; that click is also what lets the browser play
 * audio, so the first event plays once as a preview. The choice lives here, in the app (the kit stores none of it),
 * and goes to useAttention as `sound`. In an app the files come from your brand file (`--cd-sound-needs-you:
 * "/sounds/needs-you.mp3"`); this example sets the same names on the page while it is mounted, which is where the
 * `sounds` starter set puts its files. Without them, Play says no sound is set.
 */
export default function Example() {
  const [choice, setChoice] = React.useState<SoundChoice>(soundOff)
  React.useLayoutEffect(() => {
    for (const e of soundEvents) document.documentElement.style.setProperty(`--cd-sound-${e}`, `"/sounds/${e}.mp3"`)
    return () => {
      for (const e of soundEvents) document.documentElement.style.removeProperty(`--cd-sound-${e}`)
    }
  }, [])
  const attention = useAttention({ sound: { enabled: choice.mode !== "off", when: choice.mode === "away" ? "away" : "always", events: choice.events } })
  return (
    <div className="flex max-w-xl flex-col items-start gap-3 text-sm">
      <SoundSetting value={choice} onChange={setChoice} className="w-full" />
      <Button size="sm" variant="outline" onClick={() => attention.signal({ kind: "approval", title: "Approve: raise the export batch size" })}>
        An approval arrives
      </Button>
      <p className="text-xs text-muted-foreground">
        The sound is never the only signal: the count here ({attention.count}) and the “(n)” in the page title say the same while you are away.
      </p>
    </div>
  )
}
```

## <SoundSetting>

Props:

- `value?: SoundChoice | undefined` (default `soundOff`): The person's choice as the app last stored it. Left out, the control shows Off with nothing chosen.
- `onChange: (next: SoundChoice) => void`: Called with the new choice when the person picks a mode or ticks an event.
- `events?: readonly SoundEvent[] | undefined` (default `soundEvents`): The events this app offers; all five when left out.
- `className?: string | undefined`

## Values

### soundOff

The state before the person has chosen anything: sounds off, no event picked.

```ts
const soundOff: SoundChoice
```

## Strings (`soundSetting`)

The fixed words, English by default. Override any of them under `soundSetting` in `<LocaleProvider strings={{ soundSetting: { … } }}>`; the table is exported as `soundSettingStrings`.

- `label`: Sounds
- `modeLegend`: Sounds
- `modeOff`: Off
- `modeAway`: When I'm away
- `modeAlways`: Always
- `eventsLegend`: Play a sound when
- `turnOnFirst`: Turn sounds on to choose which ones.
- `chooseEvents`: Choose the events to hear.
- `loadFailed`: The sound file couldn't be loaded.
- `play`: Play
- `playEvent`: Play the sound for: {event}
- `noSound`: No sound set for this event
- `alsoShownAs`: Also shown as: {cue}
- `needs-you`: Something needs me
- `needs-you.when`: A new approval, question or choice waits on me
- `needs-you.cue`: the prompt or row, the (n) in the page title, a notification
- `finished`: A run I started finished
- `finished.when`: A run I started has finished
- `finished.cue`: a toast, the result, the badge
- `failed`: A run failed
- `failed.when`: A run failed, or a guardrail stopped it
- `failed.cue`: an error that stays on screen, the way to recover, a notice
- `reply`: A person replied
- `reply.when`: A person, not an agent, replied in a thread I'm in
- `reply.cue`: the message and the badge
- `silent`: A run has gone quiet
- `silent.when`: A run I started hasn't been heard from for longer than it should
- `silent.cue`: “No update since…”

## Types

### SoundMode

When sounds play: never, only while the window isn't in front of the person, or whether it is or not.

```ts
"off" | "away" | "always"
```

### SoundChoice

What the person chose. The kit stores none of it: the app keeps it and passes it to `useAttention`'s `sound` option.

```ts
{
  /** Whether and when sounds play. */
  mode: SoundMode;
  /** The events the person left on. */
  events: readonly SoundEvent[];
}
```

## Types from other items

The props above take these. Each is defined by another item; import it from the path shown.

### SoundEvent

Import: `import type { SoundEvent } from "@/lib/sound"` (item `sound`, https://candor.themethodic.com/docs/sound.md)

The five things the app may make a sound for. A closed list: `reply` and `silent` have no `AttentionKind` until 3.0.

```ts
"needs-you" | "finished" | "failed" | "reply" | "silent"
```

## Rules for using Candor UI

- Components take plain typed data. Never pass model output as HTML or render it as a link; messages are typed blocks.
- A control that can't be used says why: wrap it in `<Locked reason="…">`, don't just disable it.
- Don't show a value you don't have: leave it out (it renders as —) rather than guess. An estimate is labelled as one.
- Nothing is pre-selected in a decision a person makes, and the agent is named from your app's data, never from model output.
- Your app's words go in `LocaleProvider` (fixed strings) and `TermsProvider` (nouns); entity kinds and agent roles in `EntityKindsProvider` and `AgentRolesProvider`.
