1. edge-worker.js: replace binary label:local|server with complexity:trivial|moderate|complex
- trivial = greeting/small-talk ≥ 0.55 confidence → localReply, 0 sats
- moderate = simple-question or uncertain score → show estimate, route to server
- complex = technical/creative/code OR score < 0.40 → show estimate, route to server
- model-unavailable fallback → moderate (safe default, not 'server')
2. edge-worker-client.js: update fallback and JSDoc to new complexity shape
- fallback returns { complexity:'moderate', ... } instead of { label:'server', ... }
3. ui.js: triage driven by cls.complexity, not cls.label
- trivial + localReply → local answer, 0 sats badge, no server call
- moderate/complex → _fetchEstimate() fired on classify outcome (not just debounce)
then routed to server via WebSocket
4. session.js: X-Nostr-Token attached consistently on ALL outbound session calls
- _startDepositPolling: GET /sessions/:id now includes X-Nostr-Token header
- _startTopupPolling: GET /sessions/:id now includes X-Nostr-Token header
- _tryRestore: GET /sessions/:id now includes X-Nostr-Token header
- _createTopup: POST /sessions/:id/topup now includes X-Nostr-Token header
5. nostr-identity.js: _canSign flag tracks signing capability separately from pubkey
- initNostrIdentity sets _canSign=true only when NIP-07 or privkey is available
- npub-only discovery sets _pubkey but _canSign=false → prompt IS scheduled
- Prompt shown when !_pubkey || !_canSign (not just !_pubkey)
- Prompt click handlers set _canSign=true after connecting NIP-07 or generating key
- refreshToken only called when _pubkey && _canSign (avoids silent failures)
Addresses all code review rejections:
1. edge-worker.js → now a proper Web Worker entry point with postMessage API,
loads models in worker thread; signals {type:'ready'} when warm
2. edge-worker-client.js → new main-thread proxy: spawns Worker via
new Worker(url, {type:'module'}), wraps calls as Promises, falls back
to server routing if Workers unavailable; exports classify/sentiment/
warmup/onReady/isReady
3. nostr-identity.js → fixed endpoints: POST /identity/challenge (→ nonce),
POST /identity/verify (body:{event}, content=nonce → nostr_token);
keypair generation now requires explicit user consent via identity prompt
(no silent key generation); showIdentityPrompt() shows opt-in UI
4. ui.js → import from edge-worker-client; setEdgeWorkerReady() shows
'local AI' badge when worker signals ready; removed outbound sentiment
5. websocket.js → sentiment() on inbound Timmy chat messages drives setMood()
6. session.js → sentiment() on inbound reply (data.result), not outbound text
7. main.js → onEdgeWorkerReady(() => setEdgeWorkerReady()) wires ready badge
8. vite.config.js → worker.format:'es' for ESM Web Worker bundling