@in-the-loop-labs/pair-review 3.4.1 → 3.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/public/css/pr.css CHANGED
@@ -710,6 +710,15 @@
710
710
  --comment-subtle: rgba(130, 80, 223, 0.08);
711
711
  --comment-border: rgba(130, 80, 223, 0.3);
712
712
 
713
+ /* External review-comment accent colors - blue, themed per source.
714
+ One block per source so future sources (GitLab, Linear, etc.) get
715
+ their own palette without renaming classes. The .source-<name> rule
716
+ on .external-comment maps these into --ec-* locals. */
717
+ --external-github-primary: #0969da;
718
+ --external-github-secondary: #0860c5;
719
+ --external-github-subtle: rgba(9, 105, 218, 0.08);
720
+ --external-github-border: rgba(9, 105, 218, 0.3);
721
+
713
722
  /* Reasoning toggle hover - deep purple for light backgrounds */
714
723
  --color-reasoning-hover: #7c3aed;
715
724
  --color-reasoning-hover-bg: rgba(124, 58, 237, 0.1);
@@ -807,6 +816,13 @@
807
816
  --comment-subtle: rgba(163, 113, 247, 0.1);
808
817
  --comment-border: rgba(163, 113, 247, 0.3);
809
818
 
819
+ /* External review-comment accent colors - blue, themed per source (dark theme).
820
+ Mirrors the light-theme block above. */
821
+ --external-github-primary: #58a6ff;
822
+ --external-github-secondary: #79b8ff;
823
+ --external-github-subtle: rgba(88, 166, 255, 0.1);
824
+ --external-github-border: rgba(88, 166, 255, 0.3);
825
+
810
826
  /* Reasoning toggle hover - rich purple for dark backgrounds */
811
827
  --color-reasoning-hover: #9333ea;
812
828
  --color-reasoning-hover-bg: rgba(147, 51, 234, 0.12);
@@ -4676,6 +4692,292 @@ tr.line-range-start .d2h-code-line-ctn {
4676
4692
  color: #8b949e;
4677
4693
  }
4678
4694
 
4695
+ /* ========================================
4696
+ External Review Comments Display
4697
+ Read-only mirror of comments from external systems (GitHub PR review
4698
+ comments first; GitLab/Linear/etc. land as per-source rules below).
4699
+ Structural rules are source-agnostic; color comes via the --ec-*
4700
+ indirection set by .source-<name>.
4701
+ ======================================== */
4702
+ .external-comment-row {
4703
+ border: none;
4704
+ }
4705
+
4706
+ .external-comment-cell {
4707
+ padding: 4px 0;
4708
+ background: transparent;
4709
+ }
4710
+
4711
+ /* Source-agnostic defaults. .source-<name> rules below override these
4712
+ indirection variables, and every internal rule references --ec-* so
4713
+ adding a new source is just a new variable block + .source-<name>. */
4714
+ .external-comment {
4715
+ --ec-primary: var(--comment-primary);
4716
+ --ec-secondary: var(--comment-primary);
4717
+ --ec-subtle: var(--comment-subtle);
4718
+ --ec-border: var(--comment-border);
4719
+
4720
+ background: linear-gradient(to right, var(--ec-subtle) 0%, var(--color-bg-primary, #ffffff) 100%);
4721
+ border: 1px solid var(--ec-border);
4722
+ border-left: 4px solid var(--ec-primary);
4723
+ border-radius: 6px;
4724
+ padding: 8px 10px;
4725
+ margin: 0;
4726
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
4727
+ position: relative;
4728
+ overflow: hidden;
4729
+ word-break: break-word;
4730
+ box-sizing: border-box;
4731
+ }
4732
+
4733
+ .external-comment:hover {
4734
+ border-color: var(--ec-border);
4735
+ box-shadow: 0 2px 6px rgba(0, 0, 0, 0.08);
4736
+ }
4737
+
4738
+ /* Per-source color binding. Each rule maps the source's primary/border/
4739
+ subtle variables into the agnostic --ec-* locals. */
4740
+ .external-comment.source-github {
4741
+ --ec-primary: var(--external-github-primary);
4742
+ --ec-secondary: var(--external-github-secondary);
4743
+ --ec-subtle: var(--external-github-subtle);
4744
+ --ec-border: var(--external-github-border);
4745
+ }
4746
+
4747
+ .external-comment-header {
4748
+ display: flex;
4749
+ align-items: center;
4750
+ justify-content: space-between;
4751
+ margin-bottom: 4px;
4752
+ padding-bottom: 4px;
4753
+ border-bottom: 1px solid var(--ec-border);
4754
+ min-width: 0;
4755
+ gap: 8px;
4756
+ }
4757
+
4758
+ .external-comment-header-left {
4759
+ display: flex;
4760
+ align-items: center;
4761
+ gap: 8px;
4762
+ min-width: 0;
4763
+ overflow: hidden;
4764
+ }
4765
+
4766
+ .external-comment-author {
4767
+ color: var(--ec-primary);
4768
+ font-weight: 600;
4769
+ font-size: 13px;
4770
+ text-decoration: none;
4771
+ overflow: hidden;
4772
+ text-overflow: ellipsis;
4773
+ white-space: nowrap;
4774
+ }
4775
+
4776
+ .external-comment-author:hover {
4777
+ text-decoration: underline;
4778
+ }
4779
+
4780
+ .external-comment-permalink {
4781
+ display: inline-flex;
4782
+ align-items: center;
4783
+ justify-content: center;
4784
+ color: var(--color-text-secondary, #57606a);
4785
+ text-decoration: none;
4786
+ padding: 2px;
4787
+ border-radius: 4px;
4788
+ transition: color 0.2s, background 0.2s;
4789
+ }
4790
+
4791
+ .external-comment-permalink:hover {
4792
+ color: var(--ec-primary);
4793
+ background: var(--ec-subtle);
4794
+ }
4795
+
4796
+ .external-comment-permalink svg {
4797
+ width: 14px;
4798
+ height: 14px;
4799
+ fill: currentColor;
4800
+ }
4801
+
4802
+ .external-comment-body {
4803
+ color: var(--color-text-primary, #1f2328);
4804
+ line-height: 1.4;
4805
+ font-size: 14px;
4806
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif;
4807
+ padding: 0;
4808
+ margin: 0;
4809
+ word-break: break-word;
4810
+ }
4811
+
4812
+ .external-comment-body p {
4813
+ margin: 0 0 6px 0;
4814
+ }
4815
+
4816
+ .external-comment-body p:last-child {
4817
+ margin-bottom: 0;
4818
+ }
4819
+
4820
+ .external-comment-body code {
4821
+ background: var(--ec-subtle);
4822
+ color: var(--ec-secondary);
4823
+ padding: 1px 4px;
4824
+ border-radius: 3px;
4825
+ font-size: 85%;
4826
+ font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
4827
+ }
4828
+
4829
+ .external-comment-body pre {
4830
+ background: var(--color-bg-secondary, #f6f8fa);
4831
+ border: 1px solid var(--ec-border);
4832
+ border-radius: 6px;
4833
+ padding: 8px;
4834
+ overflow-x: auto;
4835
+ margin: 6px 0;
4836
+ }
4837
+
4838
+ .external-comment-body pre code {
4839
+ background: transparent;
4840
+ color: inherit;
4841
+ padding: 0;
4842
+ }
4843
+
4844
+ .external-comment-body ul,
4845
+ .external-comment-body ol {
4846
+ margin: 8px 0;
4847
+ padding-left: 24px;
4848
+ }
4849
+
4850
+ .external-comment-body li {
4851
+ margin: 4px 0;
4852
+ }
4853
+
4854
+ .external-comment-body strong {
4855
+ font-weight: 600;
4856
+ }
4857
+
4858
+ .external-comment-body em {
4859
+ font-style: italic;
4860
+ }
4861
+
4862
+ .external-comment-body blockquote {
4863
+ border-left: 4px solid var(--color-border-primary);
4864
+ padding-left: 16px;
4865
+ margin: 8px 0;
4866
+ color: var(--color-text-secondary);
4867
+ }
4868
+
4869
+ .external-comment-header-right {
4870
+ display: flex;
4871
+ align-items: center;
4872
+ gap: 4px;
4873
+ flex-shrink: 0;
4874
+ }
4875
+
4876
+ .external-comment .btn-chat-comment {
4877
+ color: var(--ec-primary);
4878
+ }
4879
+
4880
+ .external-comment .btn-chat-comment:hover {
4881
+ background: var(--ec-subtle);
4882
+ border-color: var(--ec-border);
4883
+ color: var(--ec-primary);
4884
+ }
4885
+
4886
+ [data-theme="dark"] .external-comment .btn-chat-comment {
4887
+ color: var(--ec-primary);
4888
+ }
4889
+
4890
+ [data-theme="dark"] .external-comment .btn-chat-comment:hover {
4891
+ background: var(--ec-subtle);
4892
+ border-color: var(--ec-border);
4893
+ color: var(--ec-primary);
4894
+ }
4895
+
4896
+ /* Thread = root comment + replies, grouped in one card stack.
4897
+ The thread owns the width constraint so the root, replies, and the
4898
+ thread-actions row all share the same right edge. */
4899
+ .external-comment-thread {
4900
+ display: flex;
4901
+ flex-direction: column;
4902
+ gap: 4px;
4903
+ max-width: calc(100vw - var(--sidebar-width) - var(--right-panel-group-width, 0px) - 64px);
4904
+ margin: 6px auto;
4905
+ box-sizing: border-box;
4906
+ }
4907
+
4908
+ /* One level of indent for replies. GitHub treats threads as
4909
+ flat-with-replies; no deeper nesting needed. */
4910
+ .external-comment.is-reply {
4911
+ margin-left: 24px;
4912
+ background: linear-gradient(to right, var(--ec-subtle) 0%, var(--color-bg-secondary, #f6f8fa) 100%);
4913
+ }
4914
+
4915
+ /* Outdated: comment's anchor no longer maps to current diff. Faded with
4916
+ a dashed accent border so it reads as "still here but stale". */
4917
+ .external-comment.is-outdated {
4918
+ opacity: 0.7;
4919
+ border-left-style: dashed;
4920
+ }
4921
+
4922
+ .external-comment-outdated-badge {
4923
+ display: inline-block;
4924
+ padding: 2px 6px;
4925
+ border-radius: 4px;
4926
+ font-size: 10px;
4927
+ font-weight: 600;
4928
+ background: var(--color-bg-secondary, #f6f8fa);
4929
+ color: var(--color-text-secondary, #57606a);
4930
+ border: 1px solid var(--color-border-primary, #d0d7de);
4931
+ text-transform: uppercase;
4932
+ letter-spacing: 0.04em;
4933
+ }
4934
+
4935
+ [data-theme="dark"] .external-comment {
4936
+ background: linear-gradient(to right, var(--ec-subtle) 0%, var(--color-bg-primary, #0d1117) 100%);
4937
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
4938
+ }
4939
+
4940
+ [data-theme="dark"] .external-comment.is-reply {
4941
+ background: linear-gradient(to right, var(--ec-subtle) 0%, var(--color-bg-secondary, #161b22) 100%);
4942
+ }
4943
+
4944
+ [data-theme="dark"] .external-comment-body {
4945
+ color: var(--color-text-primary, #e6edf3);
4946
+ }
4947
+
4948
+ [data-theme="dark"] .external-comment-body pre {
4949
+ background: var(--color-bg-primary, #0d1117);
4950
+ }
4951
+
4952
+ [data-theme="dark"] .external-comment-outdated-badge {
4953
+ background: var(--color-bg-tertiary, #1c2128);
4954
+ color: var(--color-text-secondary, #8b949e);
4955
+ border-color: var(--color-border-primary, #30363d);
4956
+ }
4957
+
4958
+ /* Refresh button spinner: reuse the project-wide `spin` keyframes (defined
4959
+ above for #refresh-pr and elsewhere). Triggered by JS toggling
4960
+ `.is-refreshing` on the button while a sync is in flight. */
4961
+ .btn-refresh-external-comments.is-refreshing svg {
4962
+ animation: spin 1s linear infinite;
4963
+ }
4964
+
4965
+ /* Brief error state on the refresh button — JS sets data-state='error' for a
4966
+ couple seconds when sync fails so the reviewer notices even if the toast
4967
+ was dismissed. */
4968
+ .btn-refresh-external-comments[data-state="error"] {
4969
+ color: var(--color-danger, #d1242f);
4970
+ }
4971
+
4972
+ /* Feature toggle: when `external_comments` is false in config, the
4973
+ /runtime-config.js script adds `external-comments-disabled` to
4974
+ documentElement before AIPanel/pr.js construct. Any element marked
4975
+ `.external-comments-only` is hidden synchronously so the user never sees
4976
+ external-related controls flicker into view. */
4977
+ :root.external-comments-disabled .external-comments-only {
4978
+ display: none !important;
4979
+ }
4980
+
4679
4981
  /* ========================================
4680
4982
  Dismissed User Comment Styling
4681
4983
  Visual state for soft-deleted comments when "show dismissed" is enabled
@@ -7499,15 +7801,18 @@ body.resizing * {
7499
7801
  Comment Minimize Mode
7500
7802
  -------------------------------------------------------------------------- */
7501
7803
 
7502
- /* When minimize mode is active, hide all inline comment and suggestion rows */
7804
+ /* When minimize mode is active, hide all inline comment, suggestion, and
7805
+ external-comment rows */
7503
7806
  .comments-minimized .user-comment-row,
7504
- .comments-minimized .ai-suggestion-row {
7807
+ .comments-minimized .ai-suggestion-row,
7808
+ .comments-minimized .external-comment-row {
7505
7809
  display: none;
7506
7810
  }
7507
7811
 
7508
7812
  /* Per-line expansion override — clicking an indicator reveals that line's rows */
7509
7813
  .comments-minimized .user-comment-row.comment-expanded,
7510
- .comments-minimized .ai-suggestion-row.comment-expanded {
7814
+ .comments-minimized .ai-suggestion-row.comment-expanded,
7815
+ .comments-minimized .external-comment-row.comment-expanded {
7511
7816
  display: table-row;
7512
7817
  }
7513
7818
 
@@ -7670,6 +7975,24 @@ body.resizing * {
7670
7975
  box-shadow: 0 1px 3px rgba(217, 119, 6, 0.15);
7671
7976
  }
7672
7977
 
7978
+ /* External review-comment indicator (blue). Currently sourced from GitHub
7979
+ inline review comments; future GitLab/Linear adapters will reuse the
7980
+ same `.indicator-external` class with their own color tokens defined in
7981
+ .external-comment.source-<name>. */
7982
+ .comment-indicator .indicator-external {
7983
+ color: var(--external-github-primary, #0969da);
7984
+ }
7985
+
7986
+ .comment-indicator:has(.indicator-external) {
7987
+ border-color: var(--external-github-border, rgba(9, 105, 218, 0.3));
7988
+ background: var(--external-github-subtle, rgba(9, 105, 218, 0.08));
7989
+ }
7990
+
7991
+ .comment-indicator:has(.indicator-external):hover {
7992
+ background: rgba(9, 105, 218, 0.16);
7993
+ box-shadow: 0 1px 3px rgba(9, 105, 218, 0.18);
7994
+ }
7995
+
7673
7996
  .comment-indicator .indicator-count {
7674
7997
  font-weight: 600;
7675
7998
  font-size: 10px;
@@ -7718,6 +8041,20 @@ body.resizing * {
7718
8041
  box-shadow: 0 1px 3px rgba(251, 191, 36, 0.25);
7719
8042
  }
7720
8043
 
8044
+ [data-theme="dark"] .comment-indicator:has(.indicator-external) {
8045
+ border-color: var(--external-github-border, rgba(88, 166, 255, 0.3));
8046
+ background: var(--external-github-subtle, rgba(88, 166, 255, 0.1));
8047
+ }
8048
+
8049
+ [data-theme="dark"] .comment-indicator .indicator-external {
8050
+ color: var(--external-github-primary, #58a6ff);
8051
+ }
8052
+
8053
+ [data-theme="dark"] .comment-indicator:has(.indicator-external):hover {
8054
+ background: rgba(88, 166, 255, 0.18);
8055
+ box-shadow: 0 1px 3px rgba(88, 166, 255, 0.25);
8056
+ }
8057
+
7721
8058
  [data-theme="dark"] .comment-indicator.expanded {
7722
8059
  border-width: 2px;
7723
8060
  padding: 1px 5px;
@@ -8510,6 +8847,7 @@ body.resizing * {
8510
8847
 
8511
8848
  /* Segment Control */
8512
8849
  .segment-control {
8850
+ position: relative;
8513
8851
  display: flex;
8514
8852
  align-items: center;
8515
8853
  gap: 8px;
@@ -8519,6 +8857,23 @@ body.resizing * {
8519
8857
  flex-shrink: 0;
8520
8858
  }
8521
8859
 
8860
+ /* Scroll container: clips the segment row at the panel edge so chevrons
8861
+ can drive horizontal scroll when the buttons don't fit. Wrapped around
8862
+ .segment-control-inner so the inner row keeps its existing pill styling. */
8863
+ .segment-control-scroll {
8864
+ flex: 1;
8865
+ min-width: 0; /* allow shrink below intrinsic content width */
8866
+ overflow-x: auto;
8867
+ overflow-y: hidden;
8868
+ scroll-behavior: smooth;
8869
+ scrollbar-width: none; /* Firefox */
8870
+ -ms-overflow-style: none; /* IE/Edge */
8871
+ }
8872
+
8873
+ .segment-control-scroll::-webkit-scrollbar {
8874
+ display: none; /* Chrome/Safari */
8875
+ }
8876
+
8522
8877
  .segment-control-inner {
8523
8878
  display: flex;
8524
8879
  gap: 2px;
@@ -8526,12 +8881,48 @@ body.resizing * {
8526
8881
  background: var(--color-bg-tertiary);
8527
8882
  border: 1px solid var(--color-border-primary);
8528
8883
  border-radius: var(--radius-md);
8529
- flex: 1;
8884
+ /* No flex:1 here — let the inner row take its natural width so we can
8885
+ scroll horizontally when it overflows the scroll container. */
8886
+ width: max-content;
8887
+ min-width: 100%;
8888
+ }
8889
+
8890
+ /* Chevron buttons that scroll the segment row left/right. Hidden by
8891
+ default; JS toggles [hidden] based on scrollWidth / scrollLeft. */
8892
+ .segment-scroll {
8893
+ flex-shrink: 0;
8894
+ width: 22px;
8895
+ height: 22px;
8896
+ padding: 0;
8897
+ display: inline-flex;
8898
+ align-items: center;
8899
+ justify-content: center;
8900
+ background: var(--color-bg-tertiary);
8901
+ border: 1px solid var(--color-border-primary);
8902
+ border-radius: var(--radius-sm);
8903
+ color: var(--color-text-secondary);
8904
+ cursor: pointer;
8905
+ transition: background var(--transition-fast), color var(--transition-fast), border-color var(--transition-fast);
8906
+ }
8907
+
8908
+ .segment-scroll:hover {
8909
+ background: var(--color-bg-primary);
8910
+ color: var(--color-text-primary);
8911
+ border-color: var(--color-border-secondary);
8912
+ }
8913
+
8914
+ .segment-scroll[hidden] {
8915
+ display: none;
8530
8916
  }
8531
8917
 
8532
8918
  .segment-btn {
8533
- flex: 1;
8534
- padding: 5px 4px;
8919
+ /* flex: 1 0 auto = grow to share extra space, never shrink below natural
8920
+ width, use intrinsic content width as the basis. When the sum of
8921
+ natural widths exceeds the scroll container, the inner row overflows
8922
+ and the chevrons take over. When there is extra room, the buttons
8923
+ distribute it evenly. */
8924
+ flex: 1 0 auto;
8925
+ padding: 5px 8px;
8535
8926
  font-family: inherit;
8536
8927
  font-size: 0.6875rem;
8537
8928
  font-weight: 500;
@@ -8542,6 +8933,11 @@ body.resizing * {
8542
8933
  cursor: pointer;
8543
8934
  transition: all var(--transition-fast);
8544
8935
  white-space: nowrap;
8936
+ text-align: center;
8937
+ }
8938
+
8939
+ .segment-btn[hidden] {
8940
+ display: none;
8545
8941
  }
8546
8942
 
8547
8943
  .segment-btn:hover {
@@ -8565,6 +8961,15 @@ body.resizing * {
8565
8961
  background: var(--comment-subtle);
8566
8962
  }
8567
8963
 
8964
+ /* External segment uses the GitHub external-comment blue. When future
8965
+ sources (GitLab, Linear) land, this rule can switch on a body-level
8966
+ class or rely on JS to toggle a modifier — for now GitHub is the only
8967
+ external source and the blue accent is shared. */
8968
+ .segment-btn[data-segment="external"].active {
8969
+ color: var(--external-github-primary);
8970
+ background: var(--external-github-subtle);
8971
+ }
8972
+
8568
8973
  .segment-btn[data-segment="all"].active {
8569
8974
  color: var(--color-text-primary);
8570
8975
  background: var(--color-bg-tertiary);
@@ -8696,6 +9101,16 @@ body.resizing * {
8696
9101
  gap: 4px;
8697
9102
  }
8698
9103
 
9104
+ /* Right-aligned actions slot in the findings header (PR mode: refresh
9105
+ external comments). Kept as a static sibling of .findings-nav so the
9106
+ bound click handler survives header re-renders. Empty in Local mode. */
9107
+ .findings-header-actions {
9108
+ display: flex;
9109
+ align-items: center;
9110
+ gap: 4px;
9111
+ margin-left: auto;
9112
+ }
9113
+
8699
9114
  .findings-nav-btn {
8700
9115
  display: flex;
8701
9116
  align-items: center;
@@ -9105,6 +9520,121 @@ body.resizing * {
9105
9520
  The parent wrapper handles opacity transitions, so no special styles needed here.
9106
9521
  This selector remains for specificity in case of future customization needs. */
9107
9522
 
9523
+ /* ========================================
9524
+ Review panel: External thread list items
9525
+ Mirrors the .external-comment indirection pattern: source-agnostic
9526
+ structural rules use --ec-*, then .source-<name> binds those to the
9527
+ per-source palette. Today only .source-github exists; future sources
9528
+ (GitLab, Linear) drop in a single block below.
9529
+ ======================================== */
9530
+ .finding-item.ai-panel__list-item--external {
9531
+ /* Default --ec-* fallback if a thread renders without source-<name>.
9532
+ Real threads always carry .source-github (set by the renderer). */
9533
+ --ec-primary: var(--comment-primary);
9534
+ --ec-subtle: var(--comment-subtle);
9535
+ --ec-border: var(--comment-border);
9536
+ --ec-secondary: var(--comment-primary);
9537
+
9538
+ /* Set the generic border color first, then restore the left accent so the
9539
+ shorthand-vs-longhand cascade doesn't wipe out the primary-source stripe. */
9540
+ border-color: var(--ec-border);
9541
+ border-left: 3px solid var(--ec-primary);
9542
+ padding-left: 9px;
9543
+ background: var(--ec-subtle);
9544
+ position: relative;
9545
+ }
9546
+
9547
+ .finding-item.ai-panel__list-item--external:hover {
9548
+ background: var(--ec-subtle);
9549
+ border-color: var(--ec-border);
9550
+ border-left-color: var(--ec-primary);
9551
+ /* Slight contrast bump on hover via box-shadow rather than color shift,
9552
+ so the blue accent doesn't fight the surrounding panel. */
9553
+ box-shadow: inset 0 0 0 1px var(--ec-border);
9554
+ }
9555
+
9556
+ .finding-item.ai-panel__list-item--external.active {
9557
+ background: var(--ec-subtle);
9558
+ border-color: var(--ec-primary);
9559
+ box-shadow: inset 0 0 0 1px var(--ec-primary);
9560
+ }
9561
+
9562
+ .finding-item.ai-panel__list-item--external.source-github {
9563
+ --ec-primary: var(--external-github-primary);
9564
+ --ec-secondary: var(--external-github-secondary);
9565
+ --ec-subtle: var(--external-github-subtle);
9566
+ --ec-border: var(--external-github-border);
9567
+ }
9568
+
9569
+ /* Outdated thread: faded + dashed accent, matching the inline
9570
+ .external-comment.is-outdated treatment. */
9571
+ .finding-item.ai-panel__list-item--external.is-outdated {
9572
+ opacity: 0.7;
9573
+ border-left-style: dashed;
9574
+ }
9575
+
9576
+ .finding-item.ai-panel__list-item--external .external-list-author {
9577
+ font-weight: 600;
9578
+ color: var(--ec-primary);
9579
+ }
9580
+
9581
+ .finding-item.ai-panel__list-item--external .external-list-snippet {
9582
+ color: var(--color-text-secondary);
9583
+ font-weight: 400;
9584
+ }
9585
+
9586
+ /* Total-comment count for an external thread (root + replies). Replaces
9587
+ the prior left-side dot + right-side reply badge with a single always-
9588
+ present pill that reads "1" for a lone comment and ticks up with the
9589
+ thread size. */
9590
+ .finding-item.ai-panel__list-item--external .external-list-count {
9591
+ display: inline-flex;
9592
+ align-items: center;
9593
+ justify-content: center;
9594
+ min-width: 18px;
9595
+ height: 18px;
9596
+ padding: 0 5px;
9597
+ border-radius: 9px;
9598
+ font-size: 10px;
9599
+ font-weight: 600;
9600
+ background: var(--ec-primary);
9601
+ color: var(--color-bg-primary, #ffffff);
9602
+ line-height: 1;
9603
+ flex-shrink: 0;
9604
+ }
9605
+
9606
+ .finding-item.ai-panel__list-item--external .external-list-outdated-badge {
9607
+ display: inline-block;
9608
+ padding: 1px 5px;
9609
+ border-radius: 4px;
9610
+ font-size: 9px;
9611
+ font-weight: 600;
9612
+ background: var(--color-bg-secondary, #f6f8fa);
9613
+ color: var(--color-text-secondary, #57606a);
9614
+ border: 1px solid var(--color-border-primary, #d0d7de);
9615
+ text-transform: uppercase;
9616
+ letter-spacing: 0.04em;
9617
+ }
9618
+
9619
+ /* Transient focus flash for the matching inline external-comment-row after
9620
+ the user clicks an External list item. Removed automatically by JS after
9621
+ 2 seconds, mirroring the .current-suggestion / .highlight-flash pattern. */
9622
+ .external-comment-row--focused .external-comment {
9623
+ outline: 2px solid var(--ec-primary);
9624
+ outline-offset: 2px;
9625
+ transition: outline-color 200ms ease;
9626
+ }
9627
+
9628
+ [data-theme="dark"] .finding-item.ai-panel__list-item--external .external-list-count {
9629
+ color: var(--color-bg-primary, #0d1117);
9630
+ }
9631
+
9632
+ [data-theme="dark"] .finding-item.ai-panel__list-item--external .external-list-outdated-badge {
9633
+ background: var(--color-bg-tertiary, #1c2128);
9634
+ color: var(--color-text-secondary, #8b949e);
9635
+ border-color: var(--color-border-primary, #30363d);
9636
+ }
9637
+
9108
9638
  /* Adopted findings - brighter green success state with solid left border */
9109
9639
  .finding-item.finding-adopted {
9110
9640
  background: rgba(46, 160, 67, 0.15);
@@ -11772,6 +12302,32 @@ body.resizing * {
11772
12302
  background: var(--color-bg-secondary, #f6f8fa);
11773
12303
  }
11774
12304
 
12305
+ /* Already-open sessions in another tab — reduced opacity + open tag */
12306
+ .chat-panel__session-item--open .chat-panel__session-preview,
12307
+ .chat-panel__session-item--open .chat-panel__session-meta {
12308
+ opacity: 0.55;
12309
+ }
12310
+
12311
+ .chat-panel__session-open-tag {
12312
+ display: inline-block;
12313
+ margin-left: 6px;
12314
+ padding: 0 5px;
12315
+ font-size: 10px;
12316
+ font-weight: 500;
12317
+ line-height: 14px;
12318
+ color: var(--color-text-secondary, #59636e);
12319
+ background: var(--color-bg-tertiary, rgba(128, 128, 128, 0.12));
12320
+ border-radius: 3px;
12321
+ vertical-align: middle;
12322
+ text-transform: uppercase;
12323
+ letter-spacing: 0.03em;
12324
+ }
12325
+
12326
+ [data-theme="dark"] .chat-panel__session-open-tag {
12327
+ color: var(--color-text-secondary, #8b949e);
12328
+ background: rgba(128, 128, 128, 0.18);
12329
+ }
12330
+
11775
12331
  .chat-panel__session-item--active::before {
11776
12332
  content: '';
11777
12333
  position: absolute;
@@ -11908,6 +12464,180 @@ body.resizing * {
11908
12464
  color: var(--color-text-primary);
11909
12465
  }
11910
12466
 
12467
+ /* Chat Tab Strip */
12468
+ .chat-panel__tab-strip {
12469
+ display: flex;
12470
+ align-items: stretch;
12471
+ gap: 4px;
12472
+ padding: 4px 8px;
12473
+ border-bottom: 1px solid var(--chat-border);
12474
+ background: var(--color-bg-secondary, transparent);
12475
+ flex-shrink: 0;
12476
+ overflow-x: hidden;
12477
+ }
12478
+
12479
+ .chat-panel__tab-strip--empty {
12480
+ /* Keep the strip visible (so the + button is always reachable) */
12481
+ }
12482
+
12483
+ .chat-panel__tab-strip-items {
12484
+ display: flex;
12485
+ align-items: stretch;
12486
+ gap: 4px;
12487
+ flex: 1;
12488
+ min-width: 0;
12489
+ overflow-x: auto;
12490
+ scrollbar-width: none;
12491
+ }
12492
+
12493
+ .chat-panel__tab-strip-items::-webkit-scrollbar {
12494
+ display: none;
12495
+ }
12496
+
12497
+ .chat-panel__tab {
12498
+ display: inline-flex;
12499
+ align-items: center;
12500
+ gap: 6px;
12501
+ padding: 4px 8px 4px 10px;
12502
+ font-size: 12px;
12503
+ line-height: 1.4;
12504
+ color: var(--color-text-secondary, #59636e);
12505
+ background: transparent;
12506
+ border: 1px solid transparent;
12507
+ border-radius: 6px 6px 0 0;
12508
+ cursor: pointer;
12509
+ max-width: 180px;
12510
+ min-width: 70px;
12511
+ user-select: none;
12512
+ position: relative;
12513
+ transition: background 0.12s ease, color 0.12s ease, border-color 0.12s ease;
12514
+ }
12515
+
12516
+ .chat-panel__tab:hover {
12517
+ background: var(--color-bg-tertiary, rgba(128, 128, 128, 0.08));
12518
+ color: var(--color-text-primary, #1f2328);
12519
+ }
12520
+
12521
+ .chat-panel__tab--active {
12522
+ color: var(--color-text-primary, #1f2328);
12523
+ background: var(--color-bg-primary, #fff);
12524
+ border-color: var(--chat-border, var(--color-border-primary, #d0d7de));
12525
+ border-bottom-color: var(--color-bg-primary, #fff);
12526
+ margin-bottom: -1px;
12527
+ z-index: 1;
12528
+ }
12529
+
12530
+ [data-theme="dark"] .chat-panel__tab--active {
12531
+ background: var(--color-bg-primary, #0d1117);
12532
+ border-bottom-color: var(--color-bg-primary, #0d1117);
12533
+ }
12534
+
12535
+ .chat-panel__tab-dot {
12536
+ width: 8px;
12537
+ height: 8px;
12538
+ border-radius: 50%;
12539
+ flex-shrink: 0;
12540
+ background: var(--color-accent-primary);
12541
+ box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.04) inset;
12542
+ }
12543
+
12544
+ .chat-panel__tab-dot--pending { background: #8c959f; }
12545
+ .chat-panel__tab-dot--idle { background: var(--color-accent-primary); }
12546
+ .chat-panel__tab-dot--streaming {
12547
+ background: #d97706;
12548
+ animation: chat-tab-dot-pulse 1.2s ease-in-out infinite;
12549
+ }
12550
+ .chat-panel__tab-dot--error { background: #cf222e; }
12551
+
12552
+ [data-theme="dark"] .chat-panel__tab-dot--pending { background: #6e7681; }
12553
+ [data-theme="dark"] .chat-panel__tab-dot--streaming { background: #d29922; }
12554
+ [data-theme="dark"] .chat-panel__tab-dot--error { background: #f85149; }
12555
+
12556
+ @keyframes chat-tab-dot-pulse {
12557
+ 0%, 100% { opacity: 1; transform: scale(1); }
12558
+ 50% { opacity: 0.55; transform: scale(0.85); }
12559
+ }
12560
+
12561
+ .chat-panel__tab-title {
12562
+ white-space: nowrap;
12563
+ overflow: hidden;
12564
+ text-overflow: ellipsis;
12565
+ flex: 1;
12566
+ min-width: 0;
12567
+ font-weight: 500;
12568
+ }
12569
+
12570
+ .chat-panel__tab-close {
12571
+ display: inline-flex;
12572
+ align-items: center;
12573
+ justify-content: center;
12574
+ width: 16px;
12575
+ height: 16px;
12576
+ padding: 0;
12577
+ border: none;
12578
+ background: transparent;
12579
+ color: inherit;
12580
+ border-radius: 4px;
12581
+ opacity: 0.55;
12582
+ cursor: pointer;
12583
+ flex-shrink: 0;
12584
+ transition: background 0.12s ease, opacity 0.12s ease;
12585
+ }
12586
+
12587
+ .chat-panel__tab-close:hover {
12588
+ background: var(--color-bg-tertiary, rgba(128, 128, 128, 0.12));
12589
+ opacity: 1;
12590
+ }
12591
+
12592
+ .chat-panel__tab-new-btn {
12593
+ display: inline-flex;
12594
+ align-items: center;
12595
+ justify-content: center;
12596
+ width: 24px;
12597
+ height: 24px;
12598
+ padding: 0;
12599
+ align-self: center;
12600
+ border: 1px dashed var(--color-border-primary, #d0d7de);
12601
+ background: transparent;
12602
+ color: var(--color-text-secondary, #59636e);
12603
+ border-radius: 6px;
12604
+ cursor: pointer;
12605
+ flex-shrink: 0;
12606
+ transition: background 0.12s ease, color 0.12s ease, border-color 0.12s ease;
12607
+ }
12608
+
12609
+ .chat-panel__tab-new-btn:hover {
12610
+ background: var(--color-bg-tertiary, rgba(128, 128, 128, 0.08));
12611
+ color: var(--color-text-primary, #1f2328);
12612
+ border-color: var(--color-text-tertiary, #6e7681);
12613
+ }
12614
+
12615
+ .chat-panel__empty-new-btn {
12616
+ margin-top: 12px;
12617
+ padding: 6px 14px;
12618
+ font-size: 12px;
12619
+ font-weight: 500;
12620
+ color: var(--color-text-primary, #1f2328);
12621
+ background: var(--color-bg-secondary, #f6f8fa);
12622
+ border: 1px solid var(--color-border-primary, #d0d7de);
12623
+ border-radius: 6px;
12624
+ cursor: pointer;
12625
+ transition: background 0.12s ease, border-color 0.12s ease;
12626
+ }
12627
+
12628
+ .chat-panel__empty-new-btn:hover {
12629
+ background: var(--color-bg-tertiary, rgba(128, 128, 128, 0.08));
12630
+ border-color: var(--color-text-tertiary, #6e7681);
12631
+ }
12632
+
12633
+ .chat-panel__tab:focus-visible,
12634
+ .chat-panel__tab-close:focus-visible,
12635
+ .chat-panel__tab-new-btn:focus-visible,
12636
+ .chat-panel__empty-new-btn:focus-visible {
12637
+ outline: 2px solid var(--color-accent-primary);
12638
+ outline-offset: 1px;
12639
+ }
12640
+
11911
12641
  /* Chat Messages Area */
11912
12642
  .chat-panel__messages-wrapper {
11913
12643
  flex: 1;
@@ -11918,6 +12648,19 @@ body.resizing * {
11918
12648
  min-height: 0;
11919
12649
  }
11920
12650
 
12651
+ .chat-panel__messages-stack {
12652
+ flex: 1;
12653
+ display: flex;
12654
+ flex-direction: column;
12655
+ min-height: 0;
12656
+ position: relative;
12657
+ }
12658
+
12659
+ .chat-panel__messages-stack > .chat-panel__messages {
12660
+ flex: 1;
12661
+ min-height: 0;
12662
+ }
12663
+
11921
12664
  .chat-panel__messages {
11922
12665
  flex: 1;
11923
12666
  overflow-y: auto;
@@ -12452,6 +13195,17 @@ body.resizing * {
12452
13195
  display: flex;
12453
13196
  }
12454
13197
 
13198
+ /* Thread context card: compact single-line layout. The visible row mirrors
13199
+ .chat-panel__context-card (icon + label + title + file). The card's
13200
+ `title` attribute carries the full thread (one comment per block) for
13201
+ the native hover tooltip — so the chat stays scannable. */
13202
+ .chat-panel__context-card .chat-panel__context-count {
13203
+ margin-left: auto;
13204
+ font-size: 10px;
13205
+ color: var(--color-text-tertiary);
13206
+ flex-shrink: 0;
13207
+ }
13208
+
12455
13209
  .chat-panel__context-remove:hover {
12456
13210
  background: var(--color-danger, #d1242f);
12457
13211
  color: #ffffff;
@@ -13013,6 +13767,7 @@ body.resizing * {
13013
13767
  [data-chat="disabled"] .btn-collapsed-chat,
13014
13768
  [data-chat="disabled"] .finding-chat-action,
13015
13769
  [data-chat="disabled"] .btn-chat-comment,
13770
+ [data-chat="disabled"] .external-comment-chat-btn,
13016
13771
  [data-chat="disabled"] .analysis-history-chat-btn,
13017
13772
  [data-chat="disabled"] .chat-line-btn,
13018
13773
  [data-chat="disabled"] .btn-chat-from-comment {
@@ -13027,6 +13782,7 @@ body.resizing * {
13027
13782
  [data-chat="unavailable"] .btn-collapsed-chat,
13028
13783
  [data-chat="unavailable"] .finding-chat-action,
13029
13784
  [data-chat="unavailable"] .btn-chat-comment,
13785
+ [data-chat="unavailable"] .external-comment-chat-btn,
13030
13786
  [data-chat="unavailable"] .analysis-history-chat-btn,
13031
13787
  [data-chat="unavailable"] .chat-line-btn,
13032
13788
  [data-chat="unavailable"] .btn-chat-from-comment {