@bbearai/react 0.1.7 → 0.1.8

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.
package/dist/index.mjs CHANGED
@@ -20,6 +20,14 @@ var BugBearContext = createContext({
20
20
  },
21
21
  updateTesterProfile: async () => ({ success: false }),
22
22
  refreshTesterInfo: async () => {
23
+ },
24
+ // QA Sessions
25
+ activeSession: null,
26
+ sessionFindings: [],
27
+ startSession: async () => ({ success: false }),
28
+ endSession: async () => ({ success: false }),
29
+ addFinding: async () => ({ success: false }),
30
+ refreshSession: async () => {
23
31
  }
24
32
  });
25
33
  function useBugBear() {
@@ -33,11 +41,52 @@ function BugBearProvider({ config, children, enabled = true }) {
33
41
  const [assignments, setAssignments] = useState([]);
34
42
  const [isLoading, setIsLoading] = useState(true);
35
43
  const hasInitialized = useRef(false);
44
+ const [activeSession, setActiveSession] = useState(null);
45
+ const [sessionFindings, setSessionFindings] = useState([]);
36
46
  const refreshAssignments = useCallback(async () => {
37
47
  if (!client) return;
38
48
  const newAssignments = await client.getAssignedTests();
39
49
  setAssignments(newAssignments);
40
50
  }, [client]);
51
+ const refreshSession = useCallback(async () => {
52
+ if (!client) return;
53
+ const session = await client.getActiveSession();
54
+ setActiveSession(session);
55
+ if (session) {
56
+ const findings = await client.getSessionFindings(session.id);
57
+ setSessionFindings(findings);
58
+ } else {
59
+ setSessionFindings([]);
60
+ }
61
+ }, [client]);
62
+ const startSession = useCallback(async (options = {}) => {
63
+ if (!client) return { success: false, error: "Client not initialized" };
64
+ const result = await client.startSession(options);
65
+ if (result.success && result.session) {
66
+ setActiveSession(result.session);
67
+ setSessionFindings([]);
68
+ }
69
+ return { success: result.success, error: result.error };
70
+ }, [client]);
71
+ const endSession = useCallback(async (notes) => {
72
+ if (!client || !activeSession) return { success: false, error: "No active session" };
73
+ const routesCovered = client.getNavigationHistory();
74
+ const result = await client.endSession(activeSession.id, { notes, routesCovered });
75
+ if (result.success) {
76
+ setActiveSession(null);
77
+ setSessionFindings([]);
78
+ }
79
+ return { success: result.success, error: result.error };
80
+ }, [client, activeSession]);
81
+ const addFinding = useCallback(async (options) => {
82
+ if (!client || !activeSession) return { success: false, error: "No active session" };
83
+ const result = await client.addFinding(activeSession.id, options);
84
+ if (result.success && result.finding) {
85
+ setSessionFindings((prev) => [...prev, result.finding]);
86
+ setActiveSession((prev) => prev ? { ...prev, findingsCount: prev.findingsCount + 1 } : null);
87
+ }
88
+ return result;
89
+ }, [client, activeSession]);
41
90
  const initializeBugBear = useCallback(async (bugBearClient) => {
42
91
  setIsLoading(true);
43
92
  try {
@@ -50,8 +99,16 @@ function BugBearProvider({ config, children, enabled = true }) {
50
99
  setTesterInfo(info);
51
100
  setIsTester(!!info);
52
101
  if (info && qaEnabled) {
53
- const newAssignments = await bugBearClient.getAssignedTests();
102
+ const [newAssignments, session] = await Promise.all([
103
+ bugBearClient.getAssignedTests(),
104
+ bugBearClient.getActiveSession()
105
+ ]);
54
106
  setAssignments(newAssignments);
107
+ setActiveSession(session);
108
+ if (session) {
109
+ const findings = await bugBearClient.getSessionFindings(session.id);
110
+ setSessionFindings(findings);
111
+ }
55
112
  }
56
113
  } catch (err) {
57
114
  console.error("BugBear: Init error", err);
@@ -106,7 +163,14 @@ function BugBearProvider({ config, children, enabled = true }) {
106
163
  onNavigate: config.onNavigate,
107
164
  refreshTesterStatus,
108
165
  updateTesterProfile,
109
- refreshTesterInfo
166
+ refreshTesterInfo,
167
+ // QA Sessions
168
+ activeSession,
169
+ sessionFindings,
170
+ startSession,
171
+ endSession,
172
+ addFinding,
173
+ refreshSession
110
174
  },
111
175
  children
112
176
  }
@@ -185,7 +249,7 @@ function BugBearPanel({
185
249
  defaultCollapsed = false,
186
250
  draggable = true
187
251
  }) {
188
- const { client, shouldShowWidget, testerInfo, assignments, currentAssignment, refreshAssignments, isLoading, onNavigate, updateTesterProfile, refreshTesterInfo } = useBugBear();
252
+ const { client, shouldShowWidget, testerInfo, assignments, currentAssignment, refreshAssignments, isLoading, onNavigate, updateTesterProfile, refreshTesterInfo, activeSession, sessionFindings, startSession, endSession, addFinding } = useBugBear();
189
253
  const [collapsed, setCollapsed] = useState2(defaultCollapsed);
190
254
  const [activeTab, setActiveTab] = useState2("tests");
191
255
  const [showSteps, setShowSteps] = useState2(false);
@@ -202,6 +266,16 @@ function BugBearPanel({
202
266
  const [submitting, setSubmitting] = useState2(false);
203
267
  const [submitted, setSubmitted] = useState2(false);
204
268
  const [justPassed, setJustPassed] = useState2(false);
269
+ const [showFeedbackPrompt, setShowFeedbackPrompt] = useState2(false);
270
+ const [pendingFeedbackStatus, setPendingFeedbackStatus] = useState2(null);
271
+ const [feedbackRating, setFeedbackRating] = useState2(5);
272
+ const [feedbackNote, setFeedbackNote] = useState2("");
273
+ const [feedbackFlags, setFeedbackFlags] = useState2({
274
+ isOutdated: false,
275
+ needsMoreDetail: false,
276
+ stepsUnclear: false,
277
+ expectedResultUnclear: false
278
+ });
205
279
  const [criteriaResults, setCriteriaResults] = useState2({});
206
280
  const [profileEditing, setProfileEditing] = useState2(false);
207
281
  const [profileName, setProfileName] = useState2("");
@@ -211,6 +285,22 @@ function BugBearPanel({
211
285
  const [savingProfile, setSavingProfile] = useState2(false);
212
286
  const [profileSaved, setProfileSaved] = useState2(false);
213
287
  const [showProfileOverlay, setShowProfileOverlay] = useState2(false);
288
+ const [startingSession, setStartingSession] = useState2(false);
289
+ const [sessionFocusArea, setSessionFocusArea] = useState2("");
290
+ const [sessionPlatform, setSessionPlatform] = useState2("web");
291
+ const [suggestedRoutes, setSuggestedRoutes] = useState2([]);
292
+ const [focusAreas, setFocusAreas] = useState2([]);
293
+ const [showAddFinding, setShowAddFinding] = useState2(false);
294
+ const [findingType, setFindingType] = useState2("bug");
295
+ const [findingTitle, setFindingTitle] = useState2("");
296
+ const [findingDescription, setFindingDescription] = useState2("");
297
+ const [findingSeverity, setFindingSeverity] = useState2("medium");
298
+ const [addingFinding, setAddingFinding] = useState2(false);
299
+ const [endingSession, setEndingSession] = useState2(false);
300
+ const [sessionNotes, setSessionNotes] = useState2("");
301
+ const [showEndConfirm, setShowEndConfirm] = useState2(false);
302
+ const [sessionElapsedTime, setSessionElapsedTime] = useState2(0);
303
+ const [assignmentElapsedTime, setAssignmentElapsedTime] = useState2(0);
214
304
  useEffect2(() => {
215
305
  if (typeof window === "undefined") return;
216
306
  try {
@@ -245,6 +335,84 @@ function BugBearPanel({
245
335
  setCriteriaResults({});
246
336
  setShowSteps(false);
247
337
  }, [displayedAssignment?.id]);
338
+ useEffect2(() => {
339
+ if (!activeSession) {
340
+ setSessionElapsedTime(0);
341
+ return;
342
+ }
343
+ const startTime = new Date(activeSession.startedAt).getTime();
344
+ const now = Date.now();
345
+ setSessionElapsedTime(Math.floor((now - startTime) / 1e3));
346
+ const interval = setInterval(() => {
347
+ const elapsed = Math.floor((Date.now() - startTime) / 1e3);
348
+ setSessionElapsedTime(elapsed);
349
+ }, 1e3);
350
+ return () => clearInterval(interval);
351
+ }, [activeSession]);
352
+ useEffect2(() => {
353
+ const activeAssignment = displayedAssignment?.status === "in_progress" ? displayedAssignment : null;
354
+ if (!activeAssignment?.startedAt) {
355
+ setAssignmentElapsedTime(0);
356
+ return;
357
+ }
358
+ const startTime = new Date(activeAssignment.startedAt).getTime();
359
+ const now = Date.now();
360
+ setAssignmentElapsedTime(Math.floor((now - startTime) / 1e3));
361
+ const interval = setInterval(() => {
362
+ const elapsed = Math.floor((Date.now() - startTime) / 1e3);
363
+ setAssignmentElapsedTime(elapsed);
364
+ }, 1e3);
365
+ return () => clearInterval(interval);
366
+ }, [displayedAssignment?.id, displayedAssignment?.status, displayedAssignment?.startedAt]);
367
+ useEffect2(() => {
368
+ if (!client || activeSession) {
369
+ setSuggestedRoutes([]);
370
+ setFocusAreas([]);
371
+ return;
372
+ }
373
+ const loadSuggestions = async () => {
374
+ try {
375
+ const supabase = client.supabase;
376
+ if (!supabase) return;
377
+ const projectId = client.config.projectId;
378
+ const [routeStatsRes, focusAreasRes] = await Promise.all([
379
+ supabase.from("route_test_stats").select("route, priority_score, open_bugs, exploratory_issues, last_tested_at, test_case_count").eq("project_id", projectId).order("priority_score", { ascending: false }).limit(10),
380
+ supabase.rpc("get_active_focus_areas", { p_project_id: projectId })
381
+ ]);
382
+ if (routeStatsRes.data && routeStatsRes.data.length > 0) {
383
+ const suggestions = routeStatsRes.data.filter((r) => r.route && r.priority_score > 0).slice(0, 5).map((r) => {
384
+ let reason = "";
385
+ if (r.open_bugs > 0) {
386
+ reason = `${r.open_bugs} open bug${r.open_bugs > 1 ? "s" : ""}`;
387
+ } else if (!r.last_tested_at) {
388
+ reason = "Never tested";
389
+ } else if (r.test_case_count === 0) {
390
+ reason = "No test coverage";
391
+ } else {
392
+ reason = `Priority: ${r.priority_score}`;
393
+ }
394
+ return {
395
+ route: r.route,
396
+ reason,
397
+ priorityScore: r.priority_score
398
+ };
399
+ });
400
+ setSuggestedRoutes(suggestions);
401
+ }
402
+ if (focusAreasRes.data && focusAreasRes.data.length > 0) {
403
+ setFocusAreas(focusAreasRes.data.slice(0, 3).map((fa) => ({
404
+ id: fa.id,
405
+ name: fa.name,
406
+ description: fa.description,
407
+ priority: fa.priority
408
+ })));
409
+ }
410
+ } catch (err) {
411
+ console.error("BugBear: Failed to load suggestions", err);
412
+ }
413
+ };
414
+ loadSuggestions();
415
+ }, [client, activeSession]);
248
416
  const handleMouseDown = useCallback2((e) => {
249
417
  if (!draggable || !panelPosition) return;
250
418
  const target = e.target;
@@ -295,27 +463,86 @@ function BugBearPanel({
295
463
  return null;
296
464
  }
297
465
  const handlePass = async () => {
298
- if (!client || !displayedAssignment) return;
299
- setSubmitting(true);
300
- await client.submitReport({
301
- type: "test_pass",
302
- description: `Test passed: ${displayedAssignment.testCase.title}`,
303
- assignmentId: displayedAssignment.id,
304
- testCaseId: displayedAssignment.testCase.id,
305
- appContext: getAppContext?.() || { currentRoute: window.location.pathname }
466
+ if (!displayedAssignment) return;
467
+ setPendingFeedbackStatus("passed");
468
+ setShowFeedbackPrompt(true);
469
+ setFeedbackRating(5);
470
+ setFeedbackNote("");
471
+ setFeedbackFlags({
472
+ isOutdated: false,
473
+ needsMoreDetail: false,
474
+ stepsUnclear: false,
475
+ expectedResultUnclear: false
306
476
  });
307
- await refreshAssignments();
308
- setSubmitting(false);
309
- setJustPassed(true);
310
- setTimeout(() => {
311
- setJustPassed(false);
312
- setSelectedTestId(null);
313
- setTestView("detail");
314
- }, 1200);
315
477
  };
316
478
  const handleFail = async () => {
317
- setActiveTab("report");
318
- setReportType("test_fail");
479
+ if (!displayedAssignment) return;
480
+ setPendingFeedbackStatus("failed");
481
+ setShowFeedbackPrompt(true);
482
+ setFeedbackRating(3);
483
+ setFeedbackNote("");
484
+ setFeedbackFlags({
485
+ isOutdated: false,
486
+ needsMoreDetail: false,
487
+ stepsUnclear: false,
488
+ expectedResultUnclear: false
489
+ });
490
+ };
491
+ const handleSubmitFeedback = async (skipFeedback = false) => {
492
+ if (!client || !displayedAssignment) return;
493
+ setSubmitting(true);
494
+ const feedback = skipFeedback ? void 0 : {
495
+ rating: feedbackRating,
496
+ feedbackNote: feedbackNote.trim() || void 0,
497
+ isOutdated: feedbackFlags.isOutdated,
498
+ needsMoreDetail: feedbackFlags.needsMoreDetail,
499
+ stepsUnclear: feedbackFlags.stepsUnclear,
500
+ expectedResultUnclear: feedbackFlags.expectedResultUnclear
501
+ };
502
+ if (pendingFeedbackStatus === "passed") {
503
+ await client.submitReport({
504
+ type: "test_pass",
505
+ description: `Test passed: ${displayedAssignment.testCase.title}`,
506
+ assignmentId: displayedAssignment.id,
507
+ testCaseId: displayedAssignment.testCase.id,
508
+ appContext: getAppContext?.() || { currentRoute: window.location.pathname }
509
+ });
510
+ if (feedback) {
511
+ await client.submitTestFeedback({
512
+ testCaseId: displayedAssignment.testCase.id,
513
+ assignmentId: displayedAssignment.id,
514
+ feedback,
515
+ timeToCompleteSeconds: assignmentElapsedTime || void 0
516
+ });
517
+ }
518
+ await refreshAssignments();
519
+ setSubmitting(false);
520
+ setShowFeedbackPrompt(false);
521
+ setPendingFeedbackStatus(null);
522
+ setJustPassed(true);
523
+ setTimeout(() => {
524
+ setJustPassed(false);
525
+ setSelectedTestId(null);
526
+ setTestView("detail");
527
+ }, 1200);
528
+ } else if (pendingFeedbackStatus === "failed") {
529
+ if (feedback) {
530
+ await client.submitTestFeedback({
531
+ testCaseId: displayedAssignment.testCase.id,
532
+ assignmentId: displayedAssignment.id,
533
+ feedback,
534
+ timeToCompleteSeconds: assignmentElapsedTime || void 0
535
+ });
536
+ }
537
+ setSubmitting(false);
538
+ setShowFeedbackPrompt(false);
539
+ setPendingFeedbackStatus(null);
540
+ setActiveTab("report");
541
+ setReportType("test_fail");
542
+ }
543
+ };
544
+ const handleSkipFeedback = () => {
545
+ handleSubmitFeedback(true);
319
546
  };
320
547
  const handleSubmitReport = async () => {
321
548
  if (!client || !description.trim()) return;
@@ -408,6 +635,54 @@ function BugBearPanel({
408
635
  }
409
636
  setSavingProfile(false);
410
637
  };
638
+ const handleStartSession = async () => {
639
+ setStartingSession(true);
640
+ const result = await startSession({
641
+ focusArea: sessionFocusArea.trim() || void 0,
642
+ platform: sessionPlatform
643
+ });
644
+ if (result.success) {
645
+ setSessionFocusArea("");
646
+ }
647
+ setStartingSession(false);
648
+ };
649
+ const handleEndSession = async () => {
650
+ setEndingSession(true);
651
+ const result = await endSession(sessionNotes.trim() || void 0);
652
+ if (result.success) {
653
+ setSessionNotes("");
654
+ setShowEndConfirm(false);
655
+ }
656
+ setEndingSession(false);
657
+ };
658
+ const handleAddFinding = async () => {
659
+ if (!findingTitle.trim()) return;
660
+ setAddingFinding(true);
661
+ const result = await addFinding({
662
+ type: findingType,
663
+ title: findingTitle.trim(),
664
+ description: findingDescription.trim() || void 0,
665
+ severity: findingType === "bug" ? findingSeverity : void 0,
666
+ route: typeof window !== "undefined" ? window.location.pathname : void 0
667
+ });
668
+ if (result.success) {
669
+ setFindingTitle("");
670
+ setFindingDescription("");
671
+ setFindingType("bug");
672
+ setFindingSeverity("medium");
673
+ setShowAddFinding(false);
674
+ }
675
+ setAddingFinding(false);
676
+ };
677
+ const formatElapsedTime = (seconds) => {
678
+ const h = Math.floor(seconds / 3600);
679
+ const m = Math.floor(seconds % 3600 / 60);
680
+ const s = seconds % 60;
681
+ if (h > 0) {
682
+ return `${h}:${m.toString().padStart(2, "0")}:${s.toString().padStart(2, "0")}`;
683
+ }
684
+ return `${m}:${s.toString().padStart(2, "0")}`;
685
+ };
411
686
  return /* @__PURE__ */ jsxs(
412
687
  "div",
413
688
  {
@@ -479,27 +754,40 @@ function BugBearPanel({
479
754
  ]
480
755
  }
481
756
  ),
482
- /* @__PURE__ */ jsxs("div", { className: "flex border-b border-gray-200", children: [
757
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-2 p-2 bg-gray-50 border-b border-gray-200", children: [
483
758
  /* @__PURE__ */ jsxs(
484
759
  "button",
485
760
  {
486
761
  onClick: () => setActiveTab("tests"),
487
- className: `flex-1 px-4 py-2 text-sm font-medium transition-colors ${activeTab === "tests" ? "text-purple-600 border-b-2 border-purple-600" : "text-gray-500 hover:text-gray-700"}`,
762
+ className: `flex-1 py-2 px-3 rounded-lg text-sm font-semibold transition-all flex items-center justify-center gap-1.5 ${activeTab === "tests" ? "bg-purple-600 text-white shadow-sm" : "bg-white text-gray-600 hover:bg-gray-100 border border-gray-200"}`,
488
763
  children: [
489
- "Tests ",
490
- pendingCount > 0 && `(${pendingCount})`
764
+ /* @__PURE__ */ jsx2("span", { children: "\u{1F4CB}" }),
765
+ /* @__PURE__ */ jsx2("span", { children: "Run Tests" }),
766
+ pendingCount > 0 && /* @__PURE__ */ jsx2("span", { className: `ml-1 px-1.5 py-0.5 rounded-full text-xs ${activeTab === "tests" ? "bg-purple-500 text-white" : "bg-purple-100 text-purple-600"}`, children: pendingCount })
491
767
  ]
492
768
  }
493
769
  ),
494
- /* @__PURE__ */ jsx2(
770
+ /* @__PURE__ */ jsxs(
495
771
  "button",
496
772
  {
497
- onClick: () => setActiveTab("report"),
498
- className: `flex-1 px-4 py-2 text-sm font-medium transition-colors ${activeTab === "report" ? "text-purple-600 border-b-2 border-purple-600" : "text-gray-500 hover:text-gray-700"}`,
499
- children: "Report"
773
+ onClick: () => setActiveTab("session"),
774
+ className: `flex-1 py-2 px-3 rounded-lg text-sm font-semibold transition-all flex items-center justify-center gap-1.5 relative ${activeTab === "session" ? "bg-amber-500 text-white shadow-sm" : "bg-white text-gray-600 hover:bg-gray-100 border border-gray-200"}`,
775
+ children: [
776
+ /* @__PURE__ */ jsx2("span", { children: "\u{1F50D}" }),
777
+ /* @__PURE__ */ jsx2("span", { children: "Explore" }),
778
+ activeSession && /* @__PURE__ */ jsx2("span", { className: "absolute -top-1 -right-1 w-3 h-3 bg-green-500 rounded-full border-2 border-white animate-pulse" })
779
+ ]
500
780
  }
501
781
  )
502
782
  ] }),
783
+ /* @__PURE__ */ jsx2("div", { className: "flex justify-center py-1.5 border-b border-gray-200 bg-white", children: /* @__PURE__ */ jsx2(
784
+ "button",
785
+ {
786
+ onClick: () => setActiveTab("report"),
787
+ className: `px-4 py-1 text-xs font-medium transition-colors rounded-full ${activeTab === "report" ? "bg-red-50 text-red-600 border border-red-200" : "text-gray-500 hover:text-gray-700 hover:bg-gray-50"}`,
788
+ children: "\u{1F41B} Report Bug / Feedback"
789
+ }
790
+ ) }),
503
791
  /* @__PURE__ */ jsxs("div", { className: "p-4 max-h-96 overflow-y-auto", children: [
504
792
  activeTab === "tests" && /* @__PURE__ */ jsx2("div", { children: assignments.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "text-center py-8", children: [
505
793
  /* @__PURE__ */ jsx2("span", { className: "text-4xl", children: "\u2705" }),
@@ -550,6 +838,83 @@ function BugBearPanel({
550
838
  assignment.id
551
839
  ))
552
840
  ] })
841
+ ) : showFeedbackPrompt && displayedAssignment ? (
842
+ /* Feedback prompt after completing a test */
843
+ /* @__PURE__ */ jsxs("div", { className: "p-3", children: [
844
+ /* @__PURE__ */ jsxs("div", { className: "text-center mb-4", children: [
845
+ /* @__PURE__ */ jsx2("span", { className: "text-3xl", children: pendingFeedbackStatus === "passed" ? "\u2713" : "\u2717" }),
846
+ /* @__PURE__ */ jsx2("p", { className: `font-semibold mt-1 ${pendingFeedbackStatus === "passed" ? "text-green-600" : "text-red-600"}`, children: pendingFeedbackStatus === "passed" ? "Test Passed!" : "Test Failed" })
847
+ ] }),
848
+ /* @__PURE__ */ jsxs("div", { className: "bg-purple-50 border border-purple-100 rounded-lg p-3 mb-4", children: [
849
+ /* @__PURE__ */ jsx2("p", { className: "text-purple-800 text-sm font-medium mb-1", children: "Help us improve this test" }),
850
+ /* @__PURE__ */ jsx2("p", { className: "text-purple-600 text-xs", children: "Your feedback shapes better tests for everyone." })
851
+ ] }),
852
+ /* @__PURE__ */ jsxs("div", { className: "mb-4", children: [
853
+ /* @__PURE__ */ jsx2("label", { className: "block text-xs font-medium text-gray-600 mb-2", children: "How was this test?" }),
854
+ /* @__PURE__ */ jsx2("div", { className: "flex items-center gap-1 justify-center", children: [1, 2, 3, 4, 5].map((star) => /* @__PURE__ */ jsx2(
855
+ "button",
856
+ {
857
+ type: "button",
858
+ onClick: () => setFeedbackRating(star),
859
+ className: `text-2xl transition-colors ${star <= feedbackRating ? "text-yellow-400" : "text-gray-300"} hover:text-yellow-400`,
860
+ children: "\u2605"
861
+ },
862
+ star
863
+ )) }),
864
+ /* @__PURE__ */ jsx2("p", { className: "text-center text-xs text-gray-500 mt-1", children: feedbackRating === 1 ? "Needs work" : feedbackRating === 2 ? "Could be better" : feedbackRating === 3 ? "Okay" : feedbackRating === 4 ? "Good" : "Great!" })
865
+ ] }),
866
+ feedbackRating < 4 && /* @__PURE__ */ jsxs("div", { className: "mb-4", children: [
867
+ /* @__PURE__ */ jsx2("label", { className: "block text-xs font-medium text-gray-600 mb-2", children: "What could be improved?" }),
868
+ /* @__PURE__ */ jsx2("div", { className: "grid grid-cols-2 gap-2", children: [
869
+ { key: "stepsUnclear", label: "Steps unclear" },
870
+ { key: "expectedResultUnclear", label: "Expected result unclear" },
871
+ { key: "needsMoreDetail", label: "Needs more detail" },
872
+ { key: "isOutdated", label: "Seems outdated" }
873
+ ].map(({ key, label }) => /* @__PURE__ */ jsx2(
874
+ "button",
875
+ {
876
+ type: "button",
877
+ onClick: () => setFeedbackFlags((prev) => ({ ...prev, [key]: !prev[key] })),
878
+ className: `px-2 py-1.5 rounded text-xs font-medium border transition-colors ${feedbackFlags[key] ? "bg-purple-100 border-purple-300 text-purple-700" : "bg-white border-gray-200 text-gray-600 hover:border-purple-200"}`,
879
+ children: label
880
+ },
881
+ key
882
+ )) })
883
+ ] }),
884
+ /* @__PURE__ */ jsxs("div", { className: "mb-4", children: [
885
+ /* @__PURE__ */ jsx2("label", { className: "block text-xs font-medium text-gray-600 mb-1", children: "Suggestions? (optional)" }),
886
+ /* @__PURE__ */ jsx2(
887
+ "textarea",
888
+ {
889
+ value: feedbackNote,
890
+ onChange: (e) => setFeedbackNote(e.target.value),
891
+ placeholder: "How could this test be improved?",
892
+ className: "w-full px-3 py-2 text-sm border border-gray-200 rounded-lg resize-none focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-transparent",
893
+ rows: 2
894
+ }
895
+ )
896
+ ] }),
897
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
898
+ /* @__PURE__ */ jsx2(
899
+ "button",
900
+ {
901
+ onClick: handleSkipFeedback,
902
+ disabled: submitting,
903
+ className: "flex-1 px-3 py-2 text-sm font-medium text-gray-600 bg-gray-100 rounded-lg hover:bg-gray-200 transition-colors disabled:opacity-50",
904
+ children: "Skip"
905
+ }
906
+ ),
907
+ /* @__PURE__ */ jsx2(
908
+ "button",
909
+ {
910
+ onClick: () => handleSubmitFeedback(false),
911
+ disabled: submitting,
912
+ className: "flex-1 px-3 py-2 text-sm font-medium text-white bg-purple-600 rounded-lg hover:bg-purple-700 transition-colors disabled:opacity-50",
913
+ children: submitting ? "Submitting..." : "Submit Feedback"
914
+ }
915
+ )
916
+ ] })
917
+ ] })
553
918
  ) : justPassed ? (
554
919
  /* Success state after passing */
555
920
  /* @__PURE__ */ jsxs("div", { className: "text-center py-8", children: [
@@ -591,6 +956,14 @@ function BugBearPanel({
591
956
  ] })
592
957
  ] }),
593
958
  /* @__PURE__ */ jsx2("h4", { className: "font-medium text-gray-900 text-sm mb-1", children: displayedAssignment.testCase.title }),
959
+ displayedAssignment.status === "in_progress" && displayedAssignment.startedAt && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 mb-2 text-xs text-green-600 bg-green-50 px-2 py-1 rounded", children: [
960
+ /* @__PURE__ */ jsxs("span", { className: "relative flex h-2 w-2", children: [
961
+ /* @__PURE__ */ jsx2("span", { className: "animate-ping absolute inline-flex h-full w-full rounded-full bg-green-400 opacity-75" }),
962
+ /* @__PURE__ */ jsx2("span", { className: "relative inline-flex rounded-full h-2 w-2 bg-green-500" })
963
+ ] }),
964
+ /* @__PURE__ */ jsx2("span", { className: "font-medium", children: "Testing" }),
965
+ /* @__PURE__ */ jsx2("span", { className: "font-mono", children: formatElapsedTime(assignmentElapsedTime) })
966
+ ] }),
594
967
  displayedAssignment.testCase.description && /* @__PURE__ */ jsx2("p", { className: "text-gray-500 text-xs mb-2", children: displayedAssignment.testCase.description }),
595
968
  displayedAssignment.testCase.targetRoute && onNavigate && /* @__PURE__ */ jsxs(
596
969
  "button",
@@ -767,6 +1140,277 @@ function BugBearPanel({
767
1140
  ] })
768
1141
  ] })
769
1142
  ) : null }),
1143
+ activeTab === "session" && /* @__PURE__ */ jsx2("div", { children: !activeSession ? (
1144
+ /* Start Session View */
1145
+ /* @__PURE__ */ jsxs("div", { children: [
1146
+ /* @__PURE__ */ jsxs("div", { className: "text-center mb-4", children: [
1147
+ /* @__PURE__ */ jsx2("span", { className: "text-4xl", children: "\u{1F50D}" }),
1148
+ /* @__PURE__ */ jsx2("h3", { className: "font-semibold text-gray-900 mt-2", children: "Exploratory QA Session" }),
1149
+ /* @__PURE__ */ jsx2("p", { className: "text-gray-500 text-xs mt-1", children: "Explore freely and capture findings as you go" })
1150
+ ] }),
1151
+ focusAreas.length > 0 && /* @__PURE__ */ jsxs("div", { className: "mb-4", children: [
1152
+ /* @__PURE__ */ jsxs("label", { className: "block text-xs font-medium text-gray-700 mb-2", children: [
1153
+ "\u{1F4CC} Focus Areas",
1154
+ /* @__PURE__ */ jsx2("span", { className: "ml-1 text-[10px] text-gray-500 font-normal", children: "from your team" })
1155
+ ] }),
1156
+ /* @__PURE__ */ jsx2("div", { className: "space-y-1.5", children: focusAreas.map((area) => /* @__PURE__ */ jsxs(
1157
+ "button",
1158
+ {
1159
+ onClick: () => setSessionFocusArea(area.name),
1160
+ className: `w-full text-left px-3 py-2 rounded-lg text-xs transition-colors border ${sessionFocusArea === area.name ? "bg-amber-50 border-amber-300 text-amber-700" : "bg-amber-50/50 border-amber-200 text-gray-700 hover:bg-amber-50"}`,
1161
+ children: [
1162
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
1163
+ /* @__PURE__ */ jsx2("span", { className: "font-medium", children: area.name }),
1164
+ /* @__PURE__ */ jsx2("span", { className: `text-[10px] px-1.5 py-0.5 rounded ${area.priority >= 70 ? "bg-red-100 text-red-600" : area.priority >= 50 ? "bg-amber-100 text-amber-600" : "bg-gray-100 text-gray-500"}`, children: area.priority >= 70 ? "Urgent" : area.priority >= 50 ? "Important" : "Suggested" })
1165
+ ] }),
1166
+ area.description && /* @__PURE__ */ jsx2("p", { className: "text-[10px] text-gray-500 mt-0.5 line-clamp-2", children: area.description })
1167
+ ]
1168
+ },
1169
+ area.id
1170
+ )) })
1171
+ ] }),
1172
+ suggestedRoutes.length > 0 && /* @__PURE__ */ jsxs("div", { className: "mb-4", children: [
1173
+ /* @__PURE__ */ jsx2("label", { className: "block text-xs font-medium text-gray-700 mb-2", children: "\u{1F3AF} Suggested Routes to Explore" }),
1174
+ /* @__PURE__ */ jsx2("div", { className: "space-y-1.5 max-h-32 overflow-y-auto", children: suggestedRoutes.map((suggestion, idx) => /* @__PURE__ */ jsx2(
1175
+ "button",
1176
+ {
1177
+ onClick: () => setSessionFocusArea(suggestion.route),
1178
+ className: `w-full text-left px-3 py-2 rounded-lg text-xs transition-colors border ${sessionFocusArea === suggestion.route ? "bg-purple-50 border-purple-300 text-purple-700" : "bg-gray-50 border-gray-200 text-gray-700 hover:bg-gray-100"}`,
1179
+ children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
1180
+ /* @__PURE__ */ jsx2("span", { className: "font-medium truncate", children: suggestion.route }),
1181
+ /* @__PURE__ */ jsx2("span", { className: `text-[10px] px-1.5 py-0.5 rounded ${suggestion.priorityScore >= 40 ? "bg-red-100 text-red-600" : suggestion.priorityScore >= 25 ? "bg-amber-100 text-amber-600" : "bg-gray-100 text-gray-500"}`, children: suggestion.reason })
1182
+ ] })
1183
+ },
1184
+ idx
1185
+ )) })
1186
+ ] }),
1187
+ /* @__PURE__ */ jsxs("div", { className: "mb-3", children: [
1188
+ /* @__PURE__ */ jsx2("label", { className: "block text-xs font-medium text-gray-700 mb-1", children: "Focus Area (optional)" }),
1189
+ /* @__PURE__ */ jsx2(
1190
+ "input",
1191
+ {
1192
+ type: "text",
1193
+ value: sessionFocusArea,
1194
+ onChange: (e) => setSessionFocusArea(e.target.value),
1195
+ placeholder: "e.g., checkout flow, settings page",
1196
+ className: "w-full px-3 py-2 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-purple-500"
1197
+ }
1198
+ )
1199
+ ] }),
1200
+ /* @__PURE__ */ jsxs("div", { className: "mb-4", children: [
1201
+ /* @__PURE__ */ jsx2("label", { className: "block text-xs font-medium text-gray-700 mb-1", children: "Platform" }),
1202
+ /* @__PURE__ */ jsx2("div", { className: "flex gap-2", children: [
1203
+ { key: "web", label: "\u{1F310} Web" },
1204
+ { key: "ios", label: "\u{1F4F1} iOS" },
1205
+ { key: "android", label: "\u{1F916} Android" }
1206
+ ].map(({ key, label }) => /* @__PURE__ */ jsx2(
1207
+ "button",
1208
+ {
1209
+ onClick: () => setSessionPlatform(key),
1210
+ className: `flex-1 py-2 px-3 rounded-lg text-xs font-medium transition-colors border-2 ${sessionPlatform === key ? "bg-purple-50 border-purple-500 text-purple-700" : "bg-gray-50 border-transparent text-gray-600 hover:bg-gray-100"}`,
1211
+ children: label
1212
+ },
1213
+ key
1214
+ )) })
1215
+ ] }),
1216
+ /* @__PURE__ */ jsx2(
1217
+ "button",
1218
+ {
1219
+ onClick: handleStartSession,
1220
+ disabled: startingSession,
1221
+ className: "w-full py-3 px-4 bg-green-600 text-white rounded-lg font-semibold text-sm hover:bg-green-700 disabled:opacity-50 transition-colors flex items-center justify-center gap-2",
1222
+ children: startingSession ? "Starting..." : /* @__PURE__ */ jsxs(Fragment, { children: [
1223
+ /* @__PURE__ */ jsx2("span", { children: "\u25B6" }),
1224
+ " Start Session"
1225
+ ] })
1226
+ }
1227
+ )
1228
+ ] })
1229
+ ) : showEndConfirm ? (
1230
+ /* End Session Confirmation */
1231
+ /* @__PURE__ */ jsxs("div", { children: [
1232
+ /* @__PURE__ */ jsxs("div", { className: "text-center mb-4", children: [
1233
+ /* @__PURE__ */ jsx2("span", { className: "text-4xl", children: "\u270B" }),
1234
+ /* @__PURE__ */ jsx2("h3", { className: "font-semibold text-gray-900 mt-2", children: "End Session?" }),
1235
+ /* @__PURE__ */ jsxs("p", { className: "text-gray-500 text-xs mt-1", children: [
1236
+ "Duration: ",
1237
+ formatElapsedTime(sessionElapsedTime),
1238
+ " \u2022 ",
1239
+ sessionFindings.length,
1240
+ " finding",
1241
+ sessionFindings.length !== 1 ? "s" : ""
1242
+ ] })
1243
+ ] }),
1244
+ /* @__PURE__ */ jsxs("div", { className: "mb-4", children: [
1245
+ /* @__PURE__ */ jsx2("label", { className: "block text-xs font-medium text-gray-700 mb-1", children: "Session Notes (optional)" }),
1246
+ /* @__PURE__ */ jsx2(
1247
+ "textarea",
1248
+ {
1249
+ value: sessionNotes,
1250
+ onChange: (e) => setSessionNotes(e.target.value),
1251
+ placeholder: "Any overall observations from this session...",
1252
+ rows: 3,
1253
+ className: "w-full px-3 py-2 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-purple-500 resize-none"
1254
+ }
1255
+ )
1256
+ ] }),
1257
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
1258
+ /* @__PURE__ */ jsx2(
1259
+ "button",
1260
+ {
1261
+ onClick: () => setShowEndConfirm(false),
1262
+ className: "flex-1 py-2 px-3 bg-gray-100 text-gray-700 rounded-lg font-medium text-sm hover:bg-gray-200 transition-colors",
1263
+ children: "Cancel"
1264
+ }
1265
+ ),
1266
+ /* @__PURE__ */ jsx2(
1267
+ "button",
1268
+ {
1269
+ onClick: handleEndSession,
1270
+ disabled: endingSession,
1271
+ className: "flex-1 py-2 px-3 bg-red-600 text-white rounded-lg font-medium text-sm hover:bg-red-700 disabled:opacity-50 transition-colors",
1272
+ children: endingSession ? "Ending..." : "End Session"
1273
+ }
1274
+ )
1275
+ ] })
1276
+ ] })
1277
+ ) : showAddFinding ? (
1278
+ /* Add Finding Form */
1279
+ /* @__PURE__ */ jsxs("div", { children: [
1280
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mb-3", children: [
1281
+ /* @__PURE__ */ jsx2("h3", { className: "font-semibold text-gray-900 text-sm", children: "Add Finding" }),
1282
+ /* @__PURE__ */ jsx2(
1283
+ "button",
1284
+ {
1285
+ onClick: () => setShowAddFinding(false),
1286
+ className: "text-gray-400 hover:text-gray-600",
1287
+ children: "\u2715"
1288
+ }
1289
+ )
1290
+ ] }),
1291
+ /* @__PURE__ */ jsx2("div", { className: "flex gap-1 mb-3", children: [
1292
+ { type: "bug", label: "\u{1F41B} Bug", color: "red" },
1293
+ { type: "concern", label: "\u26A0\uFE0F Concern", color: "orange" },
1294
+ { type: "suggestion", label: "\u{1F4A1} Idea", color: "blue" },
1295
+ { type: "question", label: "\u2753 Question", color: "purple" }
1296
+ ].map(({ type, label, color }) => /* @__PURE__ */ jsx2(
1297
+ "button",
1298
+ {
1299
+ onClick: () => setFindingType(type),
1300
+ className: `flex-1 py-1.5 px-2 rounded text-xs font-medium transition-colors ${findingType === type ? color === "red" ? "bg-red-100 text-red-700 ring-2 ring-red-400" : color === "orange" ? "bg-orange-100 text-orange-700 ring-2 ring-orange-400" : color === "blue" ? "bg-blue-100 text-blue-700 ring-2 ring-blue-400" : "bg-purple-100 text-purple-700 ring-2 ring-purple-400" : "bg-gray-100 text-gray-600 hover:bg-gray-200"}`,
1301
+ children: label
1302
+ },
1303
+ type
1304
+ )) }),
1305
+ findingType === "bug" && /* @__PURE__ */ jsxs("div", { className: "mb-3", children: [
1306
+ /* @__PURE__ */ jsx2("label", { className: "block text-xs font-medium text-gray-700 mb-1", children: "Severity" }),
1307
+ /* @__PURE__ */ jsx2("div", { className: "flex gap-1", children: ["critical", "high", "medium", "low", "observation"].map((sev) => /* @__PURE__ */ jsx2(
1308
+ "button",
1309
+ {
1310
+ onClick: () => setFindingSeverity(sev),
1311
+ className: `flex-1 py-1 px-1 rounded text-xs font-medium capitalize transition-colors ${findingSeverity === sev ? sev === "critical" ? "bg-red-600 text-white" : sev === "high" ? "bg-orange-500 text-white" : sev === "medium" ? "bg-yellow-500 text-black" : sev === "low" ? "bg-gray-500 text-white" : "bg-blue-500 text-white" : "bg-gray-100 text-gray-600 hover:bg-gray-200"}`,
1312
+ children: sev === "observation" ? "obs" : sev
1313
+ },
1314
+ sev
1315
+ )) })
1316
+ ] }),
1317
+ /* @__PURE__ */ jsxs("div", { className: "mb-3", children: [
1318
+ /* @__PURE__ */ jsx2("label", { className: "block text-xs font-medium text-gray-700 mb-1", children: "Title *" }),
1319
+ /* @__PURE__ */ jsx2(
1320
+ "input",
1321
+ {
1322
+ type: "text",
1323
+ value: findingTitle,
1324
+ onChange: (e) => setFindingTitle(e.target.value),
1325
+ placeholder: "Brief description of what you found",
1326
+ className: "w-full px-3 py-2 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-purple-500"
1327
+ }
1328
+ )
1329
+ ] }),
1330
+ /* @__PURE__ */ jsxs("div", { className: "mb-3", children: [
1331
+ /* @__PURE__ */ jsx2("label", { className: "block text-xs font-medium text-gray-700 mb-1", children: "Details (optional)" }),
1332
+ /* @__PURE__ */ jsx2(
1333
+ "textarea",
1334
+ {
1335
+ value: findingDescription,
1336
+ onChange: (e) => setFindingDescription(e.target.value),
1337
+ placeholder: "Steps to reproduce, expected behavior, etc.",
1338
+ rows: 2,
1339
+ className: "w-full px-3 py-2 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-purple-500 resize-none"
1340
+ }
1341
+ )
1342
+ ] }),
1343
+ /* @__PURE__ */ jsx2(
1344
+ "button",
1345
+ {
1346
+ onClick: handleAddFinding,
1347
+ disabled: addingFinding || !findingTitle.trim(),
1348
+ className: "w-full py-2 px-4 bg-purple-600 text-white rounded-lg font-medium text-sm hover:bg-purple-700 disabled:opacity-50 disabled:cursor-not-allowed transition-colors",
1349
+ children: addingFinding ? "Adding..." : "Add Finding"
1350
+ }
1351
+ )
1352
+ ] })
1353
+ ) : (
1354
+ /* Active Session View */
1355
+ /* @__PURE__ */ jsxs("div", { children: [
1356
+ /* @__PURE__ */ jsxs("div", { className: "bg-green-50 rounded-lg p-3 mb-3 border border-green-200", children: [
1357
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
1358
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
1359
+ /* @__PURE__ */ jsx2("span", { className: "w-2 h-2 bg-green-500 rounded-full animate-pulse" }),
1360
+ /* @__PURE__ */ jsx2("span", { className: "font-medium text-green-800 text-sm", children: "Session Active" })
1361
+ ] }),
1362
+ /* @__PURE__ */ jsx2("span", { className: "font-mono text-green-700 text-lg font-semibold", children: formatElapsedTime(sessionElapsedTime) })
1363
+ ] }),
1364
+ activeSession.focusArea && /* @__PURE__ */ jsxs("p", { className: "text-green-700 text-xs mt-1", children: [
1365
+ "Focus: ",
1366
+ activeSession.focusArea
1367
+ ] })
1368
+ ] }),
1369
+ /* @__PURE__ */ jsxs(
1370
+ "button",
1371
+ {
1372
+ onClick: () => setShowAddFinding(true),
1373
+ className: "w-full py-3 px-4 bg-purple-600 text-white rounded-lg font-semibold text-sm hover:bg-purple-700 transition-colors flex items-center justify-center gap-2 mb-3",
1374
+ children: [
1375
+ /* @__PURE__ */ jsx2("span", { children: "+" }),
1376
+ " Add Finding"
1377
+ ]
1378
+ }
1379
+ ),
1380
+ /* @__PURE__ */ jsxs("div", { className: "mb-3", children: [
1381
+ /* @__PURE__ */ jsx2("div", { className: "flex items-center justify-between mb-2", children: /* @__PURE__ */ jsxs("span", { className: "text-xs font-medium text-gray-500", children: [
1382
+ "Findings (",
1383
+ sessionFindings.length,
1384
+ ")"
1385
+ ] }) }),
1386
+ sessionFindings.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "text-center py-4 bg-gray-50 rounded-lg", children: [
1387
+ /* @__PURE__ */ jsx2("p", { className: "text-gray-400 text-xs", children: "No findings yet" }),
1388
+ /* @__PURE__ */ jsx2("p", { className: "text-gray-400 text-xs", children: "Explore and add findings as you go" })
1389
+ ] }) : /* @__PURE__ */ jsx2("div", { className: "space-y-2 max-h-32 overflow-y-auto", children: sessionFindings.map((finding) => /* @__PURE__ */ jsx2(
1390
+ "div",
1391
+ {
1392
+ className: `p-2 rounded-lg border text-xs ${finding.type === "bug" ? "bg-red-50 border-red-200" : finding.type === "concern" ? "bg-orange-50 border-orange-200" : finding.type === "suggestion" ? "bg-blue-50 border-blue-200" : "bg-purple-50 border-purple-200"}`,
1393
+ children: /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2", children: [
1394
+ /* @__PURE__ */ jsx2("span", { children: finding.type === "bug" ? "\u{1F41B}" : finding.type === "concern" ? "\u26A0\uFE0F" : finding.type === "suggestion" ? "\u{1F4A1}" : "\u2753" }),
1395
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
1396
+ /* @__PURE__ */ jsx2("p", { className: "font-medium text-gray-900 truncate", children: finding.title }),
1397
+ finding.severity && finding.type === "bug" && /* @__PURE__ */ jsx2("span", { className: `inline-block mt-0.5 px-1 py-0.5 rounded text-[10px] font-medium ${finding.severity === "critical" ? "bg-red-200 text-red-800" : finding.severity === "high" ? "bg-orange-200 text-orange-800" : finding.severity === "medium" ? "bg-yellow-200 text-yellow-800" : "bg-gray-200 text-gray-700"}`, children: finding.severity })
1398
+ ] })
1399
+ ] })
1400
+ },
1401
+ finding.id
1402
+ )) })
1403
+ ] }),
1404
+ /* @__PURE__ */ jsx2(
1405
+ "button",
1406
+ {
1407
+ onClick: () => setShowEndConfirm(true),
1408
+ className: "w-full py-2 px-4 bg-gray-100 text-gray-700 rounded-lg font-medium text-sm hover:bg-gray-200 transition-colors",
1409
+ children: "End Session"
1410
+ }
1411
+ )
1412
+ ] })
1413
+ ) }),
770
1414
  activeTab === "report" && /* @__PURE__ */ jsx2("div", { children: submitted ? /* @__PURE__ */ jsxs("div", { className: "text-center py-8", children: [
771
1415
  /* @__PURE__ */ jsx2("span", { className: "text-4xl", children: "\u{1F389}" }),
772
1416
  /* @__PURE__ */ jsx2("p", { className: "text-gray-600 mt-2 font-medium", children: "Report submitted!" })