feat: Workshop interaction layer — chat input, visitor presence, bark display (#40, #41, #42)

Implements the minimum viable conversation loop for Workshop #222:
visitor arrives → sends message → Timmy barks back.

- js/visitor.js: Visitor presence protocol (#41)
  - visitor_entered on load (with device detection: ipad/desktop/mobile)
  - visitor_left on unload or 30s hidden (iPad tab suspend)
  - visitor_message dispatched from chat input
  - visitor_interaction export for future tap-to-interact (#44)
  - Session duration tracking

- js/bark.js: Bark display system (#42)
  - showBark() renders prominent viewport toasts with typing animation
  - Auto-dismiss after display time + typing duration
  - Queue system (max 3 simultaneous, overflow queued)
  - Demo barks in mock mode (Workshop-themed: 222, sovereignty, chain)
  - Barks also logged permanently in chat panel

- index.html: Chat input bar (#40)
  - Terminal-styled input + send button at viewport bottom
  - Enter to send (desktop), button tap (iPad)
  - Safe-area padding for notched devices
  - Chat panel repositioned above input bar
  - Bark container in upper viewport third

- js/websocket.js: New message handlers
  - 'bark' message → showBark() dispatch
  - 'ambient_state' message → placeholder for #43
  - Demo barks start in mock mode

- js/ui.js: appendChatMessage() accepts optional CSS class
  - Visitor messages styled differently from agent messages

Build: 18 modules, 0 errors
Tested: desktop (1280x800) + mobile (390x844) via Playwright

Closes #40, #41, #42
Ref: rockachopa/Timmy-time-dashboard#222, #243
This commit is contained in:
2026-03-19 01:46:04 +00:00
parent 745208f3c8
commit a9da7393c7
6 changed files with 408 additions and 7 deletions

View File

@@ -4,6 +4,7 @@ import { initEffects, updateEffects } from './effects.js';
import { initUI, updateUI } from './ui.js';
import { initInteraction, updateControls } from './interaction.js';
import { initWebSocket, getConnectionState, getJobCount } from './websocket.js';
import { initVisitor } from './visitor.js';
let frameCount = 0;
let lastFpsTime = performance.now();
@@ -17,6 +18,7 @@ function main() {
initInteraction(camera, renderer);
initUI();
initWebSocket(scene);
initVisitor();
// Debounce resize to 1 call per frame (avoids dozens of framebuffer re-allocations during drag)
let resizeFrame = null;