@in-the-loop-labs/pair-review 3.5.1 → 3.6.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.
Files changed (48) hide show
  1. package/package.json +1 -1
  2. package/plugin/.claude-plugin/plugin.json +1 -1
  3. package/plugin-code-critic/.claude-plugin/plugin.json +1 -1
  4. package/public/css/pr.css +603 -6
  5. package/public/index.html +90 -0
  6. package/public/js/components/ChatPanel.js +163 -3
  7. package/public/js/components/KeyboardShortcuts.js +10 -26
  8. package/public/js/components/TourBar.js +248 -0
  9. package/public/js/index.js +298 -25
  10. package/public/js/local.js +6 -0
  11. package/public/js/modules/cancel-background-job.js +183 -0
  12. package/public/js/modules/hunk-summary-renderer.js +116 -0
  13. package/public/js/modules/storage-cleanup.js +16 -0
  14. package/public/js/modules/tour-renderer.js +725 -0
  15. package/public/js/pr.js +1276 -2
  16. package/public/js/utils/modal-detection.js +77 -0
  17. package/public/local.html +17 -0
  18. package/public/pr.html +17 -0
  19. package/src/ai/abort-signal-wiring.js +130 -0
  20. package/src/ai/background-queue.js +290 -0
  21. package/src/ai/claude-cli.js +1 -1
  22. package/src/ai/claude-provider.js +50 -7
  23. package/src/ai/codex-provider.js +28 -5
  24. package/src/ai/copilot-provider.js +22 -3
  25. package/src/ai/cursor-agent-provider.js +22 -6
  26. package/src/ai/executable-provider.js +4 -19
  27. package/src/ai/gemini-provider.js +22 -5
  28. package/src/ai/hunk-hashing.js +161 -0
  29. package/src/ai/index.js +2 -0
  30. package/src/ai/opencode-provider.js +21 -5
  31. package/src/ai/pi-provider.js +21 -5
  32. package/src/ai/prompts/hunk-summary.js +199 -0
  33. package/src/ai/prompts/tour.js +232 -0
  34. package/src/ai/provider.js +21 -1
  35. package/src/ai/summary-generator.js +469 -0
  36. package/src/ai/tour-generator.js +568 -0
  37. package/src/config.js +114 -0
  38. package/src/database.js +282 -1
  39. package/src/local-review.js +189 -169
  40. package/src/routes/config.js +16 -1
  41. package/src/routes/context-files.js +2 -29
  42. package/src/routes/github-collections.js +168 -90
  43. package/src/routes/local.js +311 -4
  44. package/src/routes/middleware/validate-review-id.js +53 -0
  45. package/src/routes/pr.js +259 -4
  46. package/src/routes/reviews.js +145 -29
  47. package/src/utils/diff-hunks.js +65 -0
  48. package/src/utils/json-extractor.js +5 -2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@in-the-loop-labs/pair-review",
3
- "version": "3.5.1",
3
+ "version": "3.6.0",
4
4
  "description": "Your AI-powered code review partner - Close the feedback loop with AI coding agents",
5
5
  "main": "src/server.js",
6
6
  "bin": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pair-review",
3
- "version": "3.5.1",
3
+ "version": "3.6.0",
4
4
  "description": "pair-review app integration — Open PRs and local changes in the pair-review web UI, run server-side AI analysis, and address review feedback. Requires the pair-review MCP server.",
5
5
  "author": {
6
6
  "name": "in-the-loop-labs",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "code-critic",
3
- "version": "3.5.1",
3
+ "version": "3.6.0",
4
4
  "description": "AI-powered code review analysis — Run three-level AI analysis and implement-review-fix loops directly in your coding agent. Works standalone, no server required.",
5
5
  "author": {
6
6
  "name": "in-the-loop-labs",
package/public/css/pr.css CHANGED
@@ -1445,7 +1445,8 @@
1445
1445
 
1446
1446
  /* File header comment button and file header chat button */
1447
1447
  .file-header-comment-btn,
1448
- .file-header-chat-btn {
1448
+ .file-header-chat-btn,
1449
+ .file-header-summary-toggle {
1449
1450
  display: flex;
1450
1451
  align-items: center;
1451
1452
  justify-content: center;
@@ -1459,38 +1460,66 @@
1459
1460
  color: var(--color-text-secondary, #656d76);
1460
1461
  transition: background-color 0.15s, color 0.15s, border-color 0.15s;
1461
1462
  flex-shrink: 0;
1463
+ position: relative;
1462
1464
  }
1463
1465
 
1464
1466
  .file-header-comment-btn:hover,
1465
- .file-header-chat-btn:hover {
1467
+ .file-header-chat-btn:hover,
1468
+ .file-header-summary-toggle:not([disabled]):hover {
1466
1469
  background-color: var(--color-accent-bg, rgba(9, 105, 218, 0.1));
1467
1470
  border-color: var(--color-accent-primary, #0969da);
1468
1471
  color: var(--color-accent-primary, #0969da);
1469
1472
  }
1470
1473
 
1471
1474
  .file-header-comment-btn:active,
1472
- .file-header-chat-btn:active {
1475
+ .file-header-chat-btn:active,
1476
+ .file-header-summary-toggle:not([disabled]):active {
1473
1477
  background-color: var(--color-accent-bg, rgba(9, 105, 218, 0.15));
1474
1478
  }
1475
1479
 
1476
1480
  .file-header-comment-btn svg,
1477
- .file-header-chat-btn svg {
1481
+ .file-header-chat-btn svg,
1482
+ .file-header-summary-toggle svg {
1478
1483
  width: 16px;
1479
1484
  height: 16px;
1480
1485
  }
1481
1486
 
1482
1487
  [data-theme="dark"] .file-header-comment-btn,
1483
- [data-theme="dark"] .file-header-chat-btn {
1488
+ [data-theme="dark"] .file-header-chat-btn,
1489
+ [data-theme="dark"] .file-header-summary-toggle {
1484
1490
  color: #8b949e;
1485
1491
  }
1486
1492
 
1487
1493
  [data-theme="dark"] .file-header-comment-btn:hover,
1488
- [data-theme="dark"] .file-header-chat-btn:hover {
1494
+ [data-theme="dark"] .file-header-chat-btn:hover,
1495
+ [data-theme="dark"] .file-header-summary-toggle:not([disabled]):hover {
1489
1496
  background-color: rgba(56, 139, 253, 0.15);
1490
1497
  border-color: #58a6ff;
1491
1498
  color: #58a6ff;
1492
1499
  }
1493
1500
 
1501
+ /* Disabled state — no summaries available for this file */
1502
+ .file-header-summary-toggle[disabled] {
1503
+ opacity: 0.4;
1504
+ cursor: not-allowed;
1505
+ }
1506
+
1507
+ /* Off state — summaries hidden for this file. Shows the same slash overlay
1508
+ used by the toolbar toggle so the on/off state is visually distinct. */
1509
+ .file-header-summary-toggle.summaries-off:not([disabled])::after {
1510
+ content: '';
1511
+ position: absolute;
1512
+ top: 50%;
1513
+ left: 18%;
1514
+ right: 18%;
1515
+ height: 2px;
1516
+ background-color: currentColor;
1517
+ transform: rotate(-25deg);
1518
+ transform-origin: center;
1519
+ pointer-events: none;
1520
+ border-radius: 1px;
1521
+ }
1522
+
1494
1523
  /* Has comments state - filled icon with accent color */
1495
1524
  .file-header-comment-btn.has-comments {
1496
1525
  color: var(--color-accent-primary, #0969da);
@@ -14720,3 +14749,571 @@ body.resizing * {
14720
14749
  [data-theme="dark"] .status-failed .stack-progress-status-icon {
14721
14750
  color: var(--color-danger, #f85149);
14722
14751
  }
14752
+
14753
+ /* ==========================================================================
14754
+ Hunk Summary Annotations (Phase 5)
14755
+ ========================================================================== */
14756
+
14757
+ .hunk-summary-row {
14758
+ background: transparent;
14759
+ }
14760
+
14761
+ .hunk-summary-cell {
14762
+ padding: 4px 0;
14763
+ border: none;
14764
+ }
14765
+
14766
+ .hunk-summary-annotation {
14767
+ display: flex;
14768
+ align-items: flex-start;
14769
+ gap: 10px;
14770
+ padding: 8px 12px;
14771
+ margin: 6px auto;
14772
+ font-size: 13px;
14773
+ line-height: 1.5;
14774
+ color: #0550ae;
14775
+ background: rgba(9, 105, 218, 0.06);
14776
+ border: 1px solid rgba(9, 105, 218, 0.18);
14777
+ border-left: 3px solid #0969da;
14778
+ border-radius: var(--radius-md, 6px);
14779
+ max-width: calc(100vw - var(--sidebar-width, 0px) - var(--right-panel-group-width, 0px) - 64px);
14780
+ box-sizing: border-box;
14781
+ word-break: break-word;
14782
+ }
14783
+
14784
+ [data-theme="dark"] .hunk-summary-annotation {
14785
+ color: #79c0ff;
14786
+ background: rgba(56, 139, 253, 0.10);
14787
+ border-color: rgba(56, 139, 253, 0.25);
14788
+ border-left-color: #58a6ff;
14789
+ }
14790
+
14791
+ .hunk-summary-icon {
14792
+ flex-shrink: 0;
14793
+ display: inline-flex;
14794
+ align-items: center;
14795
+ justify-content: center;
14796
+ width: 16px;
14797
+ height: 16px;
14798
+ margin-top: 1px;
14799
+ opacity: 0.85;
14800
+ }
14801
+
14802
+ .hunk-summary-text {
14803
+ flex: 1;
14804
+ min-width: 0;
14805
+ }
14806
+
14807
+ /* Review-level toggle hides every annotation row */
14808
+ body.summaries-hidden .hunk-summary-row,
14809
+ .d2h-file-wrapper.summaries-hidden-file .hunk-summary-row {
14810
+ display: none;
14811
+ }
14812
+
14813
+ /* ---------- Toolbar toggle states ---------- */
14814
+ /* Default toolbar buttons inherit a transparent border; the toggle button
14815
+ uses a fixed neutral border so the on-state color swap is visible. */
14816
+ .btn-summary-toggle {
14817
+ position: relative;
14818
+ border: 1px solid var(--color-border-primary, #d1d9e0);
14819
+ color: var(--color-text-secondary, #57606a);
14820
+ transition: color var(--transition-fast, 0.1s ease),
14821
+ border-color var(--transition-fast, 0.1s ease),
14822
+ background-color var(--transition-fast, 0.1s ease);
14823
+ }
14824
+
14825
+ /* Summaries are visible (default state when feature is on). */
14826
+ .btn-summary-toggle.active {
14827
+ color: var(--color-accent-primary, #0969da);
14828
+ border-color: var(--color-accent-primary, #0969da);
14829
+ background-color: rgba(9, 105, 218, 0.08);
14830
+ }
14831
+
14832
+ [data-theme="dark"] .btn-summary-toggle.active {
14833
+ color: var(--color-accent-primary, #58a6ff);
14834
+ border-color: var(--color-accent-primary, #58a6ff);
14835
+ background-color: rgba(56, 139, 253, 0.12);
14836
+ }
14837
+
14838
+ /* Summaries hidden — the off state is just the base button styling
14839
+ (gray secondary color, no background). Matches the tour-toggle pattern
14840
+ so the two buttons read consistently when off. */
14841
+
14842
+ /* "Generating" pulse — applied while the background summary job is running. */
14843
+ .btn-summary-toggle.generating {
14844
+ animation: summaryPulse 1.6s ease-in-out infinite;
14845
+ }
14846
+
14847
+ @keyframes summaryPulse {
14848
+ 0%, 100% {
14849
+ box-shadow: 0 0 0 0 rgba(9, 105, 218, 0);
14850
+ }
14851
+ 50% {
14852
+ box-shadow: 0 0 0 4px rgba(9, 105, 218, 0.18);
14853
+ }
14854
+ }
14855
+
14856
+ [data-theme="dark"] .btn-summary-toggle.generating {
14857
+ animation-name: summaryPulseDark;
14858
+ }
14859
+
14860
+ @keyframes summaryPulseDark {
14861
+ 0%, 100% {
14862
+ box-shadow: 0 0 0 0 rgba(56, 139, 253, 0);
14863
+ }
14864
+ 50% {
14865
+ box-shadow: 0 0 0 4px rgba(56, 139, 253, 0.28);
14866
+ }
14867
+ }
14868
+
14869
+ /* Custom hover label — shown alongside the native title in case the user's
14870
+ browser is suppressing native tooltips. Uses a `data-label` attr so the
14871
+ label tracks the on/off state. */
14872
+ .btn-summary-toggle[data-label]:hover::before {
14873
+ content: attr(data-label);
14874
+ position: absolute;
14875
+ top: calc(100% + 6px);
14876
+ right: 0;
14877
+ z-index: 200;
14878
+ padding: 4px 8px;
14879
+ font-size: 12px;
14880
+ line-height: 1.2;
14881
+ white-space: nowrap;
14882
+ color: var(--color-text-primary, #24292f);
14883
+ background: var(--color-bg-primary, #ffffff);
14884
+ border: 1px solid var(--color-border-primary, #d1d9e0);
14885
+ border-radius: var(--radius-sm, 4px);
14886
+ box-shadow: 0 2px 4px var(--color-shadow, rgba(27, 31, 36, 0.04));
14887
+ pointer-events: none;
14888
+ }
14889
+
14890
+ [data-theme="dark"] .btn-summary-toggle[data-label]:hover::before {
14891
+ color: var(--color-text-primary, #c9d1d9);
14892
+ background: var(--color-bg-primary, #0d1117);
14893
+ border-color: var(--color-border-primary, #30363d);
14894
+ }
14895
+
14896
+ .file-header-summary-toggle.summaries-off {
14897
+ opacity: 0.45;
14898
+ }
14899
+
14900
+ /* ==========================================================================
14901
+ Tour Annotations & Tour Bar (Phase 8)
14902
+ ========================================================================== */
14903
+
14904
+ /* The inline annotation row that is mounted above the anchor line for each
14905
+ stop. Pale-yellow palette to differentiate from info-blue summaries. */
14906
+ .tour-annotation-row {
14907
+ background: transparent;
14908
+ }
14909
+
14910
+ .tour-annotation-cell {
14911
+ padding: 4px 0;
14912
+ border: none;
14913
+ }
14914
+
14915
+ .tour-annotation {
14916
+ display: flex;
14917
+ flex-direction: column;
14918
+ gap: 6px;
14919
+ padding: 10px 14px;
14920
+ margin: 6px auto;
14921
+ font-size: 13px;
14922
+ line-height: 1.45;
14923
+ color: #7d5700;
14924
+ /* Brighter amber matching the .tour-bar gradient so stops read as part
14925
+ of the same tour surface, not a muted variant of it. */
14926
+ background: linear-gradient(180deg, #fff8e1 0%, #fef3c7 100%);
14927
+ border: 1px solid rgba(212, 167, 44, 0.55);
14928
+ border-left: 3px solid #d4a72c;
14929
+ border-radius: var(--radius-md, 6px);
14930
+ max-width: calc(100vw - var(--sidebar-width, 0px) - var(--right-panel-group-width, 0px) - 64px);
14931
+ box-sizing: border-box;
14932
+ word-break: break-word;
14933
+ transition: box-shadow var(--transition-fast, 0.1s ease),
14934
+ border-color var(--transition-fast, 0.1s ease);
14935
+ }
14936
+
14937
+ [data-theme="dark"] .tour-annotation {
14938
+ color: #e3b341;
14939
+ /* Mirror the bar's dark-mode amber wash so light- and dark-mode tours
14940
+ share one visual language. */
14941
+ background: linear-gradient(180deg, rgba(187, 128, 9, 0.18) 0%, rgba(187, 128, 9, 0.28) 100%),
14942
+ var(--color-bg-primary, #0d1117);
14943
+ border-color: rgba(187, 128, 9, 0.55);
14944
+ border-left-color: #bb8009;
14945
+ }
14946
+
14947
+ .tour-annotation-header {
14948
+ display: flex;
14949
+ align-items: center;
14950
+ justify-content: space-between;
14951
+ gap: 8px;
14952
+ font-weight: 600;
14953
+ }
14954
+
14955
+ .tour-stop-marker {
14956
+ display: inline-flex;
14957
+ align-items: center;
14958
+ gap: 4px;
14959
+ font-size: 12px;
14960
+ text-transform: uppercase;
14961
+ letter-spacing: 0.04em;
14962
+ opacity: 0.9;
14963
+ }
14964
+
14965
+ .tour-stop-marker svg {
14966
+ width: 14px;
14967
+ height: 14px;
14968
+ }
14969
+
14970
+ .tour-annotation-title {
14971
+ margin: 0;
14972
+ font-size: 14px;
14973
+ font-weight: 600;
14974
+ line-height: 1.3;
14975
+ }
14976
+
14977
+ .tour-annotation-description {
14978
+ margin: 0;
14979
+ font-size: 13px;
14980
+ font-weight: 400;
14981
+ line-height: 1.45;
14982
+ opacity: 0.95;
14983
+ }
14984
+
14985
+ /* Wrap around the description <p>. Collapsed state uses the WebKit
14986
+ line-clamp triplet to cap visible content at ~3 lines; expanded state
14987
+ removes the clamp so the full description shows. The wrapper (not the
14988
+ inner <p>) carries the clamp so the JS overflow check has a stable
14989
+ element to measure (`scrollHeight > clientHeight`). */
14990
+ .tour-annotation-description-wrap {
14991
+ display: -webkit-box;
14992
+ -webkit-box-orient: vertical;
14993
+ -webkit-line-clamp: 3;
14994
+ line-clamp: 3;
14995
+ overflow: hidden;
14996
+ }
14997
+
14998
+ .tour-annotation-description-wrap.expanded {
14999
+ display: block;
15000
+ -webkit-line-clamp: unset;
15001
+ line-clamp: unset;
15002
+ overflow: visible;
15003
+ }
15004
+
15005
+ /* Footer holds only the "Show more"/"Show less" toggle when the
15006
+ description overflows the line-clamp; otherwise it's an empty stub.
15007
+ Chat about lives in the header now. */
15008
+ .tour-annotation-footer {
15009
+ display: flex;
15010
+ align-items: center;
15011
+ gap: 6px;
15012
+ margin-top: 4px;
15013
+ }
15014
+
15015
+ .tour-annotation-footer:empty {
15016
+ display: none;
15017
+ }
15018
+
15019
+ /* "Show more" / "Show less" toggle. Styled as a borderless amber link
15020
+ button so it reads as part of the tour annotation but doesn't compete
15021
+ with the primary Chat about action. */
15022
+ .tour-annotation-show-more-btn {
15023
+ appearance: none;
15024
+ background: none;
15025
+ border: 0;
15026
+ padding: 2px 4px;
15027
+ margin: 0;
15028
+ font: inherit;
15029
+ font-size: 12px;
15030
+ font-weight: 600;
15031
+ color: #7d5700;
15032
+ cursor: pointer;
15033
+ text-decoration: underline;
15034
+ text-underline-offset: 2px;
15035
+ border-radius: var(--radius-sm, 3px);
15036
+ }
15037
+
15038
+ .tour-annotation-show-more-btn:hover {
15039
+ color: #5b3f00;
15040
+ background: rgba(255, 255, 255, 0.45);
15041
+ }
15042
+
15043
+ .tour-annotation-show-more-btn:focus-visible {
15044
+ outline: 2px solid #d4a72c;
15045
+ outline-offset: 1px;
15046
+ }
15047
+
15048
+ [data-theme="dark"] .tour-annotation-show-more-btn {
15049
+ color: #e3b341;
15050
+ }
15051
+
15052
+ [data-theme="dark"] .tour-annotation-show-more-btn:hover {
15053
+ color: #f3c861;
15054
+ background: rgba(187, 128, 9, 0.18);
15055
+ }
15056
+
15057
+ /* The chat button on a tour-stop annotation reuses .ai-action / .ai-action-chat
15058
+ so its idle and hover treatments stay in sync with the rest of the app. We
15059
+ override surface colors (so it reads on the amber background) AND collapse
15060
+ it to an icon-only square — the button sits in the header next to the
15061
+ stop marker, where a labelled button would crowd the title row. The title
15062
+ attribute / aria-label carry the meaning for accessibility + tooltip. */
15063
+ .tour-annotation .tour-annotation-chat-btn {
15064
+ background: rgba(255, 255, 255, 0.6);
15065
+ color: #7d5700;
15066
+ border: 1px solid rgba(212, 167, 44, 0.55);
15067
+ padding: 2px;
15068
+ width: 22px;
15069
+ height: 22px;
15070
+ display: inline-flex;
15071
+ align-items: center;
15072
+ justify-content: center;
15073
+ flex: 0 0 auto;
15074
+ }
15075
+
15076
+ .tour-annotation .tour-annotation-chat-btn svg {
15077
+ width: 14px;
15078
+ height: 14px;
15079
+ }
15080
+
15081
+ .tour-annotation .tour-annotation-chat-btn:hover {
15082
+ background: rgba(255, 255, 255, 0.9);
15083
+ color: #5b3f00;
15084
+ border-color: #d4a72c;
15085
+ }
15086
+
15087
+ [data-theme="dark"] .tour-annotation .tour-annotation-chat-btn {
15088
+ background: rgba(187, 128, 9, 0.12);
15089
+ color: #e3b341;
15090
+ border-color: rgba(187, 128, 9, 0.55);
15091
+ }
15092
+
15093
+ [data-theme="dark"] .tour-annotation .tour-annotation-chat-btn:hover {
15094
+ background: rgba(187, 128, 9, 0.25);
15095
+ color: #f3c861;
15096
+ border-color: #e3b341;
15097
+ }
15098
+
15099
+ /* Emphasize the currently-active stop annotation. */
15100
+ body.tour-active .tour-annotation-row.active-stop .tour-annotation {
15101
+ box-shadow: 0 0 0 2px rgba(212, 167, 44, 0.55),
15102
+ 0 2px 6px rgba(0, 0, 0, 0.08);
15103
+ border-color: #d4a72c;
15104
+ }
15105
+
15106
+ [data-theme="dark"] body.tour-active .tour-annotation-row.active-stop .tour-annotation {
15107
+ box-shadow: 0 0 0 2px rgba(187, 128, 9, 0.65),
15108
+ 0 2px 6px rgba(0, 0, 0, 0.30);
15109
+ border-color: #e3b341;
15110
+ }
15111
+
15112
+ /* ---------- Sticky top tour bar ---------- */
15113
+ :root {
15114
+ /* Height assumed by the diff-toolbar / file-header offset rules below.
15115
+ Keep this in sync with the bar's computed height (icon + button row +
15116
+ vertical padding). The bar also sets `min-height: var(--tour-bar-height)`
15117
+ so its actual rendered height never drops below this value. */
15118
+ --tour-bar-height: 56px;
15119
+ }
15120
+
15121
+ /* While a tour is active, push the sticky `.diff-toolbar` and the
15122
+ per-file `.d2h-file-header` down by the bar's height so they stack
15123
+ below it instead of overlapping. The bar itself is `position: sticky;
15124
+ top: 0` inside the same scroll container (.main-layout .diff-view),
15125
+ so all three pin in order: tour bar (top: 0), diff toolbar (top:
15126
+ --tour-bar-height), file header (top: --toolbar-height + --tour-bar-height). */
15127
+ body.tour-active .diff-toolbar {
15128
+ top: var(--tour-bar-height);
15129
+ }
15130
+
15131
+ body.tour-active .d2h-file-header {
15132
+ top: calc(var(--toolbar-height, 0px) + var(--tour-bar-height));
15133
+ }
15134
+
15135
+ .tour-bar {
15136
+ /* Sticky inside the diff-view scroll container so the bar spans the
15137
+ diff width only — the file-tree sidebar (and its hide/show toggle in
15138
+ the diff toolbar) stays visible and clickable. Mounted as the first
15139
+ child of .diff-view via TourBar.mount(parent). */
15140
+ position: sticky;
15141
+ top: 0;
15142
+ /* z-index must exceed .diff-toolbar (5) and .d2h-file-header (4) so the
15143
+ bar paints above them at the top of the scrollport. */
15144
+ z-index: 6;
15145
+ /* Lock the rendered height to the var so the sticky-top offsets on
15146
+ .diff-toolbar and .d2h-file-header line up exactly with the bar's
15147
+ bottom edge (no gap, no overlap). flex-shrink:0 prevents the parent
15148
+ flex column (.diff-view) from squashing the bar below min-height. */
15149
+ box-sizing: border-box;
15150
+ flex-shrink: 0;
15151
+ min-height: var(--tour-bar-height);
15152
+ display: flex;
15153
+ align-items: center;
15154
+ justify-content: space-between;
15155
+ gap: 12px;
15156
+ padding: 10px 16px;
15157
+ /* Distinctive amber theme so the bar reads as a tour-mode chrome and
15158
+ not just another toolbar. Soft tinted background + a thicker amber
15159
+ bottom rule echo the per-stop annotation accent (#d4a72c). */
15160
+ background: linear-gradient(180deg, #fff8e1 0%, #fef3c7 100%);
15161
+ border-bottom: 2px solid #d4a72c;
15162
+ box-shadow: 0 2px 12px rgba(212, 167, 44, 0.18);
15163
+ font-size: 13px;
15164
+ color: #7d5700;
15165
+ }
15166
+
15167
+ [data-theme="dark"] .tour-bar {
15168
+ background: linear-gradient(180deg, rgba(187, 128, 9, 0.18) 0%, rgba(187, 128, 9, 0.28) 100%),
15169
+ var(--color-bg-primary, #0d1117);
15170
+ border-bottom-color: #bb8009;
15171
+ color: #e3b341;
15172
+ box-shadow: 0 2px 12px rgba(0, 0, 0, 0.40);
15173
+ }
15174
+
15175
+ .tour-bar__brand {
15176
+ display: inline-flex;
15177
+ align-items: center;
15178
+ gap: 8px;
15179
+ font-weight: 600;
15180
+ color: #7d5700;
15181
+ }
15182
+
15183
+ [data-theme="dark"] .tour-bar__brand {
15184
+ color: #e3b341;
15185
+ }
15186
+
15187
+ .tour-bar__brand svg {
15188
+ width: 16px;
15189
+ height: 16px;
15190
+ }
15191
+
15192
+ .tour-bar__progress {
15193
+ flex: 1;
15194
+ text-align: center;
15195
+ font-variant-numeric: tabular-nums;
15196
+ font-weight: 500;
15197
+ color: #6b4500;
15198
+ }
15199
+
15200
+ [data-theme="dark"] .tour-bar__progress {
15201
+ color: #e3b341;
15202
+ }
15203
+
15204
+ .tour-bar__nav {
15205
+ display: inline-flex;
15206
+ align-items: center;
15207
+ gap: 6px;
15208
+ }
15209
+
15210
+ .tour-bar__nav button {
15211
+ display: inline-flex;
15212
+ align-items: center;
15213
+ gap: 4px;
15214
+ padding: 4px 10px;
15215
+ font-size: 12px;
15216
+ font-weight: 500;
15217
+ color: #7d5700;
15218
+ background: rgba(255, 255, 255, 0.65);
15219
+ border: 1px solid rgba(212, 167, 44, 0.55);
15220
+ border-radius: var(--radius-sm, 4px);
15221
+ cursor: pointer;
15222
+ transition: background-color var(--transition-fast, 0.1s ease),
15223
+ border-color var(--transition-fast, 0.1s ease);
15224
+ }
15225
+
15226
+ .tour-bar__nav button:hover:not(:disabled) {
15227
+ background: rgba(255, 255, 255, 0.95);
15228
+ border-color: #d4a72c;
15229
+ }
15230
+
15231
+ .tour-bar__nav button:disabled {
15232
+ opacity: 0.45;
15233
+ cursor: not-allowed;
15234
+ }
15235
+
15236
+ .tour-bar__nav button svg {
15237
+ width: 12px;
15238
+ height: 12px;
15239
+ }
15240
+
15241
+ [data-theme="dark"] .tour-bar__nav button {
15242
+ color: #e3b341;
15243
+ background: rgba(13, 17, 23, 0.55);
15244
+ border-color: rgba(187, 128, 9, 0.55);
15245
+ }
15246
+
15247
+ [data-theme="dark"] .tour-bar__nav button:hover:not(:disabled) {
15248
+ background: rgba(13, 17, 23, 0.85);
15249
+ border-color: #e3b341;
15250
+ }
15251
+
15252
+ /* ---------- Toolbar tour-toggle button states ---------- */
15253
+ .btn-tour-toggle {
15254
+ position: relative;
15255
+ border: 1px solid var(--color-border-primary, #d1d9e0);
15256
+ color: var(--color-text-secondary, #57606a);
15257
+ transition: color var(--transition-fast, 0.1s ease),
15258
+ border-color var(--transition-fast, 0.1s ease),
15259
+ background-color var(--transition-fast, 0.1s ease);
15260
+ }
15261
+
15262
+ /* Tour active — pale-yellow highlight. */
15263
+ .btn-tour-toggle.active {
15264
+ color: #7d5700;
15265
+ border-color: #d4a72c;
15266
+ background-color: rgba(212, 167, 44, 0.12);
15267
+ }
15268
+
15269
+ [data-theme="dark"] .btn-tour-toggle.active {
15270
+ color: #e3b341;
15271
+ border-color: #bb8009;
15272
+ background-color: rgba(187, 128, 9, 0.20);
15273
+ }
15274
+
15275
+ /* Generating pulse while the background tour job is running. */
15276
+ .btn-tour-toggle.generating {
15277
+ animation: tourPulse 1.6s ease-in-out infinite;
15278
+ }
15279
+
15280
+ /* Subtle indicator that a newer tour is stashed and waiting for restart.
15281
+ Renders as a small dot in the corner of the toolbar button. */
15282
+ .btn-tour-toggle.tour-updated-pending::after {
15283
+ content: '';
15284
+ position: absolute;
15285
+ top: 4px;
15286
+ right: 4px;
15287
+ width: 6px;
15288
+ height: 6px;
15289
+ border-radius: 50%;
15290
+ background: #d4a72c;
15291
+ box-shadow: 0 0 0 2px var(--color-bg-primary, #ffffff);
15292
+ }
15293
+
15294
+ [data-theme="dark"] .btn-tour-toggle.tour-updated-pending::after {
15295
+ background: #e3b341;
15296
+ box-shadow: 0 0 0 2px var(--color-bg-primary, #0d1117);
15297
+ }
15298
+
15299
+ @keyframes tourPulse {
15300
+ 0%, 100% {
15301
+ box-shadow: 0 0 0 0 rgba(212, 167, 44, 0);
15302
+ }
15303
+ 50% {
15304
+ box-shadow: 0 0 0 4px rgba(212, 167, 44, 0.28);
15305
+ }
15306
+ }
15307
+
15308
+ [data-theme="dark"] .btn-tour-toggle.generating {
15309
+ animation-name: tourPulseDark;
15310
+ }
15311
+
15312
+ @keyframes tourPulseDark {
15313
+ 0%, 100% {
15314
+ box-shadow: 0 0 0 0 rgba(187, 128, 9, 0);
15315
+ }
15316
+ 50% {
15317
+ box-shadow: 0 0 0 4px rgba(187, 128, 9, 0.40);
15318
+ }
15319
+ }