makethisbetter 1.9.0 → 1.9.2

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.
@@ -338,7 +338,7 @@
338
338
  z-index: 2147483645;
339
339
  display: flex;
340
340
  flex-direction: column;
341
- width: 320px;
341
+ width: min(320px, calc(100vw - 56px));
342
342
  background: #fff;
343
343
  border: 1px solid rgba(0, 0, 0, 0.1);
344
344
  border-radius: 16px;
@@ -999,7 +999,7 @@
999
999
  .mtb-clarify {
1000
1000
  position: fixed;
1001
1001
  z-index: 2147483645;
1002
- width: 346px;
1002
+ width: min(346px, calc(100vw - 56px));
1003
1003
  background: #fff;
1004
1004
  border: 1px solid rgba(0, 0, 0, 0.1);
1005
1005
  border-radius: 16px;
@@ -1011,18 +1011,23 @@
1011
1011
  overflow: hidden;
1012
1012
  }
1013
1013
 
1014
+ .mtb-clarify-continuation {
1015
+ width: min(320px, calc(100vw - 56px));
1016
+ animation: none;
1017
+ }
1018
+
1014
1019
  .mtb-clarify-header {
1015
- padding: 11px 13px 11px 14px;
1020
+ padding: 8px 10px;
1016
1021
  display: flex;
1017
1022
  align-items: center;
1018
- gap: 9px;
1023
+ gap: 7px;
1019
1024
  border-bottom: 1px solid rgba(0, 0, 0, 0.06);
1020
1025
  }
1021
1026
 
1022
1027
  .mtb-clarify-icon {
1023
- width: 24px;
1024
- height: 24px;
1025
- border-radius: 7px;
1028
+ width: 20px;
1029
+ height: 20px;
1030
+ border-radius: 6px;
1026
1031
  background: linear-gradient(140deg, #10b981, #059669);
1027
1032
  box-shadow: 0 1px 3px rgba(5, 150, 105, 0.4);
1028
1033
  display: flex;
@@ -1037,14 +1042,14 @@
1037
1042
  }
1038
1043
 
1039
1044
  .mtb-clarify-title {
1040
- font-size: 12.5px;
1045
+ font-size: 12px;
1041
1046
  font-weight: 650;
1042
1047
  line-height: 1.2;
1043
1048
  color: #191a1c;
1044
1049
  }
1045
1050
 
1046
1051
  .mtb-clarify-subtitle {
1047
- font-size: 10px;
1052
+ font-size: 9.5px;
1048
1053
  color: rgba(0, 0, 0, 0.42);
1049
1054
  }
1050
1055
 
@@ -1064,19 +1069,18 @@
1064
1069
 
1065
1070
  .mtb-clarify-messages {
1066
1071
  flex: 1;
1067
- max-height: 250px;
1068
1072
  overflow-y: auto;
1069
- padding: 13px 13px 5px;
1073
+ padding: 8px 10px 4px;
1070
1074
  display: flex;
1071
1075
  flex-direction: column;
1072
- gap: 9px;
1076
+ gap: 6px;
1073
1077
  }
1074
1078
 
1075
1079
  .mtb-clarify-bubble {
1076
1080
  max-width: 82%;
1077
- padding: 9px 12px;
1078
- font-size: 12.5px;
1079
- line-height: 1.5;
1081
+ padding: 6px 10px;
1082
+ font-size: 12px;
1083
+ line-height: 1.4;
1080
1084
  white-space: pre-wrap;
1081
1085
  word-break: break-word;
1082
1086
  }
@@ -1086,7 +1090,7 @@
1086
1090
  background: #ffffff;
1087
1091
  color: #191a1c;
1088
1092
  border: 1px solid rgba(0, 0, 0, 0.09);
1089
- border-radius: 13px 13px 13px 4px;
1093
+ border-radius: 11px 11px 11px 3px;
1090
1094
  }
1091
1095
 
1092
1096
  .mtb-clarify-user {
@@ -1094,14 +1098,14 @@
1094
1098
  background: #059669;
1095
1099
  color: #fff;
1096
1100
  border: 1px solid #059669;
1097
- border-radius: 13px 13px 4px 13px;
1101
+ border-radius: 11px 11px 3px 11px;
1098
1102
  }
1099
1103
 
1100
1104
  .mtb-clarify-thinking {
1101
1105
  display: flex;
1102
1106
  align-items: center;
1103
1107
  gap: 4px;
1104
- padding: 11px 14px;
1108
+ padding: 8px 12px;
1105
1109
  }
1106
1110
 
1107
1111
  .mtb-clarify-thinking .mtb-dot {
@@ -1111,20 +1115,20 @@
1111
1115
  }
1112
1116
 
1113
1117
  .mtb-clarify-footer {
1114
- padding: 3px 13px 13px;
1118
+ padding: 3px 10px 10px;
1115
1119
  display: flex;
1116
1120
  flex-direction: column;
1117
1121
  }
1118
1122
 
1119
1123
  .mtb-clarify-input {
1120
1124
  width: 100%;
1121
- height: 52px;
1122
- padding: 9px 11px;
1125
+ height: 40px;
1126
+ padding: 7px 10px;
1123
1127
  border: 1px solid rgba(0, 0, 0, 0.12);
1124
- border-radius: 10px;
1128
+ border-radius: 8px;
1125
1129
  font-family: inherit;
1126
- font-size: 13px;
1127
- line-height: 1.5;
1130
+ font-size: 12px;
1131
+ line-height: 1.4;
1128
1132
  color: #191a1c;
1129
1133
  outline: none;
1130
1134
  background: #fafafa;
@@ -1145,20 +1149,20 @@
1145
1149
 
1146
1150
  .mtb-clarify-actions {
1147
1151
  display: flex;
1148
- gap: 8px;
1149
- margin-top: 9px;
1152
+ gap: 6px;
1153
+ margin-top: 6px;
1150
1154
  }
1151
1155
 
1152
1156
  .mtb-clarify-skip {
1153
1157
  flex: 0 0 auto;
1154
- height: 34px;
1155
- padding: 0 14px;
1158
+ height: 30px;
1159
+ padding: 0 12px;
1156
1160
  border: 1px solid rgba(0, 0, 0, 0.12);
1157
- border-radius: 10px;
1161
+ border-radius: 8px;
1158
1162
  background: #fff;
1159
1163
  cursor: pointer;
1160
1164
  font-family: inherit;
1161
- font-size: 12.5px;
1165
+ font-size: 12px;
1162
1166
  font-weight: 550;
1163
1167
  color: rgba(0, 0, 0, 0.55);
1164
1168
  white-space: nowrap;
@@ -1171,13 +1175,40 @@
1171
1175
 
1172
1176
  .mtb-clarify-send {
1173
1177
  flex: 1;
1178
+ height: 30px;
1179
+ display: inline-flex;
1180
+ align-items: center;
1181
+ justify-content: center;
1182
+ gap: 5px;
1183
+ border: none;
1184
+ border-radius: 8px;
1185
+ background: #059669;
1186
+ color: #fff;
1187
+ cursor: pointer;
1188
+ font-family: inherit;
1189
+ font-size: 12px;
1190
+ font-weight: 600;
1191
+ transition: background 0.12s;
1192
+ }
1193
+
1194
+ .mtb-clarify-send:not(:disabled):hover {
1195
+ background: #047857;
1196
+ }
1197
+
1198
+ .mtb-clarify-send:disabled {
1199
+ background: rgba(0, 0, 0, 0.15);
1200
+ cursor: not-allowed;
1201
+ }
1202
+
1203
+ .mtb-clarify-send-feedback {
1204
+ width: 100%;
1174
1205
  height: 34px;
1175
1206
  display: inline-flex;
1176
1207
  align-items: center;
1177
1208
  justify-content: center;
1178
1209
  gap: 6px;
1179
1210
  border: none;
1180
- border-radius: 10px;
1211
+ border-radius: 9px;
1181
1212
  background: #059669;
1182
1213
  color: #fff;
1183
1214
  cursor: pointer;
@@ -1187,11 +1218,11 @@
1187
1218
  transition: background 0.12s;
1188
1219
  }
1189
1220
 
1190
- .mtb-clarify-send:not(:disabled):hover {
1221
+ .mtb-clarify-send-feedback:not(:disabled):hover {
1191
1222
  background: #047857;
1192
1223
  }
1193
1224
 
1194
- .mtb-clarify-send:disabled {
1225
+ .mtb-clarify-send-feedback:disabled {
1195
1226
  background: rgba(0, 0, 0, 0.15);
1196
1227
  cursor: not-allowed;
1197
1228
  }
@@ -1503,4 +1534,3 @@
1503
1534
  font-size: 11px;
1504
1535
  color: rgba(0, 0, 0, 0.42);
1505
1536
  }
1506
-
@@ -36,7 +36,10 @@ function sessionFlowResponse(
36
36
  }
37
37
 
38
38
  async function confirmSubmission(shadow: ShadowRoot): Promise<void> {
39
- // ClarifyCard auto-finalizes after done, no manual confirmation needed
39
+ await vi.waitFor(() => {
40
+ expect(shadow.querySelector('.mtb-clarify-send-feedback')).not.toBeNull()
41
+ })
42
+ shadow.querySelector<HTMLButtonElement>('.mtb-clarify-send-feedback')!.click()
40
43
  await vi.waitFor(() => {
41
44
  expect(shadow.querySelector('.mtb-success')).not.toBeNull()
42
45
  })
@@ -78,9 +81,10 @@ describe('MakeThisBetter widget flow', () => {
78
81
  }), { status: 201 })
79
82
  }
80
83
  if (href.endsWith('/clarification')) {
81
- return new Response(JSON.stringify({
82
- clarification: { status: 'completed', messages: [], done: true },
83
- }), { status: 200, headers: { 'Content-Type': 'application/json' } })
84
+ return new Response(
85
+ 'event: done\ndata: {"messages":[{"role":"assistant","content":"All clear, thanks!"}],"done":true}\n\n',
86
+ { status: 200, headers: { 'Content-Type': 'text/event-stream' } },
87
+ )
84
88
  }
85
89
  if (init?.body instanceof FormData) capturedBody = init.body
86
90
  return new Response(JSON.stringify({
@@ -102,6 +106,8 @@ describe('MakeThisBetter widget flow', () => {
102
106
  overlay.dispatchEvent(new MouseEvent('mouseup', { clientX: 20, clientY: 30, bubbles: true }))
103
107
 
104
108
  const textarea = shadow.querySelector<HTMLTextAreaElement>('.mtb-textarea')!
109
+ const feedbackPopup = shadow.querySelector<HTMLDivElement>('.mtb-popup')!
110
+ const popupPosition = { left: feedbackPopup.style.left, top: feedbackPopup.style.top }
105
111
  textarea.value = 'Export is broken'
106
112
  textarea.dispatchEvent(new Event('input'))
107
113
  shadow.querySelector<HTMLButtonElement>('.mtb-submit-btn')!.click()
@@ -119,7 +125,19 @@ describe('MakeThisBetter widget flow', () => {
119
125
  text: 'Export PDF',
120
126
  name: 'Export PDF',
121
127
  }))
122
- // ClarifyCard auto-finalizes after done, wait for the success card
128
+ await vi.waitFor(() => {
129
+ expect(shadow.querySelector('.mtb-clarify-send-feedback')).not.toBeNull()
130
+ }, { timeout: 5000 })
131
+ const clarification = shadow.querySelector<HTMLDivElement>('.mtb-clarify')!
132
+ expect(clarification).toBe(feedbackPopup)
133
+ expect(clarification.classList.contains('mtb-clarify-continuation')).toBe(true)
134
+ expect({ left: clarification.style.left, top: clarification.style.top }).toEqual(popupPosition)
135
+ expect(shadow.querySelector('.mtb-popup')).toBeNull()
136
+ expect(shadow.querySelector('.mtb-clarify-ai')?.textContent).toBe('All clear, thanks!')
137
+ expect(fetchSpy.mock.calls.some(([url]) => String(url).endsWith('/feedback'))).toBe(false)
138
+
139
+ shadow.querySelector<HTMLButtonElement>('.mtb-clarify-send-feedback')!.click()
140
+
123
141
  await vi.waitFor(() => {
124
142
  expect(shadow.querySelector('.mtb-success-title')?.textContent).toBe('Sent — thanks!')
125
143
  }, { timeout: 5000 })
@@ -255,8 +273,13 @@ describe('MakeThisBetter widget flow', () => {
255
273
  textarea.dispatchEvent(new Event('input'))
256
274
  shadow.querySelector<HTMLButtonElement>('.mtb-submit-btn')!.click()
257
275
 
258
- // ClarifyCard auto-finalizes after done=true, which triggers the /feedback
259
- // POST. That POST hangs on finalizeResponse, so success never appears.
276
+ await vi.waitFor(() => {
277
+ expect(shadow.querySelector('.mtb-clarify-send-feedback')).not.toBeNull()
278
+ })
279
+ shadow.querySelector<HTMLButtonElement>('.mtb-clarify-send-feedback')!.click()
280
+
281
+ // The explicit confirmation triggers the /feedback POST. That POST hangs
282
+ // on finalizeResponse, so Exit can still invalidate the pending result.
260
283
  await vi.waitFor(() => {
261
284
  expect(fetchSpy.mock.calls.some(([url]) => String(url).endsWith('/feedback'))).toBe(true)
262
285
  })
@@ -713,7 +736,7 @@ describe('MakeThisBetter widget flow', () => {
713
736
  })
714
737
  })
715
738
 
716
- it('auto-finalizes when AI clarification is unavailable', async () => {
739
+ it('requires Send feedback when AI clarification is unavailable', async () => {
717
740
  const target = document.createElement('button')
718
741
  target.id = 'export-btn'
719
742
  target.textContent = 'Export PDF'
@@ -742,7 +765,13 @@ describe('MakeThisBetter widget flow', () => {
742
765
  textarea.dispatchEvent(new Event('input'))
743
766
  shadow.querySelector<HTMLButtonElement>('.mtb-submit-btn')!.click()
744
767
 
745
- // ClarifyCard auto-finalizes, so the success card should appear directly
768
+ await vi.waitFor(() => {
769
+ expect(shadow.querySelector('.mtb-clarify-send-feedback')).not.toBeNull()
770
+ }, { timeout: 5000 })
771
+ expect(fetchSpy.mock.calls.some(([url]) => String(url).endsWith('/feedback'))).toBe(false)
772
+
773
+ shadow.querySelector<HTMLButtonElement>('.mtb-clarify-send-feedback')!.click()
774
+
746
775
  await vi.waitFor(() => {
747
776
  expect(shadow.querySelector('.mtb-success-title')?.textContent).toBe("Thanks! We've received your feedback.")
748
777
  }, { timeout: 5000 })
@@ -279,6 +279,7 @@ export class WidgetController {
279
279
  onSubmit: (description) => this.handleSubmit(description),
280
280
  onClose: () => this.exitAll(),
281
281
  onMyFeedback: this.buildMyFeedbackHandler(),
282
+ position: this.config.position ?? 'right',
282
283
  })
283
284
  this.submittingUi = this.popup
284
285
  }
@@ -342,10 +343,11 @@ export class WidgetController {
342
343
  submissionAttempt: number,
343
344
  ): Promise<void> {
344
345
  if (!submission.ai_clarify_available) {
345
- this.teardownSubmittingUi()
346
- this.mode = 'clarifying'
347
- this.tab?.setActive(true)
348
- await this.finalizeSubmission()
346
+ const element = this.teardownSubmittingUi(true)
347
+ this.showClarify(submission, pos, {
348
+ element,
349
+ preloadedResult: { messages: [], done: true },
350
+ })
349
351
  return
350
352
  }
351
353
 
@@ -363,24 +365,19 @@ export class WidgetController {
363
365
 
364
366
  if (submissionAttempt !== this.submissionAttempt) return
365
367
 
366
- this.teardownSubmittingUi()
368
+ const element = this.teardownSubmittingUi(true)
367
369
 
368
370
  if (result) {
369
- if (result.done) {
370
- this.mode = 'clarifying'
371
- this.tab?.setActive(true)
372
- await this.finalizeSubmission()
373
- } else {
374
- this.showClarify(submission, pos, { preloadedResult: result })
375
- }
371
+ this.showClarify(submission, pos, { element, preloadedResult: result })
376
372
  } else {
377
- this.showClarify(submission, pos, { pendingClarification: clarifyPromise })
373
+ this.showClarify(submission, pos, { element, pendingClarification: clarifyPromise })
378
374
  }
379
375
  }
380
376
 
381
- private teardownSubmittingUi(): void {
377
+ private teardownSubmittingUi(preservePopup = false): HTMLDivElement | undefined {
378
+ const popupElement = preservePopup ? this.popup?.releaseElement() : undefined
382
379
  this.teardownDraw()
383
- this.popup?.destroy()
380
+ if (!popupElement) this.popup?.destroy()
384
381
  this.popup = null
385
382
  this.dim?.destroy()
386
383
  this.dim = null
@@ -390,12 +387,14 @@ export class WidgetController {
390
387
  this.recordingManager?.destroy()
391
388
  this.recordingManager = null
392
389
  this.submittingUi = null
390
+ return popupElement
393
391
  }
394
392
 
395
393
  private showClarify(
396
394
  submission: SubmissionSessionResponse,
397
395
  pos: { x: number; y: number } | null,
398
396
  speculative?: {
397
+ element?: HTMLDivElement
399
398
  preloadedResult?: ClarifyStreamResult
400
399
  pendingClarification?: Promise<ClarifyStreamResult | null>
401
400
  },
@@ -407,6 +406,8 @@ export class WidgetController {
407
406
  submissionToken: submission.token,
408
407
  apiClient: this.apiClient,
409
408
  messages: this.messages,
409
+ element: speculative?.element,
410
+ position: this.config.position ?? 'right',
410
411
  x: pos?.x,
411
412
  y: pos?.y,
412
413
  onFinalize: () => this.finalizeSubmission(),