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.