-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsourcekit-qa-report.html
More file actions
960 lines (891 loc) · 40.6 KB
/
sourcekit-qa-report.html
File metadata and controls
960 lines (891 loc) · 40.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>SourceKit QA Report</title>
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600;700&family=DM+Sans:wght@400;500;600;700&display=swap" rel="stylesheet">
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
html { scroll-behavior: smooth; }
body {
font-family: 'DM Sans', system-ui, sans-serif;
background: #0a0a0f;
color: #e0e0e0;
line-height: 1.6;
-webkit-font-smoothing: antialiased;
}
:root {
--bg: #0a0a0f;
--surface: #12131a;
--surface-2: #1a1b25;
--border: #2a2b35;
--text: #e0e0e0;
--text-dim: #8a8b95;
--accent: #00e5a0;
--accent-dim: rgba(0,229,160,0.15);
--red: #ff4d6a;
--red-dim: rgba(255,77,106,0.15);
--orange: #ff9f43;
--orange-dim: rgba(255,159,67,0.15);
--yellow: #ffd93d;
--yellow-dim: rgba(255,217,61,0.15);
--blue: #4da6ff;
--blue-dim: rgba(77,166,255,0.15);
--mono: 'JetBrains Mono', monospace;
}
.container { max-width: 1400px; margin: 0 auto; padding: 32px 24px; }
/* Header */
.header {
border-bottom: 1px solid var(--border);
padding-bottom: 24px;
margin-bottom: 32px;
}
.header h1 {
font-family: var(--mono);
font-size: 28px;
font-weight: 700;
color: var(--accent);
margin-bottom: 8px;
}
.header .subtitle {
color: var(--text-dim);
font-size: 14px;
}
.header .meta {
display: flex;
gap: 24px;
margin-top: 12px;
font-size: 13px;
color: var(--text-dim);
}
.header .meta span { font-family: var(--mono); }
/* Tabs */
.tabs {
display: flex;
gap: 4px;
border-bottom: 1px solid var(--border);
margin-bottom: 24px;
overflow-x: auto;
position: sticky;
top: 0;
z-index: 100;
background: var(--bg);
padding: 12px 0 0;
}
.tab {
font-family: var(--mono);
font-size: 13px;
font-weight: 500;
padding: 10px 20px;
cursor: pointer;
color: var(--text-dim);
border-bottom: 2px solid transparent;
white-space: nowrap;
transition: all 0.2s;
}
.tab:hover { color: var(--text); }
.tab.active {
color: var(--accent);
border-bottom-color: var(--accent);
}
/* Tab Content */
.tab-content { display: none; animation: fadeIn 0.3s ease; }
.tab-content.active { display: block; }
@keyframes fadeIn { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: translateY(0); } }
/* Section */
.section { margin-bottom: 32px; }
.section-title {
font-family: var(--mono);
font-size: 16px;
font-weight: 600;
color: var(--accent);
margin-bottom: 16px;
padding-bottom: 8px;
border-bottom: 1px solid var(--border);
}
/* Cards */
.card {
background: var(--surface);
border: 1px solid var(--border);
border-radius: 8px;
padding: 20px;
margin-bottom: 16px;
}
/* Stats Grid */
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
gap: 16px;
margin-bottom: 32px;
}
.stat-card {
background: var(--surface);
border: 1px solid var(--border);
border-radius: 8px;
padding: 20px;
text-align: center;
}
.stat-value {
font-family: var(--mono);
font-size: 36px;
font-weight: 700;
}
.stat-label {
font-size: 12px;
color: var(--text-dim);
text-transform: uppercase;
letter-spacing: 0.5px;
margin-top: 4px;
}
/* Table */
table {
width: 100%;
border-collapse: collapse;
font-size: 13px;
}
th {
font-family: var(--mono);
font-size: 11px;
text-transform: uppercase;
letter-spacing: 0.5px;
color: var(--text-dim);
text-align: left;
padding: 12px 16px;
border-bottom: 1px solid var(--border);
position: sticky;
top: 48px;
background: var(--surface);
z-index: 10;
}
td {
padding: 12px 16px;
border-bottom: 1px solid rgba(42,43,53,0.5);
vertical-align: top;
}
tr:hover { background: var(--surface-2); }
/* Severity pills */
.pill {
font-family: var(--mono);
font-size: 11px;
font-weight: 600;
padding: 3px 10px;
border-radius: 4px;
display: inline-block;
}
.pill-p0 { background: var(--red-dim); color: var(--red); }
.pill-p1 { background: var(--orange-dim); color: var(--orange); }
.pill-p2 { background: var(--yellow-dim); color: var(--yellow); }
.pill-p3 { background: var(--blue-dim); color: var(--blue); }
.pill-pass { background: var(--accent-dim); color: var(--accent); }
/* Code block */
.code-block {
background: #0d0e14;
border: 1px solid var(--border);
border-radius: 6px;
padding: 16px;
font-family: var(--mono);
font-size: 12px;
line-height: 1.7;
overflow-x: auto;
white-space: pre-wrap;
color: #c8c8d0;
margin-bottom: 16px;
}
.code-block .comment { color: #5a5b65; }
.code-block .keyword { color: var(--accent); }
.code-block .string { color: var(--yellow); }
/* Workflow */
.workflow {
display: flex;
align-items: flex-start;
gap: 8px;
flex-wrap: wrap;
margin-bottom: 24px;
}
.workflow-step {
background: var(--surface);
border: 1px solid var(--border);
border-radius: 6px;
padding: 12px 16px;
font-size: 13px;
min-width: 140px;
text-align: center;
}
.workflow-step.pass { border-color: var(--accent); }
.workflow-step.warn { border-color: var(--orange); }
.workflow-step.fail { border-color: var(--red); }
.workflow-step .step-label {
font-family: var(--mono);
font-size: 11px;
color: var(--text-dim);
margin-bottom: 4px;
}
.workflow-arrow {
color: var(--text-dim);
font-size: 20px;
padding-top: 12px;
}
/* Feature status */
.feature-row {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 0;
border-bottom: 1px solid rgba(42,43,53,0.5);
}
.feature-name { font-weight: 500; }
.feature-status { font-family: var(--mono); font-size: 12px; }
/* Prompt card */
.prompt-card {
background: var(--surface);
border: 1px solid var(--border);
border-radius: 8px;
margin-bottom: 20px;
overflow: hidden;
}
.prompt-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px 20px;
border-bottom: 1px solid var(--border);
cursor: pointer;
}
.prompt-header h3 {
font-family: var(--mono);
font-size: 14px;
font-weight: 600;
}
.prompt-body { padding: 20px; }
.prompt-body p { margin-bottom: 12px; color: var(--text-dim); font-size: 13px; }
.callout {
padding: 16px 20px;
border-radius: 6px;
margin-bottom: 16px;
font-size: 13px;
}
.callout-red { background: var(--red-dim); border-left: 3px solid var(--red); }
.callout-orange { background: var(--orange-dim); border-left: 3px solid var(--orange); }
.callout-green { background: var(--accent-dim); border-left: 3px solid var(--accent); }
</style>
</head>
<body>
<div class="container">
<!-- HEADER -->
<div class="header">
<h1>SourceKit QA Report</h1>
<div class="subtitle">Comprehensive feature-by-feature testing of SourceKit at getsourcekit.vercel.app</div>
<div class="meta">
<span>Date: 2026-02-28</span>
<span>Tester: Claude (automated browser QA)</span>
<span>Stack: React + Vite + TS + shadcn/ui + Supabase</span>
<span>Repo: github.com/mrNLK/sourcekit-charm</span>
</div>
</div>
<!-- STATS -->
<div class="stats-grid">
<div class="stat-card">
<div class="stat-value" style="color: var(--red);">2</div>
<div class="stat-label">P0/P1 Bugs</div>
</div>
<div class="stat-card">
<div class="stat-value" style="color: var(--orange);">5</div>
<div class="stat-label">P2 Bugs</div>
</div>
<div class="stat-card">
<div class="stat-value" style="color: var(--blue);">3</div>
<div class="stat-label">P3 Issues</div>
</div>
<div class="stat-card">
<div class="stat-value" style="color: var(--accent);">8</div>
<div class="stat-label">Features Tested</div>
</div>
<div class="stat-card">
<div class="stat-value" style="color: var(--accent);">6</div>
<div class="stat-label">Features Passing</div>
</div>
</div>
<!-- TABS -->
<div class="tabs">
<div class="tab active" data-tab="summary">Issue Summary</div>
<div class="tab" data-tab="workflow">Workflow Map</div>
<div class="tab" data-tab="details">Detailed Findings</div>
<div class="tab" data-tab="scoring">Scoring Audit</div>
<div class="tab" data-tab="fix-prompts">Fix Prompts (P0+P1)</div>
<div class="tab" data-tab="improve-prompts">Improvement Prompts (P2+P3)</div>
</div>
<!-- TAB: Issue Summary -->
<div class="tab-content active" id="summary">
<div class="section">
<div class="section-title">Issue Summary Table</div>
<div class="card" style="overflow-x:auto;">
<table>
<thead>
<tr>
<th>ID</th>
<th>Severity</th>
<th>Feature</th>
<th>Issue</th>
<th>Repro Steps</th>
<th>Root Cause</th>
</tr>
</thead>
<tbody>
<tr>
<td style="font-family:var(--mono);">BUG-001</td>
<td><span class="pill pill-p1">P1</span></td>
<td>Outreach</td>
<td><strong>Generate Outreach button throws TypeError</strong>: "Cannot read properties of undefined (reading 'name')"</td>
<td>Open any candidate detail panel > scroll to Outreach > click "Generate Outreach"</td>
<td>Outreach generator reads <code>.name</code> from role/company context object that is undefined. The context from the search strategy is not passed to the outreach component. Also fires 4-5x per click (event handler leak).</td>
</tr>
<tr>
<td style="font-family:var(--mono);">BUG-002</td>
<td><span class="pill pill-p1">P1</span></td>
<td>Candidate Search</td>
<td><strong>First search from strategy returns 0 results</strong>. Clicking "Search with this strategy" navigates to Results page but shows "No engineers found". Retry from Results page succeeds.</td>
<td>New Search > enter role+company > Build Sourcing Strategy > click "Search with this strategy"</td>
<td>Race condition: the strategy state (repos, query) is likely not fully transferred to the search component before the edge function call fires. Only an OPTIONS preflight was captured in network monitoring, suggesting the POST either fired before monitoring started or was aborted.</td>
</tr>
<tr>
<td style="font-family:var(--mono);">BUG-003</td>
<td><span class="pill pill-p2">P2</span></td>
<td>Pipeline</td>
<td><strong>Stage dropdown only shows 2 of 5 stages</strong>. On candidate detail (from Pipeline), the stage selector dropdown shows only "Contacted" and "Not Interested", missing Recruiter Screen, Rejected, Moved to ATS.</td>
<td>Pipeline > click candidate card > click stage dropdown (e.g., "Contacted")</td>
<td>Dropdown options are likely hardcoded to a subset or the stage list is being filtered incorrectly.</td>
</tr>
<tr>
<td style="font-family:var(--mono);">BUG-004</td>
<td><span class="pill pill-p2">P2</span></td>
<td>Pipeline</td>
<td><strong>Stage dropdown UI does not update after stage change</strong>. Clicking "Not Interested" from the dropdown changes the stage in Supabase (verified on kanban), but the button still displays "Contacted".</td>
<td>Pipeline > click candidate > change stage via dropdown > observe button text</td>
<td>The local React state for the stage button is not re-rendered after the Supabase write succeeds. Likely a missing state update or stale closure.</td>
</tr>
<tr>
<td style="font-family:var(--mono);">BUG-005</td>
<td><span class="pill pill-p2">P2</span></td>
<td>Navigation</td>
<td><strong>Page refresh always navigates to New Search</strong>. Refreshing (F5) on Pipeline, Results, or any sub-page redirects to the home/New Search page.</td>
<td>Navigate to Pipeline (or any page) > press F5</td>
<td>React Router likely uses in-memory state for navigation instead of URL-based routes, or the route paths are not defined in the Vercel routing config (SPA fallback).</td>
</tr>
<tr>
<td style="font-family:var(--mono);">BUG-006</td>
<td><span class="pill pill-p2">P2</span></td>
<td>Outreach</td>
<td><strong>Generate Outreach handler fires multiple times per click</strong>. Console shows 4-5 identical errors per single button click, indicating the event handler is attached multiple times.</td>
<td>Open candidate detail > click "Generate Outreach" once > observe 4-5 console errors</td>
<td>Event listener is registered in a useEffect without cleanup, or the component re-renders and re-attaches handlers on each render cycle.</td>
</tr>
<tr>
<td style="font-family:var(--mono);">BUG-007</td>
<td><span class="pill pill-p2">P2</span></td>
<td>Outreach</td>
<td><strong>No user-facing error when outreach generation fails</strong>. Button shows no loading state, no error toast, no feedback. Silent failure.</td>
<td>Click "Generate Outreach" > observe no UI response</td>
<td>Error is caught and logged to console but no toast/alert is shown to the user.</td>
</tr>
<tr>
<td style="font-family:var(--mono);">BUG-008</td>
<td><span class="pill pill-p3">P3</span></td>
<td>Search History</td>
<td><strong>Result count mismatch</strong>. History shows "46 results" for "Senior ML Engineer at Anthropic" but Results page only displays 20 candidates.</td>
<td>History page > compare result count badge vs actual results loaded</td>
<td>History may be counting total GitHub API matches (before filtering/deduplication) while Results page shows post-processed results. Or multiple search attempts are aggregated.</td>
</tr>
<tr>
<td style="font-family:var(--mono);">BUG-009</td>
<td><span class="pill pill-p3">P3</span></td>
<td>UI/UX</td>
<td><strong>AI-parsed search criteria tags are truncated</strong>. Tags like "Large language model dev..." and "PyTorch or JAX with deep..." cut off without tooltip or expand option.</td>
<td>Results page > observe top criteria tags</td>
<td>CSS text-overflow: ellipsis applied without a title attribute or tooltip component.</td>
</tr>
<tr>
<td style="font-family:var(--mono);">BUG-010</td>
<td><span class="pill pill-p3">P3</span></td>
<td>Pipeline</td>
<td><strong>Drag-and-drop on kanban board not functional via automated testing</strong>. Drop zones show "Drop candidates here" but drag interaction could not be verified. Stage change only works via dropdown on detail page.</td>
<td>Pipeline > attempt to drag candidate card to another column</td>
<td>May be a testing tool limitation, or actual DnD implementation may need verification with manual testing.</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="section">
<div class="section-title">Feature Status</div>
<div class="card">
<div class="feature-row">
<span class="feature-name">Authentication (Supabase Google SSO)</span>
<span class="pill pill-pass">PASS</span>
</div>
<div class="feature-row">
<span class="feature-name">Role Research / Strategy Builder</span>
<span class="pill pill-pass">PASS</span>
</div>
<div class="feature-row">
<span class="feature-name">Candidate Search (GitHub via Edge Functions)</span>
<span class="pill pill-p1">P1 - intermittent first-search failure</span>
</div>
<div class="feature-row">
<span class="feature-name">Candidate Enrichment (EEA scoring, AI summary)</span>
<span class="pill pill-pass">PASS</span>
</div>
<div class="feature-row">
<span class="feature-name">Scoring System (weighted EEA signals)</span>
<span class="pill pill-pass">PASS</span>
</div>
<div class="feature-row">
<span class="feature-name">Pipeline / Kanban Board</span>
<span class="pill pill-p2">P2 - stage dropdown incomplete + stale UI</span>
</div>
<div class="feature-row">
<span class="feature-name">Candidate Detail View</span>
<span class="pill pill-pass">PASS</span>
</div>
<div class="feature-row">
<span class="feature-name">Outreach Generation</span>
<span class="pill pill-p1">P1 - TypeError, no error handling</span>
</div>
<div class="feature-row">
<span class="feature-name">Watchlist</span>
<span class="pill pill-pass">PASS</span>
</div>
<div class="feature-row">
<span class="feature-name">Search History</span>
<span class="pill pill-pass">PASS</span>
</div>
<div class="feature-row">
<span class="feature-name">Bulk Actions</span>
<span class="pill pill-pass">PASS</span>
</div>
<div class="feature-row">
<span class="feature-name">Websets (Exa AI)</span>
<span class="pill pill-pass">PASS</span>
</div>
<div class="feature-row">
<span class="feature-name">Settings</span>
<span class="pill pill-pass">PASS</span>
</div>
<div class="feature-row">
<span class="feature-name">Data Persistence (refresh survival)</span>
<span class="pill pill-p2">P2 - route lost on refresh</span>
</div>
</div>
</div>
</div>
<!-- TAB: Workflow Map -->
<div class="tab-content" id="workflow">
<div class="section">
<div class="section-title">Core Workflow: Role Research to Pipeline</div>
<div class="workflow">
<div class="workflow-step pass">
<div class="step-label">Step 1</div>
New Search<br><small style="color:var(--accent)">PASS</small>
</div>
<div class="workflow-arrow">→</div>
<div class="workflow-step pass">
<div class="step-label">Step 2</div>
Enter Role + Company<br><small style="color:var(--accent)">PASS</small>
</div>
<div class="workflow-arrow">→</div>
<div class="workflow-step pass">
<div class="step-label">Step 3</div>
Build Strategy<br><small style="color:var(--accent)">PASS</small>
</div>
<div class="workflow-arrow">→</div>
<div class="workflow-step warn">
<div class="step-label">Step 4</div>
Search with Strategy<br><small style="color:var(--orange)">P1: First attempt fails</small>
</div>
<div class="workflow-arrow">→</div>
<div class="workflow-step pass">
<div class="step-label">Step 5</div>
View Results (20)<br><small style="color:var(--accent)">PASS on retry</small>
</div>
<div class="workflow-arrow">→</div>
<div class="workflow-step pass">
<div class="step-label">Step 6</div>
View Candidate Detail<br><small style="color:var(--accent)">PASS</small>
</div>
<div class="workflow-arrow">→</div>
<div class="workflow-step pass">
<div class="step-label">Step 7</div>
Add to Pipeline<br><small style="color:var(--accent)">PASS</small>
</div>
<div class="workflow-arrow">→</div>
<div class="workflow-step warn">
<div class="step-label">Step 8</div>
Change Stage<br><small style="color:var(--orange)">P2: Incomplete dropdown</small>
</div>
<div class="workflow-arrow">→</div>
<div class="workflow-step fail">
<div class="step-label">Step 9</div>
Generate Outreach<br><small style="color:var(--red)">P1: TypeError crash</small>
</div>
</div>
</div>
<div class="section">
<div class="section-title">Secondary Workflows</div>
<div class="card">
<div class="feature-row">
<span class="feature-name">Watchlist: Add candidate > view in Watchlist > persists on refresh</span>
<span class="pill pill-pass">PASS</span>
</div>
<div class="feature-row">
<span class="feature-name">History: Search > view in History > shows result count + timestamp</span>
<span class="pill pill-pass">PASS</span>
</div>
<div class="feature-row">
<span class="feature-name">Bulk Actions: View pipeline candidates > table with filters + AI chat panel</span>
<span class="pill pill-pass">PASS</span>
</div>
<div class="feature-row">
<span class="feature-name">Websets: Create webset form > empty state displayed correctly</span>
<span class="pill pill-pass">PASS</span>
</div>
<div class="feature-row">
<span class="feature-name">Settings: Outreach defaults + API keys + integrations > all fields rendered</span>
<span class="pill pill-pass">PASS</span>
</div>
<div class="feature-row">
<span class="feature-name">Validation: Empty form submission > red borders + error messages</span>
<span class="pill pill-pass">PASS</span>
</div>
<div class="feature-row">
<span class="feature-name">Job Description tab: Alternative input mode with URL + text area</span>
<span class="pill pill-pass">PASS</span>
</div>
</div>
</div>
</div>
<!-- TAB: Detailed Findings -->
<div class="tab-content" id="details">
<div class="section">
<div class="section-title">Phase 1: Authentication</div>
<div class="callout callout-green">All auth tests passed. Google SSO via Supabase authenticated correctly. No flash of unstyled content. Dark mode rendered on load. Session persists across navigation.</div>
</div>
<div class="section">
<div class="section-title">Phase 2A: Role Research</div>
<div class="card">
<p><strong>Happy Path:</strong> Entered "Senior ML Engineer" + "Anthropic". Strategy built in ~15 seconds with 4-step progress animation. Output included:</p>
<ul style="margin: 12px 0 12px 20px; color: var(--text-dim);">
<li>Search Query (editable semantic description)</li>
<li>Target Repositories (10 repos with descriptions and external links, removable with X)</li>
<li>Companies to Source From (8 companies with typed tags: Competitor, Adjacent, Talent Hub)</li>
<li>Skills (12 skills, Must Have / Nice to Have, removable tags)</li>
<li>EEA Signals (8 signals, color-coded strength indicators)</li>
<li>Sticky "Ready to search?" bar at bottom</li>
</ul>
<p><strong>Edge Cases:</strong></p>
<ul style="margin: 12px 0 12px 20px; color: var(--text-dim);">
<li>Empty submission: Both fields show red borders + "required" messages. PASS.</li>
<li>Job Description tab: Toggle works, shows URL + text area. PASS.</li>
<li>Clear button: Resets all fields and results. PASS.</li>
</ul>
</div>
</div>
<div class="section">
<div class="section-title">Phase 2B: Candidate Search</div>
<div class="card">
<div class="callout callout-orange">
<strong>BUG-002:</strong> First "Search with this strategy" click returned 0 results. Network monitoring captured only OPTIONS preflight to github-search edge function, no POST. Retry from Results page succeeded with 20 candidates. Intermittent race condition between strategy state transfer and search execution.
</div>
<p>When working, Results page shows: AI-parsed search criteria tags, repos searched, filter bar (location, Hidden Gems, Enrich All, Language, Min Score, Export, Seniority), and candidate cards with scores, EEA badges, commit stats, language bars.</p>
</div>
</div>
<div class="section">
<div class="section-title">Phase 2C: Candidate Enrichment + Detail View</div>
<div class="card">
<p><strong>Thomas Wolf (Score 98) detail panel verified:</strong></p>
<ul style="margin: 12px 0 12px 20px; color: var(--text-dim);">
<li>Profile header: avatar, name, score, handle, followers, join date</li>
<li>Action buttons: In Pipeline, Watchlisted, GitHub, LinkedIn</li>
<li>About: AI-generated summary of candidate background</li>
<li>EEA Section: USCIS Criteria (Original Work, Leadership, Published, Judging, Remuneration, Membership) + Supplementary Signals (Sustained, Tech Profile, Velocity, Builder, Early Mover)</li>
<li>Top Documentation Gaps: 4 specific gaps identified</li>
<li>Skills: Python, Jupyter Notebook, C++ with proportion bars</li>
<li>Notable Work: huggingface/transformers (1384 commits) + other repos</li>
<li>Outreach section with tone dropdown + Generate button (broken, see BUG-001)</li>
<li>GitHub stats footer: 79 Repos, 58 Stars, 3,388 Followers, Since 2014</li>
</ul>
<div class="callout callout-green">Enrichment data renders correctly and comprehensively. EEA scoring categories with strength labels are well-designed. LinkedIn URL populated: https://www.linkedin.com/in/thom-wolf</div>
</div>
</div>
<div class="section">
<div class="section-title">Phase 2E: Pipeline / Kanban</div>
<div class="card">
<p>5 columns: Contacted, Not Interested, Recruiter Screen, Rejected, Moved to ATS. Stage filter tabs with counts. Candidate cards show avatar, name, score, time indicator.</p>
<div class="callout callout-orange"><strong>BUG-003:</strong> Stage dropdown on detail page only shows Contacted and Not Interested (missing 3 stages).</div>
<div class="callout callout-orange"><strong>BUG-004:</strong> Stage dropdown button text doesn't update after successful stage change.</div>
<p>Stage change does persist to Supabase: confirmed Thomas Wolf moved from Contacted to Not Interested column on returning to kanban view.</p>
</div>
</div>
<div class="section">
<div class="section-title">Phase 3: Data Integrity</div>
<div class="card">
<ul style="margin: 12px 0 12px 20px; color: var(--text-dim);">
<li><strong>Pipeline data persists on refresh:</strong> PASS (candidate in correct stage after F5)</li>
<li><strong>Watchlist persists on refresh:</strong> PASS (badge count and candidate present)</li>
<li><strong>Route state on refresh:</strong> FAIL (always redirects to New Search, BUG-005)</li>
<li><strong>Stage changes persist:</strong> PASS (Supabase write confirmed)</li>
<li><strong>Search history persists:</strong> PASS (previous searches visible with timestamps)</li>
</ul>
</div>
</div>
</div>
<!-- TAB: Scoring Audit -->
<div class="tab-content" id="scoring">
<div class="section">
<div class="section-title">Scoring Formula (src/lib/scoring.ts)</div>
<div class="card">
<p style="margin-bottom:16px;">The scoring system uses a weighted boolean signal model with a max score of 100.</p>
<table>
<thead>
<tr><th>Signal</th><th>Weight</th><th>Source</th></tr>
</thead>
<tbody>
<tr><td>top_company</td><td style="font-family:var(--mono);color:var(--accent);">20</td><td>LLM enrichment or regex match against 12 top companies</td></tr>
<tr><td>has_phd</td><td style="font-family:var(--mono);color:var(--accent);">15</td><td>LLM enrichment or regex /phd|doctorate|doctoral/</td></tr>
<tr><td>has_publications</td><td style="font-family:var(--mono);color:var(--accent);">15</td><td>LLM enrichment or regex /publication|paper|published|journal/</td></tr>
<tr><td>open_source</td><td style="font-family:var(--mono);color:var(--accent);">10</td><td>LLM enrichment or regex /open.?source|github stars|contributor|maintainer/</td></tr>
<tr><td>conference_speaker</td><td style="font-family:var(--mono);color:var(--accent);">10</td><td>LLM enrichment or regex against 7 top conferences</td></tr>
<tr><td>has_patents</td><td style="font-family:var(--mono);color:var(--accent);">10</td><td>LLM enrichment or regex /patents?/</td></tr>
<tr><td>leadership_role</td><td style="font-family:var(--mono);color:var(--accent);">10</td><td>LLM enrichment or regex /founder|cto|vp|vice president|chief|director/</td></tr>
<tr><td>top_university</td><td style="font-family:var(--mono);color:var(--accent);">10</td><td>LLM enrichment or regex against 11 top universities</td></tr>
<tr><td>experience_bonus</td><td style="font-family:var(--mono);color:var(--accent);">5</td><td>10+ years experience</td></tr>
</tbody>
</table>
<p style="margin-top:16px;"><strong>Max possible:</strong> 105 (capped at 100). Color coding: green (80+), yellow (60-79), orange (40-59), red (<40).</p>
</div>
</div>
<div class="section">
<div class="section-title">Score Verification: Thomas Wolf</div>
<div class="card">
<p style="margin-bottom:12px;"><strong>Displayed Score: 98</strong> | <strong>EEA Score: 51</strong></p>
<p style="margin-bottom:12px;">The main score (98) is the GitHub-based relevance score, NOT the EEA score. These are two separate scoring systems:</p>
<ul style="margin: 12px 0 12px 20px; color: var(--text-dim);">
<li><strong>Score (98):</strong> AI relevance score based on GitHub activity, commit count, repo relevance, follower count, language match. This score is generated by the search/enrichment pipeline, not by scoring.ts.</li>
<li><strong>EEA (51):</strong> Evidence of Exceptional Ability score from scoring.ts. For Thomas Wolf: leadership_role (10, Co-founder) + open_source (10, transformers maintainer) + top_company (20, Hugging Face is likely matched) + experience_bonus (5, Since 2014 = 12 years) + possible other signals = plausible range 45-55.</li>
</ul>
<div class="callout callout-green">Both scoring systems appear internally consistent. The dual-score display is clear in the candidate cards (main score as large number, EEA as badge).</div>
</div>
</div>
</div>
<!-- TAB: Fix Prompts (P0+P1) -->
<div class="tab-content" id="fix-prompts">
<div class="section">
<div class="section-title">Claude Code Fix Prompts for P0 + P1 Issues</div>
<div class="prompt-card">
<div class="prompt-header">
<h3><span class="pill pill-p1">P1</span> BUG-001: Fix Outreach Generation TypeError</h3>
</div>
<div class="prompt-body">
<p>Console error: <code>Outreach generation failed: Error: Cannot read properties of undefined (reading 'name')</code></p>
<div class="code-block"><span class="comment">// Prompt for Claude Code:</span>
<span class="keyword">Fix the outreach generation TypeError in the candidate detail panel.</span>
<span class="string">Bug:</span> Clicking "Generate Outreach" in the candidate detail panel throws
"Cannot read properties of undefined (reading 'name')". The error fires
4-5 times per click (event handler leak).
<span class="string">Root cause:</span> The outreach generation function reads company/role context
(likely `strategy.company.name` or `role.name`) but this context is not
passed when opening the candidate detail from the Results page or Pipeline.
<span class="string">Fix required:</span>
1. Find the outreach generation handler (search for "Generate Outreach"
button onClick or the function that reads `.name`).
2. Add null-safe access: use optional chaining (`?.`) or provide fallback
values from the candidate's own data (e.g., current company from
enrichment).
3. Fix the event handler leak: ensure the click handler is attached once.
If using useEffect, add a cleanup return. If using onClick prop directly,
ensure the component isn't re-mounting multiple times.
4. Add a try/catch with a toast notification on failure so users see
feedback instead of silent failure.
5. Add a loading state to the button while generation is in progress.
<span class="string">Files to check:</span>
- Component rendering the "Generate Outreach" button (likely in
src/components/CandidateDetail or similar)
- The outreach generation service/function
- The search strategy context/store that should provide role + company
<span class="string">Test:</span> After fix, click "Generate Outreach" on any candidate from Results
and from Pipeline. Verify: (a) no console errors, (b) loading state shows,
(c) outreach text appears or error toast shows, (d) only 1 handler fires
per click.</div>
</div>
</div>
<div class="prompt-card">
<div class="prompt-header">
<h3><span class="pill pill-p1">P1</span> BUG-002: Fix First Search Race Condition</h3>
</div>
<div class="prompt-body">
<p>First "Search with this strategy" click returns 0 results. Retry from Results page succeeds.</p>
<div class="code-block"><span class="comment">// Prompt for Claude Code:</span>
<span class="keyword">Fix the race condition where the first search from the strategy page
returns 0 results.</span>
<span class="string">Bug:</span> After building a sourcing strategy, clicking "Search with this
strategy" navigates to the Results page but returns "No engineers found"
(0 results). Clicking the Search button again on the Results page
succeeds with 20 results.
<span class="string">Root cause hypothesis:</span> The strategy state (search query, target repos,
criteria) is passed via React state/context during navigation. The
Results component fires the search edge function call before the state
is fully populated, sending an empty or incomplete request.
<span class="string">Fix required:</span>
1. Find where "Search with this strategy" triggers navigation + search.
2. Ensure the search request only fires AFTER the strategy state is
confirmed available. Options:
a. Pass strategy as URL params or route state instead of context.
b. Add a useEffect in Results that watches for strategy state and
only triggers search when state is non-null.
c. Trigger the search from the strategy page BEFORE navigating,
store the request promise, and resolve it on the Results page.
3. Add a retry mechanism: if search returns 0 results and strategy
state is available, auto-retry once after 500ms.
<span class="string">Files to check:</span>
- Strategy page component (the "Search with this strategy" button handler)
- Results page component (the search trigger logic)
- The search state/context provider
- The github-search edge function call
<span class="string">Test:</span> Build a strategy for any role > click "Search with this strategy"
> verify results appear on first attempt without needing to retry.
Repeat 5x to confirm no intermittent failures.</div>
</div>
</div>
</div>
</div>
<!-- TAB: Improvement Prompts (P2+P3) -->
<div class="tab-content" id="improve-prompts">
<div class="section">
<div class="section-title">Improvement Prompts for P2 + P3 Issues</div>
<div class="prompt-card">
<div class="prompt-header">
<h3><span class="pill pill-p2">P2</span> BUG-003+004: Pipeline Stage Dropdown Fixes</h3>
</div>
<div class="prompt-body">
<div class="code-block"><span class="keyword">Fix the pipeline stage dropdown to show all 5 stages and update UI
on stage change.</span>
<span class="string">Issues:</span>
1. Stage dropdown on candidate detail (from Pipeline) only shows
"Contacted" and "Not Interested". Missing: Recruiter Screen, Rejected, Moved to ATS.
2. After selecting a new stage, the dropdown button text does not
update (still shows old stage), even though the change persists
in Supabase.
<span class="string">Fix:</span>
1. Find the stage dropdown component. Ensure it reads ALL stage
options from the same constant/enum used by the kanban columns:
["contacted", "not_interested", "recruiter_screen", "rejected", "moved_to_ats"].
2. After the Supabase upsert succeeds, update the local React state
for the selected stage. Use setState or invalidate the query cache
(if using React Query / TanStack Query).
<span class="string">Test:</span> Open candidate from Pipeline > dropdown shows all 5 stages >
select "Recruiter Screen" > button text updates to "Recruiter Screen" > go back to
kanban > candidate appears in Recruiter Screen column.</div>
</div>
</div>
<div class="prompt-card">
<div class="prompt-header">
<h3><span class="pill pill-p2">P2</span> BUG-005: Fix Route Persistence on Refresh</h3>
</div>
<div class="prompt-body">
<div class="code-block"><span class="keyword">Fix page refresh to maintain current route instead of redirecting
to New Search.</span>
<span class="string">Issue:</span> Pressing F5 on any page (Pipeline, Results, History, etc.)
always redirects to the New Search home page.
<span class="string">Fix options:</span>
1. If using react-router with BrowserRouter: ensure Vercel has a
rewrite rule in vercel.json:
{ "rewrites": [{ "source": "/(.*)", "destination": "/index.html" }] }
2. If using in-memory state for navigation (no URL changes): switch
to proper route paths (/pipeline, /results, /history, etc.) so
the URL reflects the current page.
3. If using HashRouter: this should already work. Check if the
router is wrapping the entire app correctly.
<span class="string">Test:</span> Navigate to Pipeline > press F5 > should stay on Pipeline.
Repeat for Results, History, Watchlist, Settings.</div>
</div>
</div>
<div class="prompt-card">
<div class="prompt-header">
<h3><span class="pill pill-p2">P2</span> BUG-006+007: Outreach Error Handling + Handler Leak</h3>
</div>
<div class="prompt-body">
<div class="code-block"><span class="keyword">Add error handling UI and fix event handler leak for outreach
generation.</span>
<span class="string">Issues:</span>
1. No loading spinner, success message, or error toast when outreach
generation runs or fails.
2. Click handler fires 4-5 times per single click.
<span class="string">Fix:</span>
1. Add loading state: set isGenerating=true on click, show spinner
on button, disable button during generation.
2. On success: display generated outreach text in a text area below
the button with a "Copy" button.
3. On error: show a toast/alert with a user-friendly message like
"Failed to generate outreach. Please try again."
4. Fix handler leak: if using addEventListener in useEffect, return
a cleanup function. Prefer onClick prop on the button element.
<span class="string">Test:</span> Click "Generate Outreach" > button shows spinner > on
completion, text appears or error toast shows. Check console: only
1 handler fires per click.</div>
</div>
</div>
<div class="prompt-card">
<div class="prompt-header">
<h3><span class="pill pill-p3">P3</span> BUG-008: History Result Count Accuracy</h3>
</div>
<div class="prompt-body">
<div class="code-block"><span class="keyword">Align history result counts with actual displayed results.</span>
<span class="string">Issue:</span> History shows "46 results" for a search that displays 20
candidates on the Results page.
<span class="string">Fix:</span> When writing to search_history table, store the count of
candidates that were actually processed and displayed, not the raw
GitHub API match count. Alternatively, show both: "46 found, 20
displayed" to set accurate expectations.</div>
</div>
</div>
<div class="prompt-card">
<div class="prompt-header">
<h3><span class="pill pill-p3">P3</span> BUG-009: Truncated Search Criteria Tags</h3>
</div>
<div class="prompt-body">
<div class="code-block"><span class="keyword">Add tooltips to truncated AI-parsed search criteria tags.</span>
<span class="string">Issue:</span> Tags like "Large language model dev..." are cut off with
ellipsis but have no tooltip showing the full text.
<span class="string">Fix:</span> Add a title attribute or a Tooltip component (shadcn/ui has
one) to each criteria tag showing the full text on hover.</div>
</div>
</div>
<div class="prompt-card">
<div class="prompt-header">
<h3><span class="pill pill-p3">P3</span> BUG-010: Kanban Drag-and-Drop Verification</h3>
</div>
<div class="prompt-body">
<div class="code-block"><span class="keyword">Verify and test kanban drag-and-drop functionality manually.</span>
<span class="string">Issue:</span> Automated testing could not confirm drag-and-drop works on
the kanban board. Drop zones display "Drop candidates here" text.
<span class="string">Action:</span> Manually test drag-and-drop in Chrome. If it works, no fix
needed. If not, ensure the DnD library (likely @dnd-kit or
react-beautiful-dnd) is properly configured with droppable zones
and draggable items. The onDragEnd handler should call the same
Supabase update as the stage dropdown.</div>
</div>
</div>
</div>
</div>
</div>
<script>
document.querySelectorAll('.tab').forEach(tab => {
tab.addEventListener('click', () => {
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
document.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active'));
tab.classList.add('active');
document.getElementById(tab.dataset.tab).classList.add('active');
});
});
</script>
</body>
</html>