makethisbetter 1.1.2 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -28,6 +28,7 @@ export declare class ClarifyCard {
28
28
  private stopPolling;
29
29
  private poll;
30
30
  private handleDone;
31
+ private renderConversation;
31
32
  private renderMessages;
32
33
  private appendMessage;
33
34
  private showThinking;
@@ -1 +1 @@
1
- {"version":3,"file":"clarify.d.ts","sourceRoot":"","sources":["../../src/popup/clarify.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAA;AACvD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAC3C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAA;AAO9C,UAAU,kBAAkB;IAC1B,UAAU,EAAE,MAAM,CAAA;IAClB,SAAS,EAAE,SAAS,CAAA;IACpB,QAAQ,EAAE,YAAY,CAAA;IACtB,MAAM,EAAE,MAAM,IAAI,CAAA;CACnB;AAED,qBAAa,WAAW;IACtB,OAAO,CAAC,EAAE,CAAgB;IAC1B,OAAO,CAAC,UAAU,CAAiB;IACnC,OAAO,CAAC,OAAO,CAAmB;IAClC,OAAO,CAAC,OAAO,CAAoB;IACnC,OAAO,CAAC,SAAS,CAA8C;IAC/D,OAAO,CAAC,SAAS,CAA6C;IAC9D,OAAO,CAAC,UAAU,CAAQ;IAC1B,OAAO,CAAC,SAAS,CAAW;IAC5B,OAAO,CAAC,MAAM,CAAY;IAC1B,OAAO,CAAC,OAAO,CAAQ;IACvB,OAAO,CAAC,SAAS,CAAQ;gBAEb,MAAM,EAAE,eAAe,EAAE,IAAI,EAAE,kBAAkB;IAmB7D,OAAO,CAAC,SAAS;IAkBjB,OAAO,CAAC,UAAU;YAqBJ,iBAAiB;YAiBjB,WAAW;IAiCzB,OAAO,CAAC,YAAY;IAKpB,OAAO,CAAC,WAAW;YAOL,IAAI;IAelB,OAAO,CAAC,UAAU;IAQlB,OAAO,CAAC,cAAc;IAQtB,OAAO,CAAC,aAAa;IAQrB,OAAO,CAAC,YAAY;IAQpB,OAAO,IAAI,IAAI;CAShB"}
1
+ {"version":3,"file":"clarify.d.ts","sourceRoot":"","sources":["../../src/popup/clarify.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAA;AACvD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAC3C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAA;AAO9C,UAAU,kBAAkB;IAC1B,UAAU,EAAE,MAAM,CAAA;IAClB,SAAS,EAAE,SAAS,CAAA;IACpB,QAAQ,EAAE,YAAY,CAAA;IACtB,MAAM,EAAE,MAAM,IAAI,CAAA;CACnB;AAED,qBAAa,WAAW;IACtB,OAAO,CAAC,EAAE,CAAgB;IAC1B,OAAO,CAAC,UAAU,CAAiB;IACnC,OAAO,CAAC,OAAO,CAAmB;IAClC,OAAO,CAAC,OAAO,CAAoB;IACnC,OAAO,CAAC,SAAS,CAA8C;IAC/D,OAAO,CAAC,SAAS,CAA6C;IAC9D,OAAO,CAAC,UAAU,CAAQ;IAC1B,OAAO,CAAC,SAAS,CAAW;IAC5B,OAAO,CAAC,MAAM,CAAY;IAC1B,OAAO,CAAC,OAAO,CAAQ;IACvB,OAAO,CAAC,SAAS,CAAQ;gBAEb,MAAM,EAAE,eAAe,EAAE,IAAI,EAAE,kBAAkB;IAmB7D,OAAO,CAAC,SAAS;IAkBjB,OAAO,CAAC,UAAU;YAuBJ,iBAAiB;YAiBjB,WAAW;IAiCzB,OAAO,CAAC,YAAY;IAKpB,OAAO,CAAC,WAAW;YAOL,IAAI;IAelB,OAAO,CAAC,UAAU;IAQlB,OAAO,CAAC,kBAAkB;IAM1B,OAAO,CAAC,cAAc;IAQtB,OAAO,CAAC,aAAa;IAQrB,OAAO,CAAC,YAAY;IAQpB,OAAO,IAAI,IAAI;CAShB"}
package/dist/types.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  export interface MakeThisBetterConfig {
2
2
  projectKey: string;
3
- entryMode?: 'button' | 'pet';
3
+ entryMode?: 'button';
4
4
  locale?: string;
5
5
  position?: 'left' | 'right';
6
6
  theme?: 'light' | 'dark' | 'auto';
@@ -49,6 +49,7 @@ export interface FeedbackResponse {
49
49
  id: string;
50
50
  status: string;
51
51
  project_id: string;
52
+ ai_clarify_available?: boolean;
52
53
  }
53
54
  export interface ClarifyMessage {
54
55
  role: string;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,oBAAoB;IACnC,UAAU,EAAE,MAAM,CAAA;IAClB,SAAS,CAAC,EAAE,QAAQ,GAAG,KAAK,CAAA;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;IAC3B,KAAK,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,CAAA;IACjC,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,oBAAoB,CAAC,EAAE,OAAO,CAAA;IAC9B,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,WAAW,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAA;IACnC,IAAI,CAAC,EAAE;QACL,EAAE,CAAC,EAAE,MAAM,CAAA;QACX,KAAK,CAAC,EAAE,MAAM,CAAA;QACd,IAAI,CAAC,EAAE,MAAM,CAAA;KACd,CAAA;CACF;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,KAAK,GAAG,MAAM,CAAA;IACpB,CAAC,EAAE,MAAM,CAAA;IACT,CAAC,EAAE,MAAM,CAAA;IACT,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,eAAe;IAC9B,WAAW,EAAE,MAAM,CAAA;IACnB,QAAQ,EAAE,MAAM,CAAA;IAChB,UAAU,EAAE,MAAM,CAAA;IAClB,OAAO,EAAE,MAAM,CAAA;IACf,EAAE,EAAE,MAAM,CAAA;IACV,YAAY,EAAE,MAAM,CAAA;IACpB,aAAa,EAAE,MAAM,CAAA;IACrB,cAAc,EAAE,MAAM,EAAE,CAAA;IACxB,WAAW,EAAE,UAAU,EAAE,CAAA;IACzB,cAAc,CAAC,EAAE;QACf,QAAQ,EAAE,MAAM,CAAA;QAChB,IAAI,EAAE,MAAM,CAAA;QACZ,IAAI,EAAE,MAAM,CAAA;KACb,CAAA;IACD,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAA;IAC5C,kBAAkB,CAAC,EAAE,MAAM,CAAA;CAC5B;AAED,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAA;IACV,MAAM,EAAE,MAAM,CAAA;IACd,UAAU,EAAE,MAAM,CAAA;CACnB;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,EAAE,cAAc,EAAE,CAAA;IAC1B,IAAI,EAAE,OAAO,CAAA;CACd"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,oBAAoB;IACnC,UAAU,EAAE,MAAM,CAAA;IAClB,SAAS,CAAC,EAAE,QAAQ,CAAA;IACpB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;IAC3B,KAAK,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,CAAA;IACjC,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,oBAAoB,CAAC,EAAE,OAAO,CAAA;IAC9B,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,WAAW,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAA;IACnC,IAAI,CAAC,EAAE;QACL,EAAE,CAAC,EAAE,MAAM,CAAA;QACX,KAAK,CAAC,EAAE,MAAM,CAAA;QACd,IAAI,CAAC,EAAE,MAAM,CAAA;KACd,CAAA;CACF;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,KAAK,GAAG,MAAM,CAAA;IACpB,CAAC,EAAE,MAAM,CAAA;IACT,CAAC,EAAE,MAAM,CAAA;IACT,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,eAAe;IAC9B,WAAW,EAAE,MAAM,CAAA;IACnB,QAAQ,EAAE,MAAM,CAAA;IAChB,UAAU,EAAE,MAAM,CAAA;IAClB,OAAO,EAAE,MAAM,CAAA;IACf,EAAE,EAAE,MAAM,CAAA;IACV,YAAY,EAAE,MAAM,CAAA;IACpB,aAAa,EAAE,MAAM,CAAA;IACrB,cAAc,EAAE,MAAM,EAAE,CAAA;IACxB,WAAW,EAAE,UAAU,EAAE,CAAA;IACzB,cAAc,CAAC,EAAE;QACf,QAAQ,EAAE,MAAM,CAAA;QAChB,IAAI,EAAE,MAAM,CAAA;QACZ,IAAI,EAAE,MAAM,CAAA;KACb,CAAA;IACD,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAA;IAC5C,kBAAkB,CAAC,EAAE,MAAM,CAAA;CAC5B;AAED,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAA;IACV,MAAM,EAAE,MAAM,CAAA;IACd,UAAU,EAAE,MAAM,CAAA;IAClB,oBAAoB,CAAC,EAAE,OAAO,CAAA;CAC/B;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,EAAE,cAAc,EAAE,CAAA;IAC1B,IAAI,EAAE,OAAO,CAAA;CACd"}
@@ -3,7 +3,6 @@ export declare class WidgetController {
3
3
  private config;
4
4
  private shadow;
5
5
  private tab;
6
- private pet;
7
6
  private toolbar;
8
7
  private session;
9
8
  private popup;
@@ -1 +1 @@
1
- {"version":3,"file":"controller.d.ts","sourceRoot":"","sources":["../../src/widget/controller.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAE,oBAAoB,EAAc,MAAM,UAAU,CAAA;AAIhE,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,MAAM,CAAsB;IACpC,OAAO,CAAC,MAAM,CAAkB;IAChC,OAAO,CAAC,GAAG,CAA2B;IACtC,OAAO,CAAC,GAAG,CAAwB;IACnC,OAAO,CAAC,OAAO,CAAiC;IAChD,OAAO,CAAC,OAAO,CAAiC;IAChD,OAAO,CAAC,KAAK,CAA4B;IACzC,OAAO,CAAC,OAAO,CAA2B;IAC1C,OAAO,CAAC,OAAO,CAA2B;IAC1C,OAAO,CAAC,iBAAiB,CAAqC;IAC9D,OAAO,CAAC,SAAS,CAAW;IAC5B,OAAO,CAAC,gBAAgB,CAAuB;IAC/C,OAAO,CAAC,mBAAmB,CAAmC;IAC9D,OAAO,CAAC,QAAQ,CAAgC;IAChD,OAAO,CAAC,IAAI,CAAe;IAC3B,OAAO,CAAC,iBAAiB,CAA0B;IACnD,OAAO,CAAC,gBAAgB,CAAgC;gBAE5C,MAAM,EAAE,oBAAoB;IAcxC,OAAO,CAAC,OAAO;IAgBf,OAAO,CAAC,cAAc;IAStB,OAAO,CAAC,iBAAiB;IAYzB,OAAO,CAAC,wBAAwB;IAKhC,OAAO,CAAC,mBAAmB;IAiB3B,OAAO,CAAC,uBAAuB;IA6B/B,OAAO,CAAC,SAAS;YAcH,YAAY;IAmC1B,OAAO,CAAC,WAAW;IAgBnB,OAAO,CAAC,WAAW;IAkBnB,OAAO,CAAC,OAAO;IAiBf,OAAO,IAAI,IAAI;CAYhB"}
1
+ {"version":3,"file":"controller.d.ts","sourceRoot":"","sources":["../../src/widget/controller.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAAE,oBAAoB,EAAc,MAAM,UAAU,CAAA;AAIhE,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,MAAM,CAAsB;IACpC,OAAO,CAAC,MAAM,CAAkB;IAChC,OAAO,CAAC,GAAG,CAA2B;IACtC,OAAO,CAAC,OAAO,CAAiC;IAChD,OAAO,CAAC,OAAO,CAAiC;IAChD,OAAO,CAAC,KAAK,CAA4B;IACzC,OAAO,CAAC,OAAO,CAA2B;IAC1C,OAAO,CAAC,OAAO,CAA2B;IAC1C,OAAO,CAAC,iBAAiB,CAAqC;IAC9D,OAAO,CAAC,SAAS,CAAW;IAC5B,OAAO,CAAC,gBAAgB,CAAuB;IAC/C,OAAO,CAAC,mBAAmB,CAAmC;IAC9D,OAAO,CAAC,QAAQ,CAAgC;IAChD,OAAO,CAAC,IAAI,CAAe;IAC3B,OAAO,CAAC,iBAAiB,CAA0B;IACnD,OAAO,CAAC,gBAAgB,CAAgC;gBAE5C,MAAM,EAAE,oBAAoB;IAcxC,OAAO,CAAC,OAAO;IAYf,OAAO,CAAC,cAAc;IAStB,OAAO,CAAC,iBAAiB;IAYzB,OAAO,CAAC,wBAAwB;IAKhC,OAAO,CAAC,mBAAmB;IAgB3B,OAAO,CAAC,uBAAuB;IA6B/B,OAAO,CAAC,SAAS;YAcH,YAAY;IAuC1B,OAAO,CAAC,WAAW;IAenB,OAAO,CAAC,WAAW;IAiBnB,OAAO,CAAC,OAAO;IAgBf,OAAO,IAAI,IAAI;CAWhB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "makethisbetter",
3
- "version": "1.1.2",
3
+ "version": "1.2.0",
4
4
  "description": "Make This Better JS Widget SDK — embed user feedback collection in any website",
5
5
  "type": "module",
6
6
  "main": "./dist/makethisbetter.cjs.js",
@@ -70,5 +70,5 @@
70
70
  "type": "git",
71
71
  "url": "https://github.com/makethisbetter/makethisbetter-js.git"
72
72
  },
73
- "license": "BUSL-1.1"
73
+ "license": "MIT"
74
74
  }
@@ -112,15 +112,14 @@ describe('FrustrationDetector', () => {
112
112
  document.body.removeChild(host)
113
113
  })
114
114
 
115
- it('does not directly emit console errors (only counts them as signal)', () => {
115
+ it('does not directly emit console errors (only counts them as dead-click input)', () => {
116
116
  makeDetector()
117
117
 
118
118
  if (window.onerror) {
119
119
  ;(window.onerror as Function)('Test error', 'test.js', 1, 1, new Error('Test error'))
120
120
  }
121
121
 
122
- const errorEvents = events.filter(e => e.signal === 'console_error')
123
- expect(errorEvents.length).toBe(0)
122
+ expect(events.length).toBe(0)
124
123
  })
125
124
 
126
125
  it('stops listening after stop()', () => {
@@ -3,7 +3,6 @@ import { errorSource } from './error-source'
3
3
  export type FrustrationSignal =
4
4
  | 'rage_click'
5
5
  | 'dead_click'
6
- | 'console_error'
7
6
  | 'rapid_navigation'
8
7
  | 'form_failure'
9
8
  | 'error_page'
@@ -395,6 +395,109 @@ describe('ClarifyCard', () => {
395
395
  shadow.destroy()
396
396
  })
397
397
 
398
+ it('ignores Enter during IME composition', async () => {
399
+ const startClarification = vi.fn()
400
+ .mockResolvedValueOnce({
401
+ status: 'pending',
402
+ messages: [{ role: 'assistant', content: 'Tell me more' }],
403
+ done: false,
404
+ } satisfies ClarifyResponse)
405
+ .mockResolvedValueOnce({
406
+ status: 'pending',
407
+ messages: [],
408
+ done: false,
409
+ } satisfies ClarifyResponse)
410
+
411
+ const { shadow, card } = setupCard({ startClarification })
412
+ await vi.waitFor(() => {
413
+ expect(startClarification).toHaveBeenCalledOnce()
414
+ })
415
+ await vi.advanceTimersByTimeAsync(0)
416
+
417
+ const input = shadow.root.querySelector<HTMLInputElement>('.mtb-clarify-input')!
418
+ input.value = '导出坏了'
419
+ input.dispatchEvent(new Event('input'))
420
+
421
+ input.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', isComposing: true }))
422
+ expect(startClarification).toHaveBeenCalledOnce()
423
+
424
+ const legacyImeEnter = new KeyboardEvent('keydown', { key: 'Enter' })
425
+ Object.defineProperty(legacyImeEnter, 'keyCode', { value: 229 })
426
+ input.dispatchEvent(legacyImeEnter)
427
+ expect(startClarification).toHaveBeenCalledOnce()
428
+
429
+ input.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter' }))
430
+ await vi.waitFor(() => {
431
+ expect(startClarification).toHaveBeenCalledTimes(2)
432
+ })
433
+ expect(startClarification).toHaveBeenCalledWith('fb_123', '导出坏了')
434
+
435
+ card.destroy()
436
+ shadow.destroy()
437
+ })
438
+
439
+ it('keeps thinking indicator while waiting for the AI reply', async () => {
440
+ const startClarification = vi.fn().mockResolvedValue({
441
+ status: 'pending',
442
+ messages: [],
443
+ done: false,
444
+ } satisfies ClarifyResponse)
445
+ const getClarification = vi.fn().mockResolvedValue({
446
+ status: 'pending',
447
+ messages: [],
448
+ done: false,
449
+ } satisfies ClarifyResponse)
450
+
451
+ const { shadow, card } = setupCard({ startClarification, getClarification })
452
+ await vi.waitFor(() => {
453
+ expect(startClarification).toHaveBeenCalled()
454
+ })
455
+ await vi.advanceTimersByTimeAsync(0)
456
+
457
+ expect(shadow.root.querySelector('.mtb-clarify-thinking')).toBeTruthy()
458
+
459
+ await vi.advanceTimersByTimeAsync(2000)
460
+ await vi.waitFor(() => {
461
+ expect(getClarification).toHaveBeenCalled()
462
+ })
463
+ await vi.advanceTimersByTimeAsync(0)
464
+ expect(shadow.root.querySelector('.mtb-clarify-thinking')).toBeTruthy()
465
+
466
+ card.destroy()
467
+ shadow.destroy()
468
+ })
469
+
470
+ it('removes thinking indicator when the AI reply arrives', async () => {
471
+ const startClarification = vi.fn().mockResolvedValue({
472
+ status: 'pending',
473
+ messages: [],
474
+ done: false,
475
+ } satisfies ClarifyResponse)
476
+ const getClarification = vi.fn().mockResolvedValue({
477
+ status: 'pending',
478
+ messages: [{ role: 'assistant', content: 'What broke?' }],
479
+ done: false,
480
+ } satisfies ClarifyResponse)
481
+
482
+ const { shadow, card } = setupCard({ startClarification, getClarification })
483
+ await vi.waitFor(() => {
484
+ expect(startClarification).toHaveBeenCalled()
485
+ })
486
+ await vi.advanceTimersByTimeAsync(0)
487
+
488
+ await vi.advanceTimersByTimeAsync(2000)
489
+ await vi.waitFor(() => {
490
+ expect(getClarification).toHaveBeenCalled()
491
+ })
492
+ await vi.advanceTimersByTimeAsync(0)
493
+
494
+ expect(shadow.root.querySelector('.mtb-clarify-thinking')).toBeNull()
495
+ expect(shadow.root.querySelector('.mtb-clarify-ai')?.textContent).toBe('What broke?')
496
+
497
+ card.destroy()
498
+ shadow.destroy()
499
+ })
500
+
398
501
  it('stops polling on destroy', async () => {
399
502
  const getClarification = vi.fn().mockResolvedValue({
400
503
  status: 'pending',
@@ -76,6 +76,8 @@ export class ClarifyCard {
76
76
  })
77
77
 
78
78
  this.inputEl.addEventListener('keydown', (e: KeyboardEvent) => {
79
+ // keyCode 229: IME composition fallback for browsers without isComposing
80
+ if (e.isComposing || e.keyCode === 229) return
79
81
  if (e.key === 'Enter') {
80
82
  e.preventDefault()
81
83
  this.sendMessage(m)
@@ -90,7 +92,7 @@ export class ClarifyCard {
90
92
  try {
91
93
  const res = await this.apiClient.startClarification(this.feedbackId)
92
94
  if (this.destroyed) return
93
- this.renderMessages(res.messages)
95
+ this.renderConversation(res.messages, res.done)
94
96
  if (res.done) {
95
97
  this.handleDone()
96
98
  } else {
@@ -118,7 +120,7 @@ export class ClarifyCard {
118
120
  try {
119
121
  const res = await this.apiClient.startClarification(this.feedbackId, text)
120
122
  if (this.destroyed) return
121
- this.renderMessages(res.messages)
123
+ this.renderConversation(res.messages, res.done)
122
124
  if (res.done) {
123
125
  this.handleDone()
124
126
  } else {
@@ -151,7 +153,7 @@ export class ClarifyCard {
151
153
  try {
152
154
  const res = await this.apiClient.getClarification(this.feedbackId)
153
155
  if (this.destroyed) return
154
- this.renderMessages(res.messages)
156
+ this.renderConversation(res.messages, res.done)
155
157
  if (res.done) {
156
158
  this.stopPolling()
157
159
  this.handleDone()
@@ -170,6 +172,12 @@ export class ClarifyCard {
170
172
  }, DONE_DELAY_MS)
171
173
  }
172
174
 
175
+ private renderConversation(messages: ClarifyMessage[], done: boolean): void {
176
+ this.renderMessages(messages)
177
+ const last = messages[messages.length - 1]
178
+ if (!done && (!last || last.role === 'user')) this.showThinking()
179
+ }
180
+
173
181
  private renderMessages(messages: ClarifyMessage[]): void {
174
182
  this.messagesEl.innerHTML = ''
175
183
  for (const msg of messages) {
package/src/types.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  export interface MakeThisBetterConfig {
2
2
  projectKey: string
3
- entryMode?: 'button' | 'pet'
3
+ entryMode?: 'button'
4
4
  locale?: string
5
5
  position?: 'left' | 'right'
6
6
  theme?: 'light' | 'dark' | 'auto'
@@ -52,6 +52,7 @@ export interface FeedbackResponse {
52
52
  id: string
53
53
  status: string
54
54
  project_id: string
55
+ ai_clarify_available?: boolean
55
56
  }
56
57
 
57
58
  export interface ClarifyMessage {
@@ -61,4 +61,41 @@ describe('MakeThisBetter widget flow', () => {
61
61
  expect(shadow.querySelector('.mtb-success-title')?.textContent).toBe('Thanks!')
62
62
  }, { timeout: 5000 })
63
63
  })
64
+
65
+ it('skips the AI clarify chat when ai_clarify_available is false', async () => {
66
+ const target = document.createElement('button')
67
+ target.id = 'export-btn'
68
+ target.textContent = 'Export PDF'
69
+ document.body.appendChild(target)
70
+
71
+ document.elementsFromPoint = vi.fn(() => [target])
72
+
73
+ const fetchSpy = vi.spyOn(globalThis, 'fetch').mockImplementation(async () => {
74
+ return new Response(JSON.stringify({ id: 'fb_1', status: 'open', project_id: 'proj_test', ai_clarify_available: false }), { status: 201 })
75
+ })
76
+
77
+ MakeThisBetter.init({
78
+ projectKey: 'proj_test',
79
+ apiUrl: 'https://api.example.com/api/v1',
80
+ })
81
+
82
+ const shadow = document.getElementById('mtb-widget-host')!.shadowRoot!
83
+ shadow.querySelector<HTMLButtonElement>('.mtb-tab')!.click()
84
+
85
+ const overlay = shadow.querySelector<HTMLDivElement>('.mtb-overlay')!
86
+ overlay.dispatchEvent(new MouseEvent('mousedown', { clientX: 20, clientY: 30, bubbles: true }))
87
+ overlay.dispatchEvent(new MouseEvent('mouseup', { clientX: 20, clientY: 30, bubbles: true }))
88
+
89
+ const textarea = shadow.querySelector<HTMLTextAreaElement>('.mtb-textarea')!
90
+ textarea.value = 'Export is broken'
91
+ textarea.dispatchEvent(new Event('input'))
92
+ shadow.querySelector<HTMLButtonElement>('.mtb-submit-btn')!.click()
93
+
94
+ await vi.waitFor(() => {
95
+ expect(shadow.querySelector('.mtb-success-title')?.textContent).toBe('Thanks!')
96
+ }, { timeout: 5000 })
97
+
98
+ expect(shadow.querySelector('.mtb-clarify')).toBeNull()
99
+ expect(fetchSpy).toHaveBeenCalledTimes(1)
100
+ })
64
101
  })
@@ -1,6 +1,5 @@
1
1
  import { ShadowContainer } from './shadow'
2
2
  import { FeedbackTab } from './tab'
3
- import { PetEntry } from './pet'
4
3
  import { FrustrationPromptCard } from './frustration-prompt'
5
4
  import { AnnotationToolbar } from '../annotate/toolbar'
6
5
  import type { ToolbarMode } from '../annotate/toolbar'
@@ -24,7 +23,6 @@ export class WidgetController {
24
23
  private config: MakeThisBetterConfig
25
24
  private shadow!: ShadowContainer
26
25
  private tab: FeedbackTab | null = null
27
- private pet: PetEntry | null = null
28
26
  private toolbar: AnnotationToolbar | null = null
29
27
  private session: AnnotationSession | null = null
30
28
  private popup: CommentPopup | null = null
@@ -56,17 +54,13 @@ export class WidgetController {
56
54
  private buildUI(): void {
57
55
  this.shadow = new ShadowContainer(this.config.theme ?? 'auto')
58
56
 
59
- if ((this.config.entryMode ?? 'button') === 'pet') {
60
- this.pet = new PetEntry(this.shadow, this.messages, () => this.handleTabClick())
61
- } else {
62
- this.tab = new FeedbackTab(
63
- this.shadow,
64
- this.messages,
65
- this.config.position ?? 'right',
66
- () => this.handleTabClick(),
67
- this.config.tabText,
68
- )
69
- }
57
+ this.tab = new FeedbackTab(
58
+ this.shadow,
59
+ this.messages,
60
+ this.config.position ?? 'right',
61
+ () => this.handleTabClick(),
62
+ this.config.tabText,
63
+ )
70
64
  }
71
65
 
72
66
  private handleTabClick(): void {
@@ -98,7 +92,6 @@ export class WidgetController {
98
92
  private enterAnnotationMode(): void {
99
93
  this.mode = 'annotating'
100
94
  this.tab?.setActive(true)
101
- this.pet?.setActive(true)
102
95
 
103
96
  this.toolbar = new AnnotationToolbar(
104
97
  this.shadow,
@@ -183,7 +176,11 @@ export class WidgetController {
183
176
  this.session = null
184
177
  this.recordingManager?.destroy()
185
178
  this.recordingManager = null
186
- this.showClarify(feedback.id, feedback.project_id)
179
+ if (feedback.ai_clarify_available === false) {
180
+ this.showSuccess(feedback.project_id)
181
+ } else {
182
+ this.showClarify(feedback.id, feedback.project_id)
183
+ }
187
184
  } catch {
188
185
  this.popup?.setLoading(false)
189
186
  this.popup?.setError(this.messages.error.submit)
@@ -193,7 +190,6 @@ export class WidgetController {
193
190
  private showClarify(feedbackId: string, projectId: string): void {
194
191
  this.mode = 'idle'
195
192
  this.tab?.setActive(false)
196
- this.pet?.setActive(false)
197
193
  this.clarify = new ClarifyCard(this.shadow, {
198
194
  feedbackId,
199
195
  apiClient: this.apiClient,
@@ -209,7 +205,6 @@ export class WidgetController {
209
205
  private showSuccess(projectId: string): void {
210
206
  this.mode = 'idle'
211
207
  this.tab?.setActive(false)
212
- this.pet?.setActive(false)
213
208
  const baseUrl = this.config.apiUrl
214
209
  ? this.config.apiUrl.replace(/\/api\/v1\/?$/, '')
215
210
  : 'https://makethisbetter.dev'
@@ -227,7 +222,6 @@ export class WidgetController {
227
222
  private exitAll(): void {
228
223
  this.mode = 'idle'
229
224
  this.tab?.setActive(false)
230
- this.pet?.setActive(false)
231
225
  this.toolbar?.destroy()
232
226
  this.toolbar = null
233
227
  this.session?.destroy()
@@ -249,7 +243,6 @@ export class WidgetController {
249
243
  this.clarify?.destroy()
250
244
  this.success?.destroy()
251
245
  this.tab?.destroy()
252
- this.pet?.destroy()
253
246
  this.recordingManager?.destroy()
254
247
  this.shadow.destroy()
255
248
  }
@@ -1,56 +1,58 @@
1
1
  import { afterEach, describe, expect, it, vi } from 'vitest'
2
+ import { PetEntry } from './pet'
3
+ import { ShadowContainer } from './shadow'
4
+ import { getMessages } from '../i18n'
2
5
  import { MakeThisBetter } from '../index'
3
6
 
4
7
  vi.mock('html-to-image', () => ({
5
8
  toPng: vi.fn(async () => 'data:image/png;base64,AA=='),
6
9
  }))
7
10
 
8
- describe('Pet mode', () => {
11
+ // PetEntry is kept in the repo for the phase-2 pet mode but is not wired into
12
+ // the widget build, so it is unit-tested directly instead of via init().
13
+ describe('PetEntry', () => {
14
+ let shadow: ShadowContainer
15
+
9
16
  afterEach(() => {
10
- MakeThisBetter.destroy()
11
- vi.restoreAllMocks()
17
+ shadow?.destroy()
12
18
  document.body.innerHTML = ''
13
19
  })
14
20
 
15
- it('renders pet entry instead of tab when entryMode is pet', () => {
16
- MakeThisBetter.init({
17
- projectKey: 'proj_test',
18
- entryMode: 'pet',
19
- apiUrl: 'https://api.example.com/api/v1',
20
- })
21
+ function makePet(onClick: () => void = () => {}) {
22
+ shadow = new ShadowContainer('light')
23
+ return new PetEntry(shadow, getMessages('en'), onClick)
24
+ }
21
25
 
22
- const shadow = document.getElementById('mtb-widget-host')!.shadowRoot!
23
- expect(shadow.querySelector('.mtb-pet')).toBeTruthy()
24
- expect(shadow.querySelector('.mtb-tab')).toBeNull()
25
- })
26
+ function shadowRoot() {
27
+ return document.getElementById('mtb-widget-host')!.shadowRoot!
28
+ }
26
29
 
27
- it('shows speech bubble on the pet', () => {
28
- MakeThisBetter.init({
29
- projectKey: 'proj_test',
30
- entryMode: 'pet',
31
- apiUrl: 'https://api.example.com/api/v1',
32
- })
30
+ it('renders the pet element with a speech bubble', () => {
31
+ makePet()
33
32
 
34
- const shadow = document.getElementById('mtb-widget-host')!.shadowRoot!
35
- const bubble = shadow.querySelector('.mtb-pet-bubble')
33
+ expect(shadowRoot().querySelector('.mtb-pet')).toBeTruthy()
34
+ const bubble = shadowRoot().querySelector('.mtb-pet-bubble')
36
35
  expect(bubble).toBeTruthy()
37
36
  expect(bubble!.textContent).toBe('Need help?')
38
37
  })
39
38
 
40
- it('opens annotation mode on pet click', () => {
41
- MakeThisBetter.init({
42
- projectKey: 'proj_test',
43
- entryMode: 'pet',
44
- apiUrl: 'https://api.example.com/api/v1',
45
- })
39
+ it('invokes the click callback on click', () => {
40
+ const onClick = vi.fn()
41
+ makePet(onClick)
46
42
 
47
- const shadow = document.getElementById('mtb-widget-host')!.shadowRoot!
48
- shadow.querySelector<HTMLDivElement>('.mtb-pet')!.click()
49
- expect(shadow.querySelector('.mtb-overlay')).toBeTruthy()
50
- expect(shadow.querySelector('.mtb-toolbar')).toBeTruthy()
43
+ shadowRoot().querySelector<HTMLDivElement>('.mtb-pet')!.click()
44
+ expect(onClick).toHaveBeenCalledOnce()
45
+ })
46
+ })
47
+
48
+ describe('Widget entry', () => {
49
+ afterEach(() => {
50
+ MakeThisBetter.destroy()
51
+ vi.restoreAllMocks()
52
+ document.body.innerHTML = ''
51
53
  })
52
54
 
53
- it('renders tab by default (no entryMode)', () => {
55
+ it('renders the tab entry and never the pet', () => {
54
56
  MakeThisBetter.init({
55
57
  projectKey: 'proj_test',
56
58
  apiUrl: 'https://api.example.com/api/v1',