How to toggle features on / off
Every dddk feature is opt-in or runtime-toggleable. Below is the full list of toggles and how to flip each one.
Voice input (hold-space STT)
Default: on if you wire up a VoiceModule.
import { VoiceModule } from '@perhapxin/dddk';
const voice = new VoiceModule({ llm: yourLLM });
const dddk = new DotDotDuck({
llm: yourLLM,
voice: { enabled: true }, // default true
});
dddk.on('voice_start', () => {
voice.captureOnce(dddk.subtitle).then((text) => {
if (text) dddk.startAgent(text);
});
});
dddk.mount();
Toggle at runtime:
dddk.setVoiceEnabled(false);
dddk.isVoiceEnabled(); // boolean
When disabled, holding space does nothing — single-tap and double-tap (accept / reject for agent prompts) still work.
Subtitle (the floating prompt bar)
The subtitle bar is the dark bottom-of-screen prompt that shows agent messages, voice status, skill steps.
Show / hide programmatically:
dddk.subtitle.show({
text: 'Working on it...',
type: 'agent', // 'agent' | 'info' | 'selection'
autoHide: 2000, // ms, or omit for sticky
hints: 'space to accept · esc to cancel',
onAccept: () => { /* … */ },
onReject: () => { /* … */ },
onCancel: () => { /* … */ },
});
dddk.subtitle.hide();
Show a non-text indicator (small badge in the corner of the bar):
dddk.subtitle.showIndicator('processing', 'Translating…');
dddk.subtitle.hideIndicator();
Suppress all agent-driven subtitle output:
The subtitle bar is driven by webagent events. If you want to mute it for a session without disabling the agent itself:
const original = dddk.subtitle.show.bind(dddk.subtitle);
dddk.subtitle.show = () => {}; // muted
// restore: dddk.subtitle.show = original;
For a permanent skin, override the subtitle CSS tokens (see 11-theming) — --dddk-bar-*.
Magic Pointer (long-press → AI annotation)
Opt-in. The feature only runs if you instantiate it.
import { MagicPointer } from '@perhapxin/dddk';
const mp = new MagicPointer({
llm: yourLLM,
dwellMs: 500, // default
triggerModifier: null, // or 'alt' | 'shift' | 'meta' | 'ctrl'
});
mp.attachTo(dddk);
Toggle at runtime:
mp.setEnabled(false);
mp.isEnabled(); // boolean
Opt elements out:
Add data-no-magic-pointer on host elements that already use long-press (delete confirmations, etc.). Or pass a CSS selector:
new MagicPointer({
llm,
ignoreSelector: '.acme-hold-to-confirm, [data-no-mp]',
});
Require a modifier (zero conflicts with native long-press anywhere):
new MagicPointer({ llm, triggerModifier: 'alt' });
// Now only Alt+long-press triggers, plain long-press goes to the page.
Selection Tools (inline toolbar on text selection)
Opt-in.
import { SelectionToolsModule } from '@perhapxin/dddk';
const selection = new SelectionToolsModule({
llm,
floatingToolbar: true, // false → only palette commands, no inline UI
});
selection.registerOn(dddk.palette, dddk.subtitle);
selection.start(dddk.subtitle);
Disable inline toolbar, keep palette commands:
selection.stop(); // remove inline toolbar
// translate: / summarize: / explain: prefixes in palette still work
Theme switch (light / dark)
Built in. Use the ThemeToggleModule or set the data attribute directly.
import { ThemeToggleModule } from '@perhapxin/dddk';
const theme = new ThemeToggleModule();
theme.registerOn(dddk.palette); // adds /theme command
theme.apply(); // apply current preference
// programmatic:
theme.set('dark'); // 'light' | 'dark' | 'system'
theme.toggle(); // light ↔ dark
Or just:
document.documentElement.dataset.theme = 'dark';
All dddk surfaces re-render via CSS tokens. See 11-theming for the full token reference + custom modes (sepia, high-contrast, etc.).
Language switcher
import { LanguageSwitcherModule } from '@perhapxin/dddk';
const lang = new LanguageSwitcherModule({
available: [
{ code: 'en', label: 'English' },
{ code: 'zh-TW', label: '繁體中文' },
{ code: 'ja', label: '日本語' },
],
current: 'en',
onChange: (code) => { yourI18n.setLocale(code); },
});
lang.registerOn(dddk.palette); // adds /language command
dddk does not own your i18n — you wire the onChange callback to whatever locale system your app uses (next-intl, sveltekit-i18n, vue-i18n, your own).
Mobile chrome
Opt-in. Mobile-only — no-op on desktop.
import { MobileTrigger } from '@perhapxin/dddk';
const mobile = new MobileTrigger();
mobile.attachTo(dddk);
Gesture: rapid up-down swipe ≥3 reversals in 700ms. Won't fire on normal scroll. Top + bottom chrome bars appear; user can tap palette icon, central voice button, or close.
Toggle at runtime: mobile.show() / mobile.hide() / mobile.toggle().
Agent cursor (visible cursor + pre-click pause)
Opt-in. Shows a large floating cursor when the webagent is acting.
import { AgentCursor } from '@perhapxin/dddk';
const cursor = new AgentCursor({
preClickPauseMs: 250, // pause before each action fires
size: 32,
travelMs: 350,
});
cursor.attachTo(dddk);
Runtime toggle:
cursor.setEnabled(false);
Cross-tab session sync
new DotDotDuck({
llm,
webAgent: { crossTabSync: true }, // default false
});
When true, the agent session is mirrored to localStorage + broadcast on a BroadcastChannel. A new tab on the same origin picks up where the previous one left off. Same-origin only — see webagent / 12-session-continuity.
Tear-down
When your app unmounts (page leave, host SPA route change, etc.) — always:
dddk.destroy();
// + any modules you instantiated:
voice.destroy();
selection.stop();
magicPointer.destroy();
agentCursor.destroy();
mobile.destroy();
Calling mount() again afterward without destroy will double-bind listeners.