v0.2.2 release notes

Patch release on top of v0.2.1. No breaking changes. Three themes:

  1. Prompt registry — every LLM system prompt the SDK ships is now overridable per-locale from one API. Host localises Japanese/Spanish/French without patching any subsystem.
  2. autoInstall() one-line install — a zero-config on-ramp that returns a fully-constructed DotDotDuck with sensible defaults (locale detection, demo LLM stub, bundled duck sprites).
  3. Mascot upgrade — new HERO greeting capsule (FAB morphs into a horizontal pill), 8 mascot animations tunable via CSS variables, and SDK ships duck sprites out of the box (no more --dddk-*-url wiring for basic install).

Plus fixes for the palette footer disappearing on touch-capable laptops, InlineAgent's redundant "processing" indicator, and the Dwell swim overlay URL scoping bug.

TL;DR

  • dddk.prompts — new PromptRegistry on every DotDotDuck instance. dddk.prompts.list() enumerates every SDK-shipped prompt id; dddk.prompts.override(id, locale, render) swaps one per-locale; dddk.prompts.append(id, extra) stitches site-wide reminders on the end.
  • autoInstall(overrides?) — new factory. import { autoInstall } from '@perhapxin/dddk'; const dddk = autoInstall(); boots everything with a demo LLM, browser-locale detection, and default duck sprites. Pass overrides to fill in real host wiring.
  • HERO greeting capsule — first-visit FAB morphs into a horizontal yellow-translucent pill with the mascot on the right and greeting text on the left. All 12+ visual props are --dddk-fab-hero-* variables; hosts theme by declaring on :root.
  • 8 mascot animations var-tunable--dddk-avatar-swim-duration, --dddk-avatar-bob-duration, --dddk-indicator-swim-duration, --dddk-indicator-wave-duration, --dddk-brand-mark-bob-duration, --dddk-dwell-avatar-bob-duration, --dddk-dwell-chill-breath-duration, plus the existing --dddk-fab-hero-transition-ms / --dddk-swim-duration. Hosts throttle for slower brands or hush for reduced motion.
  • 7 duck sprites bundled — SDK now ships neutral.png, swim-side.png, hero-greet.png, chill-shades.png, swim-cycle.png, logo.png, and cursor.png under dist/duck/, wired via --dddk-*-url defaults in tokens.css. Hosts don't need to serve their own PNGs unless they want to swap the mascot.
  • New WebAgent cursor sprite — the SVG pointer glyph (small yellow triangle + duck head) is replaced by a bitmap sprite: a duck riding a paper airplane, sharp origami tip at the top-left as the click origin. Swap via --dddk-cursor-url. Scroll + reading modes keep the SVG glyphs (still themeable via --webagent-cursor-{fill,stroke}).
  • Palette "Powered by dotdotduck" footer — brand mark on the right (uses --dddk-brand-mark-url, defaults to the shipped logo); left side flips between keyboard hints (↑ ↓ ⏎ esc) and touch hints (tap to pick / tap outside to close) based on (hover: none) and (pointer: coarse). Fixes the "footer vanished on my touchscreen laptop" bug.
  • InlineAgent no longer double-signals — the redundant "processing" pill is gone; the streaming diff overlay IS the loading state, showing it twice added noise and (worse) sometimes lingered after the diff finished due to a race between clear-on-first-delta and diff-render.
  • Dwell swim URL fixed — swim overlay's spritesheet URL now goes through --dddk-swim-cycle-url (root-absolute-safe) instead of a hard-coded /duck/swim-cycle.png. Hosts serving from a subdirectory no longer lose the animation.

What changed

Prompt registry (the headline for teams doing multi-language product)

Prompts previously lived inline in ~10 subsystem modules (webagent narrator, planner, InlineAgent, markdown-edit, translator, STT cleanup, Dwell classifier). A host that wanted "Japanese prompts" or "no persona line" had to spelunk each module.

v0.2.2 consolidates every default behind stable IDs on dddk.prompts:

dddk.prompts.list();
// → ['dwell-classify.system', 'inline-edit.system', 'markdown-edit.system',
//    'planner.system', 'translate.system', 'voice-cleanup.system',
//    'webagent-cot.system', 'webagent-narrator.system', 'task.system']

// Override one prompt for one locale
dddk.prompts.override('inline-edit.system', 'ja', () => `あなたはインライン編集アシスタントです…`);

// Append a site-wide reminder to the narrator (runs for every locale)
dddk.prompts.append('webagent-narrator.system', () => 'Never mention pricing. Always cite sources.');

// Reset a prompt back to the SDK default
dddk.prompts.reset('inline-edit.system');

Fallback order: override(id, locale)override(id, 'en') → SDK default. Hosts localise EN + zh-TW + ja without touching the defaults for locales they don't care about.

Full guide: prompts.md.

autoInstall()

Zero-config on-ramp:

import { autoInstall } from '@perhapxin/dddk';
import '@perhapxin/dddk/styles.css';

const dddk = autoInstall();
// Mounts palette + subtitle + Dwell + FAB with SDK-default duck sprites.
// Locale auto-detected from navigator.language.
// LLM defaults to a "wire your real provider" demo stub — swap in when ready.

Real integration passes overrides on top:

const dddk = autoInstall({
  llm: myOpenAIProvider,
  siteName: 'Acme',
  agentName: 'Rex',
  paletteCommands: myCommands,
  brand: { voice: 'friendly' },
});

autoInstall(overrides) and new DotDotDuck({ ...defaults, ...overrides }) are functionally equivalent — the factory just picks the defaults. See auto-install.md.

HERO greeting capsule (showHeroGreeting)

New greeting shape: on first visit the small circular FAB morphs horizontally into a yellow-translucent capsule — mascot stays on the right (its original corner), greeting text fills the space that opens up on the left, all-CSS transition (no separate bubble element). Every visual prop is a variable: width, height, radius, background, shadow, transition, face size, padding — full list in mascot.md.

Hosts fire it from wherever their onboarding flow lives:

await dddk.mobile.showHeroGreeting(
  "Hi! I'm Rex. Tap me or press Ctrl+K to open the palette.",
  { autoDismissMs: 20000 },
);

Reverses on dismiss (capsule contracts back to circle, face reverts to the previous state).

8 mascot animations tunable

Previously only 2 mascot animation timings were overridable via CSS variables (--dddk-fab-hero-transition-ms, --dddk-swim-duration). v0.2.2 promotes 8 more that hosts actually want to tune for brand cadence:

:root {
  /* Bar-attached avatar */
  --dddk-avatar-swim-duration: 620ms;
  --dddk-avatar-bob-duration: 2.6s;
  /* Thinking indicator */
  --dddk-indicator-swim-duration: 1.5s;
  --dddk-indicator-swim-pip-duration: 1s;
  --dddk-indicator-wave-duration: 0.9s;
  /* Palette brand mark */
  --dddk-brand-mark-bob-duration: 3.2s;
  /* Dwell corner mascot */
  --dddk-dwell-avatar-bob-duration: 2s;
  --dddk-dwell-chill-breath-duration: 3s;
}

To kill an animation entirely, target the selector directly ([data-dddk-ui="palette-footer-brand-mark"] { animation: none; }). Under prefers-reduced-motion: reduce the SDK already switches every mascot loop off — the variables above are for brand tuning, not accessibility.

Bundled duck sprites

The SDK now ships six PNGs under dist/duck/, wired via --dddk-*-url defaults on :root:

Variable Default Where it's used
--dddk-avatar-url ./duck/neutral.png Subtitle bar avatar, Dwell chip
--dddk-swim-url ./duck/swim-side.png Proactive swim indicator
--dddk-hero-url ./duck/hero-greet.png HERO greeting (optional big-reveal sprite)
--dddk-chill-url ./duck/chill-shades.png Dwell frame corner mascot
--dddk-swim-cycle-url ./duck/swim-cycle.png Dwell top-edge swim spritesheet (8 frames)
--dddk-brand-mark-url ./duck/logo.png Palette "Powered by dotdotduck" mark
--dddk-cursor-url ./duck/cursor.png WebAgent synthetic cursor (paper-airplane + duck)

Hosts wanting to swap the mascot for their own brand character just redeclare on :root:

:root {
  --dddk-avatar-url: url('/my-brand/mascot.png');
  --dddk-swim-url: url('/my-brand/swim.png');
  /* Keep --dddk-brand-mark-url as-is if you want to keep the dotdotduck
     attribution on the palette footer. */
}

Sub-path caveat — Chrome resolves url() in CSS custom properties relative to the DOCUMENT (not the CSS file) when the variable is applied via a JS-injected <style> (which is how Dwell / palette style shells load). If your app is served from a subdirectory like /tools/dddk/, the SDK's default ./duck/... breaks. Fix: override with root-absolute paths on your app's :root--dddk-swim-cycle-url: url('/my-app/duck/swim-cycle.png');. Detailed writeup in theming.md.

Two changes:

  1. Brand mark on the right shows the shipped logo icon (via new --dddk-brand-mark-url, defaults to ./duck/logo.png bundled in dist/duck/). Falls back to --dddk-avatar-url if the brand mark is unset — hosts overriding the avatar don't accidentally lose the palette attribution.
  2. Left-side hints adapt to input mode: keyboard hints (↑ ↓ navigate · ↵ select · esc close) on hover-capable devices; touch hints (Tap a row to pick · Tap outside to close) on (hover: none) and (pointer: coarse). Previous v0.2.1 media query used (pointer: coarse) alone, which caught touchscreen laptops with real trackpads and hid the footer entirely.

InlineAgent stops double-signalling

The "processing" subtitle indicator that fired on runInstruction and prefix-submit paths is removed. Rationale: the streaming diff overlay itself is the loading state — text streams in chunk by chunk with visible strikeout / new-text — and a redundant pill at bottom-of-screen sometimes lingered after the diff completed because clear-on-first-delta could race the diff finish. Errors still surface via subtitle.show({ type: 'info' }) in the catch branch.

Bug fixes

  • Palette footer disappearing on touch-capable laptops — media query was (pointer: coarse) alone, which matches trackpads on modern touch laptops. Tightened to (hover: none) and (pointer: coarse) so only truly touch-primary devices flip the hints.
  • Dwell swim overlay 404 on subdirectory hosts — previously hard-coded url('/duck/swim-cycle.png'). Now goes through --dddk-swim-cycle-url with a shipped default; hosts override for subdirectory paths.
  • Palette footer text vanishing after locale changerefreshFooter() innerHTML-replaced the strip, wiping the brand mark that the initial mount had appended separately. Both paths now converge on buildFooterHTML().
  • 12 undeclared CSS variables--dddk-fab-hero-*, --dddk-indicator-duck-url, --dddk-dwell-swim-*, etc. were read via var() throughout the SDK but never declared in tokens.css (no discoverable tuning surface for hosts). All declared with sensible defaults.

Contributor changes

  • New src/prompts/ directory (registry.ts + defaults.ts) as the single source of truth for prompt IDs. Adding a new prompt: add to PROMPT_IDS enum → register in defaults.ts → resolve at callsite via promptRegistry.render(id, ctx, locale).
  • Three inline prompt consts extracted to their own files (modules/immersive-translate/prompt.ts, modules/voice/prompt.ts, triggers/dwell/prompt.ts) so the registry can import them without pulling in the subsystem runtime.
  • tsup.config.ts onSuccess hook now copies src/duck/*.pngdist/duck/, and rewrites url('./duck/...')url('../duck/...') for the sub-path dist/styles/tokens.css variant so both the bundled dist/styles.css and sub-path imports resolve correctly.

Migrating from v0.2.1

Purely additive — no callsite changes required. See migrating.md for optional adoption of the new APIs.