git-juggler 0.2.0__tar.gz → 0.2.2__tar.gz

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 (179) hide show
  1. {git_juggler-0.2.0 → git_juggler-0.2.2}/PKG-INFO +1 -1
  2. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/backend/git_juggler/agent_hook_events.py +6 -1
  3. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/backend/git_juggler/agent_tracking/activity_models.py +2 -0
  4. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/backend/git_juggler/agent_tracking/claude_sessions.py +3 -0
  5. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/backend/git_juggler/app.py +40 -2
  6. git_juggler-0.2.2/src/backend/git_juggler/ci.py +146 -0
  7. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/backend/git_juggler/commit_detail.py +20 -1
  8. git_juggler-0.2.2/src/backend/git_juggler/frontend_dist/assets/index--rgc3DQg.css +1 -0
  9. git_juggler-0.2.2/src/backend/git_juggler/frontend_dist/assets/index-XfKcnwHm.js +22 -0
  10. git_juggler-0.2.2/src/backend/git_juggler/frontend_dist/ci-logos/jenkins.webp +0 -0
  11. git_juggler-0.2.2/src/backend/git_juggler/frontend_dist/git-juggler.png +0 -0
  12. git_juggler-0.2.2/src/backend/git_juggler/frontend_dist/index.html +14 -0
  13. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/backend/git_juggler/git_data.py +97 -12
  14. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/backend/git_juggler/github_actions.py +94 -4
  15. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/backend/git_juggler/jenkins.py +80 -1
  16. git_juggler-0.2.2/src/backend/git_juggler/pick_folder.py +48 -0
  17. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/backend/git_juggler/schemas.py +16 -0
  18. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/backend/tests/test_agent_hooks.py +28 -2
  19. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/backend/tests/test_ci.py +19 -8
  20. git_juggler-0.2.2/src/backend/tests/test_ci_poll.py +170 -0
  21. git_juggler-0.2.2/src/backend/tests/test_ci_poll_route.py +52 -0
  22. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/backend/tests/test_file_diff.py +12 -0
  23. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/backend/tests/test_git_data.py +62 -0
  24. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/backend/tests/test_github_actions.py +2 -2
  25. git_juggler-0.2.2/src/backend/tests/test_pick_folder.py +34 -0
  26. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/frontend/index.html +2 -2
  27. git_juggler-0.2.2/src/frontend/public/ci-logos/jenkins.webp +0 -0
  28. git_juggler-0.2.2/src/frontend/public/git-juggler.png +0 -0
  29. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/frontend/src/App.css +362 -195
  30. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/frontend/src/App.tsx +23 -8
  31. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/frontend/src/api/client.ts +46 -1
  32. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/frontend/src/api/types.ts +16 -0
  33. git_juggler-0.2.2/src/frontend/src/components/Agents/AgentHoverCard.tsx +67 -0
  34. git_juggler-0.2.2/src/frontend/src/components/Agents/AgentStatusPopover.tsx +208 -0
  35. git_juggler-0.2.2/src/frontend/src/components/Badges/CiRunBadge.tsx +161 -0
  36. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/frontend/src/components/Badges/ciStatus.tsx +15 -6
  37. git_juggler-0.2.2/src/frontend/src/components/CIRunPopover.tsx +344 -0
  38. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/frontend/src/components/Commits/CommitDetail.tsx +2 -0
  39. git_juggler-0.2.2/src/frontend/src/components/Commits/LineCounts.tsx +16 -0
  40. git_juggler-0.2.2/src/frontend/src/components/Commits/UncommittedRow.tsx +241 -0
  41. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/frontend/src/components/ContextMenu/BranchContextMenu.tsx +2 -0
  42. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/frontend/src/components/ContextMenu/CommitContextMenu.tsx +14 -0
  43. git_juggler-0.2.2/src/frontend/src/components/ContextMenu/CreateBranchModal.tsx +77 -0
  44. git_juggler-0.2.2/src/frontend/src/components/ContextMenu/RepoContextMenu.tsx +101 -0
  45. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/frontend/src/components/ContextMenu/TagContextMenu.tsx +2 -0
  46. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/frontend/src/components/Menu/AgentsSettings.tsx +1 -1
  47. git_juggler-0.2.2/src/frontend/src/components/Menu/CiPollField.tsx +18 -0
  48. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/frontend/src/components/Menu/GitHubSettings.tsx +49 -9
  49. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/frontend/src/components/Menu/JenkinsSettings.tsx +51 -11
  50. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/frontend/src/components/Search/SearchBox.tsx +26 -3
  51. git_juggler-0.2.2/src/frontend/src/components/Sidebar/NewGroupModal.tsx +56 -0
  52. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/frontend/src/components/Sidebar/RepoList.tsx +82 -112
  53. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/frontend/src/components/Tabs/TabsBar.tsx +7 -3
  54. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/frontend/src/components/Terminal/TerminalPanel.tsx +1 -1
  55. git_juggler-0.2.2/src/frontend/src/lib/agentStatusPopoverData.ts +91 -0
  56. git_juggler-0.2.2/src/frontend/src/lib/ciPoll.ts +52 -0
  57. git_juggler-0.2.2/src/frontend/src/lib/ciRunPopoverData.ts +94 -0
  58. git_juggler-0.2.2/src/frontend/src/lib/stageWindow.ts +27 -0
  59. git_juggler-0.2.2/src/frontend/src/lib/swallowNextClick.ts +21 -0
  60. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/frontend/src/state/store.ts +184 -18
  61. git_juggler-0.2.2/src/frontend/tests/agentStatusPopoverData.test.ts +93 -0
  62. git_juggler-0.2.2/src/frontend/tests/ciPoll.test.ts +55 -0
  63. git_juggler-0.2.2/src/frontend/tests/ciRunPopoverData.test.ts +85 -0
  64. git_juggler-0.2.2/src/frontend/tests/stageWindow.test.ts +22 -0
  65. git_juggler-0.2.0/src/backend/git_juggler/ci.py +0 -66
  66. git_juggler-0.2.0/src/backend/git_juggler/frontend_dist/assets/index-B3IAjQDX.css +0 -1
  67. git_juggler-0.2.0/src/backend/git_juggler/frontend_dist/assets/index-C7Ojrlhd.js +0 -21
  68. git_juggler-0.2.0/src/backend/git_juggler/frontend_dist/index.html +0 -14
  69. git_juggler-0.2.0/src/frontend/src/components/Agents/AgentHoverCard.tsx +0 -99
  70. git_juggler-0.2.0/src/frontend/src/components/Badges/CiRunBadge.tsx +0 -64
  71. git_juggler-0.2.0/src/frontend/src/components/Commits/UncommittedRow.tsx +0 -128
  72. git_juggler-0.2.0/src/frontend/src/components/ContextMenu/RepoContextMenu.tsx +0 -29
  73. {git_juggler-0.2.0 → git_juggler-0.2.2}/.github/workflows/build-package.yml +0 -0
  74. {git_juggler-0.2.0 → git_juggler-0.2.2}/.gitignore +0 -0
  75. {git_juggler-0.2.0 → git_juggler-0.2.2}/CLAUDE.md +0 -0
  76. {git_juggler-0.2.0 → git_juggler-0.2.2}/LICENSE +0 -0
  77. {git_juggler-0.2.0 → git_juggler-0.2.2}/README.md +0 -0
  78. {git_juggler-0.2.0 → git_juggler-0.2.2}/notes.txt +0 -0
  79. {git_juggler-0.2.0 → git_juggler-0.2.2}/plans/github-actions-settings-and-build-status.md +0 -0
  80. {git_juggler-0.2.0 → git_juggler-0.2.2}/plans/jenkins-settings-and-build-status.md +0 -0
  81. {git_juggler-0.2.0 → git_juggler-0.2.2}/pyproject.toml +0 -0
  82. {git_juggler-0.2.0 → git_juggler-0.2.2}/scripts/build-package.sh +0 -0
  83. {git_juggler-0.2.0 → git_juggler-0.2.2}/scripts/dev.sh +0 -0
  84. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/backend/git_juggler/__init__.py +0 -0
  85. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/backend/git_juggler/agent_hooks.py +0 -0
  86. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/backend/git_juggler/agent_tracking/__init__.py +0 -0
  87. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/backend/git_juggler/agent_tracking/claude_transcripts.py +0 -0
  88. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/backend/git_juggler/agent_tracking/git_resolver.py +0 -0
  89. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/backend/git_juggler/agent_tracking/opencode_sessions.py +0 -0
  90. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/backend/git_juggler/agent_tracking/paths.py +0 -0
  91. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/backend/git_juggler/agent_tracking/text.py +0 -0
  92. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/backend/git_juggler/browse.py +0 -0
  93. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/backend/git_juggler/config.py +0 -0
  94. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/backend/git_juggler/dev_app.py +0 -0
  95. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/backend/git_juggler/file_diff.py +0 -0
  96. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/backend/git_juggler/frontend_dist/agent-logos/claude.png +0 -0
  97. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/backend/git_juggler/frontend_dist/agent-logos/opencode.webp +0 -0
  98. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/backend/git_juggler/frontend_dist/favicon.svg +0 -0
  99. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/backend/git_juggler/frontend_dist/icons.svg +0 -0
  100. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/backend/git_juggler/git_utils.py +0 -0
  101. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/backend/git_juggler/main.py +0 -0
  102. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/backend/git_juggler/repos.py +0 -0
  103. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/backend/git_juggler/terminal.py +0 -0
  104. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/backend/git_juggler/themes.py +0 -0
  105. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/backend/tests/test_agent_tracking.py +0 -0
  106. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/backend/tests/test_claude_sessions_data.py +0 -0
  107. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/backend/tests/test_jenkins.py +0 -0
  108. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/backend/tests/test_opencode_sessions.py +0 -0
  109. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/backend/tests/test_preferences.py +0 -0
  110. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/backend/tests/test_repos.py +0 -0
  111. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/backend/tests/test_themes.py +0 -0
  112. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/frontend/.gitignore +0 -0
  113. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/frontend/README.md +0 -0
  114. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/frontend/package-lock.json +0 -0
  115. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/frontend/package.json +0 -0
  116. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/frontend/public/agent-logos/claude.png +0 -0
  117. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/frontend/public/agent-logos/opencode.webp +0 -0
  118. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/frontend/public/favicon.svg +0 -0
  119. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/frontend/public/icons.svg +0 -0
  120. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/frontend/src/components/Agents/AgentActivityPanel.tsx +0 -0
  121. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/frontend/src/components/Agents/agentFormat.ts +0 -0
  122. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/frontend/src/components/Badges/BranchBadge.tsx +0 -0
  123. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/frontend/src/components/Badges/StashBadge.tsx +0 -0
  124. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/frontend/src/components/Badges/TagBadge.tsx +0 -0
  125. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/frontend/src/components/Badges/icons.tsx +0 -0
  126. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/frontend/src/components/Branches/DeleteBranchModal.tsx +0 -0
  127. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/frontend/src/components/CommandPalette/CommandPalette.tsx +0 -0
  128. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/frontend/src/components/Commits/CommitList.tsx +0 -0
  129. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/frontend/src/components/Commits/CommitRow.tsx +0 -0
  130. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/frontend/src/components/ContextMenu/CreateTagModal.tsx +0 -0
  131. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/frontend/src/components/Diff/FileDiffModal.tsx +0 -0
  132. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/frontend/src/components/Graph/GraphPanel.tsx +0 -0
  133. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/frontend/src/components/Graph/branchColor.ts +0 -0
  134. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/frontend/src/components/Graph/computeColumns.ts +0 -0
  135. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/frontend/src/components/Graph/laneWidth.ts +0 -0
  136. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/frontend/src/components/Menu/AgentQuickPicks.tsx +0 -0
  137. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/frontend/src/components/Menu/DirectoryBrowserModal.tsx +0 -0
  138. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/frontend/src/components/Menu/MainMenu.tsx +0 -0
  139. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/frontend/src/components/Menu/RepoPathsSettings.tsx +0 -0
  140. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/frontend/src/components/Menu/ThemePicker.tsx +0 -0
  141. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/frontend/src/components/Menu/ThemeQuickPicks.tsx +0 -0
  142. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/frontend/src/components/Pipelines/PipelinesPanel.tsx +0 -0
  143. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/frontend/src/components/Pipelines/StageStrip.tsx +0 -0
  144. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/frontend/src/components/Search/MultiSelect.tsx +0 -0
  145. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/frontend/src/components/Search/TriSwitch.tsx +0 -0
  146. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/frontend/src/components/Sidebar/Sidebar.tsx +0 -0
  147. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/frontend/src/components/Tags/DeleteTagModal.tsx +0 -0
  148. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/frontend/src/components/Welcome/WelcomeWizard.tsx +0 -0
  149. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/frontend/src/components/inputs/NumberField.tsx +0 -0
  150. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/frontend/src/components/inputs/TextField.tsx +0 -0
  151. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/frontend/src/components/inputs/ToggleField.tsx +0 -0
  152. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/frontend/src/components/inputs/styles.ts +0 -0
  153. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/frontend/src/index.css +0 -0
  154. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/frontend/src/index.tsx +0 -0
  155. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/frontend/src/lib/appTheme.ts +0 -0
  156. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/frontend/src/lib/branchVisibility.ts +0 -0
  157. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/frontend/src/lib/bundledThemes.ts +0 -0
  158. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/frontend/src/lib/color.ts +0 -0
  159. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/frontend/src/lib/dismissOnOutsideClick.ts +0 -0
  160. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/frontend/src/lib/flip.ts +0 -0
  161. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/frontend/src/lib/formatDate.ts +0 -0
  162. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/frontend/src/lib/preferenceSender.ts +0 -0
  163. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/frontend/src/lib/scrollToCommit.ts +0 -0
  164. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/frontend/src/lib/sideBySide.ts +0 -0
  165. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/frontend/src/lib/stripAnsi.ts +0 -0
  166. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/frontend/src/lib/vscodeTheme.ts +0 -0
  167. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/frontend/src/state/overlayStack.ts +0 -0
  168. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/frontend/src/state/preferenceSync.ts +0 -0
  169. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/frontend/src/state/preferences.ts +0 -0
  170. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/frontend/src/state/themes.ts +0 -0
  171. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/frontend/tests/agentFormat.test.ts +0 -0
  172. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/frontend/tests/computeColumns.test.ts +0 -0
  173. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/frontend/tests/preferenceSender.test.ts +0 -0
  174. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/frontend/tests/sideBySide.test.ts +0 -0
  175. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/frontend/tests/vscodeTheme.test.ts +0 -0
  176. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/frontend/tsconfig.app.json +0 -0
  177. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/frontend/tsconfig.json +0 -0
  178. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/frontend/tsconfig.node.json +0 -0
  179. {git_juggler-0.2.0 → git_juggler-0.2.2}/src/frontend/vite.config.ts +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: git-juggler
3
- Version: 0.2.0
3
+ Version: 0.2.2
4
4
  Summary: A local Git dashboard for people juggling agents and too many repos.
5
5
  License-Expression: MIT
6
6
  License-File: LICENSE
@@ -151,12 +151,16 @@ class AgentHookEventReader:
151
151
  card: ClaudeSession | None = None
152
152
  opencode_session: OpenCodeSession | None = None
153
153
  busy: bool | None = None # what the agent itself reports, when it does
154
+ waiting_for: str | None = None
154
155
  if provider == "claude" and registry is not None and session_id:
155
156
  card = registry.get(session_id)
156
157
  if card is None or not _pid_alive(card.pid):
157
158
  continue
158
159
  process_pid: int | None = card.pid
159
- busy = card.status == "busy" if card.status in ("busy", "idle") else None
160
+ # "waiting" (on a permission prompt or a question) is still mid-turn.
161
+ busy = card.status in ("busy", "waiting") if card.status in ("busy", "idle", "waiting") else None
162
+ if card.status == "waiting":
163
+ waiting_for = card.waiting_for or "permission prompt"
160
164
  else:
161
165
  process_pid = next((event.agent_pid for event in reversed(session_events) if event.agent_pid is not None), None)
162
166
  if process_pid is not None and not _pid_alive(process_pid):
@@ -235,6 +239,7 @@ class AgentHookEventReader:
235
239
  process_pid=process_pid,
236
240
  name=card.name if card is not None else (opencode_session.title if opencode_session is not None else None),
237
241
  details=details,
242
+ waiting_for=waiting_for,
238
243
  )
239
244
  )
240
245
  return scans, next_change
@@ -61,3 +61,5 @@ class AgentRepositoryScan:
61
61
  process_pid: int | None = None
62
62
  name: str | None = None
63
63
  details: SessionDetails | None = None
64
+ # What the agent is blocked on ("permission prompt", "input needed"), or None.
65
+ waiting_for: str | None = None
@@ -21,6 +21,8 @@ class ClaudeSession:
21
21
  status_updated_at: int | None = None
22
22
  version: str | None = None
23
23
  entrypoint: str | None = None
24
+ # Set with status "waiting": "permission prompt" or "input needed".
25
+ waiting_for: str | None = None
24
26
 
25
27
 
26
28
  def registry_signature(directory: Path | None) -> tuple[tuple[str, int], ...] | None:
@@ -69,5 +71,6 @@ def read_registry(directory: Path | None) -> dict[str, ClaudeSession] | None:
69
71
  status_updated_at=data.get("statusUpdatedAt") if isinstance(data.get("statusUpdatedAt"), int) else None,
70
72
  version=data.get("version") if isinstance(data.get("version"), str) else None,
71
73
  entrypoint=data.get("entrypoint") if isinstance(data.get("entrypoint"), str) else None,
74
+ waiting_for=data.get("waitingFor") if isinstance(data.get("waitingFor"), str) else None,
72
75
  )
73
76
  return sessions
@@ -2,18 +2,20 @@ from __future__ import annotations
2
2
 
3
3
  import sys
4
4
  from pathlib import Path
5
+ from typing import Annotated
5
6
 
6
7
  from fastapi import FastAPI, HTTPException, Query, WebSocket, WebSocketDisconnect
7
8
  from fastapi.middleware.cors import CORSMiddleware
8
9
  from fastapi.responses import FileResponse
9
10
  from fastapi.staticfiles import StaticFiles
10
11
 
11
- from . import config
12
+ from . import config, github_actions, jenkins
12
13
  from .agent_hook_events import AgentHookEventReader
13
14
  from .agent_hooks import hooks_status, install_claude_hooks, install_opencode_hooks
14
15
  from .agent_tracking.activity_models import AgentRepositoryScan
15
16
  from .browse import browse_directory
16
- from .ci import get_active_pipelines, get_ci_run_stages, get_ci_runs
17
+ from .pick_folder import NativePickerUnavailable, pick_folder
18
+ from .ci import get_active_pipelines, get_ci_run_stages, get_ci_runs, poll_ci_runs
17
19
  from .commit_detail import get_commit_detail
18
20
  from .file_diff import get_commit_file_diff, get_working_file_diff
19
21
  from .git_data import (
@@ -31,13 +33,17 @@ from .schemas import (
31
33
  AgentHooksResponse,
32
34
  AgentRepositoryScanResponse,
33
35
  BrowseDirectoryResponse,
36
+ PickFolderResponse,
34
37
  CiRunInfo,
35
38
  CiStage,
39
+ CiConnectionTestResponse,
36
40
  CommitDetail,
37
41
  ConfigResponse,
38
42
  ConfigUpdateRequest,
39
43
  FileDiff,
44
+ GitHubConfig,
40
45
  GraphResponse,
46
+ JenkinsConfig,
41
47
  Preferences,
42
48
  RepoScanProgress,
43
49
  RepoStatusResponse,
@@ -81,6 +87,13 @@ def create_app(root_path: Path, frontend_dist: Path | None = None) -> FastAPI:
81
87
  def api_browse(path: str | None = None) -> BrowseDirectoryResponse:
82
88
  return browse_directory(path)
83
89
 
90
+ @app.post("/api/pick-folder", response_model=PickFolderResponse)
91
+ def api_pick_folder() -> PickFolderResponse:
92
+ try:
93
+ return PickFolderResponse(path=pick_folder())
94
+ except NativePickerUnavailable as e:
95
+ raise HTTPException(status_code=501, detail=str(e))
96
+
84
97
  terminal_shell = "cmd" if sys.platform == "win32" else "posix"
85
98
 
86
99
  def _current_config() -> ConfigResponse:
@@ -233,6 +246,20 @@ def create_app(root_path: Path, frontend_dist: Path | None = None) -> FastAPI:
233
246
  config.load_jenkins_config(),
234
247
  )
235
248
 
249
+ # Live poll of a repo's pipelines. Without `run`, every active run of the
250
+ # repo (`head_sha` narrows it where the provider can filter): used right
251
+ # after a commit action, when the run may not be known yet. With one or more
252
+ # `run=<provider>:<run_id>` (CiRunInfo.provider / run_id), only those runs,
253
+ # including their final status once finished.
254
+ @app.get("/api/repos/{repo_id}/ci/poll", response_model=list[CiRunInfo])
255
+ def api_ci_poll(
256
+ repo_id: str,
257
+ run: Annotated[list[str] | None, Query()] = None,
258
+ head_sha: str | None = None,
259
+ ) -> list[CiRunInfo]:
260
+ path = _resolve_repo_path(repo_id)
261
+ return poll_ci_runs(path, run, head_sha, config.load_github_config(), config.load_jenkins_config())
262
+
236
263
  # Stages (GitHub jobs / Jenkins pipeline stages) of one run. `run_id` is the
237
264
  # value CiRunInfo.run_id carried; 404 when the provider has no stage data.
238
265
  @app.get("/api/repos/{repo_id}/ci/stages", response_model=list[CiStage])
@@ -258,6 +285,16 @@ def create_app(root_path: Path, frontend_dist: Path | None = None) -> FastAPI:
258
285
  config.load_jenkins_config(),
259
286
  )
260
287
 
288
+ @app.post("/api/ci/github/test", response_model=CiConnectionTestResponse)
289
+ def api_test_github_connection(body: GitHubConfig) -> CiConnectionTestResponse:
290
+ ok, message = github_actions.test_connection(body.model_dump())
291
+ return CiConnectionTestResponse(ok=ok, message=message)
292
+
293
+ @app.post("/api/ci/jenkins/test", response_model=CiConnectionTestResponse)
294
+ def api_test_jenkins_connection(body: JenkinsConfig) -> CiConnectionTestResponse:
295
+ ok, message = jenkins.test_connection(body.model_dump())
296
+ return CiConnectionTestResponse(ok=ok, message=message)
297
+
261
298
  hook_event_reader = AgentHookEventReader()
262
299
 
263
300
  def _agent_scan_response(scan: AgentRepositoryScan) -> AgentRepositoryScanResponse:
@@ -288,6 +325,7 @@ def create_app(root_path: Path, frontend_dist: Path | None = None) -> FastAPI:
288
325
  process_pid=scan.process_pid,
289
326
  name=scan.name,
290
327
  details=scan.details.__dict__ if scan.details is not None else None,
328
+ waiting_for=scan.waiting_for,
291
329
  )
292
330
 
293
331
  def _hook_status_response(status) -> AgentHookProviderStatusResponse:
@@ -0,0 +1,146 @@
1
+ from __future__ import annotations
2
+
3
+ from concurrent.futures import ThreadPoolExecutor
4
+ from dataclasses import dataclass
5
+ from pathlib import Path
6
+ from typing import Protocol
7
+
8
+ from . import github_actions, jenkins
9
+ from .schemas import ActivePipeline, CiRunInfo, CiStage, RepoSummary
10
+
11
+
12
+ class CiProvider(Protocol):
13
+ """What a CI backend (GitHub Actions, Jenkins) offers the app."""
14
+
15
+ name: str
16
+
17
+ def fetch_completed_runs(self, repo_path: Path, commit_hashes: set[str]) -> dict[str, list[CiRunInfo]]:
18
+ """Recent runs of the repo keyed by commit hash. Loaded with the commit
19
+ graph and refreshed on its own, slow, interval — never by the run poll."""
20
+ ...
21
+
22
+ def poll_runs(self, repo_path: Path, run_ids: list[str] | None, head_sha: str | None) -> list[CiRunInfo]:
23
+ """`run_ids=None`: every active (queued/running) run of the repo, to
24
+ discover a pipeline that just started (`head_sha` narrows it to one
25
+ commit where the provider can filter). Otherwise exactly those runs,
26
+ whatever their state, so a finished run reports its final status.
27
+ Stages are included for runs that are still going."""
28
+ ...
29
+
30
+
31
+ @dataclass
32
+ class GitHubActionsProvider:
33
+ config: dict | None
34
+ name: str = "github_actions"
35
+
36
+ def fetch_completed_runs(self, repo_path: Path, commit_hashes: set[str]) -> dict[str, list[CiRunInfo]]:
37
+ return github_actions.get_github_actions_runs(repo_path, commit_hashes, self.config or {})
38
+
39
+ def poll_runs(self, repo_path: Path, run_ids: list[str] | None, head_sha: str | None) -> list[CiRunInfo]:
40
+ return github_actions.poll_runs(repo_path, self.config, run_ids, head_sha)
41
+
42
+
43
+ @dataclass
44
+ class JenkinsProvider:
45
+ config: dict
46
+ name: str = "jenkins"
47
+
48
+ def fetch_completed_runs(self, repo_path: Path, commit_hashes: set[str]) -> dict[str, list[CiRunInfo]]:
49
+ return jenkins.get_jenkins_builds(repo_path, commit_hashes, self.config)
50
+
51
+ def poll_runs(self, repo_path: Path, run_ids: list[str] | None, head_sha: str | None) -> list[CiRunInfo]:
52
+ return jenkins.poll_runs(repo_path, self.config, run_ids, head_sha)
53
+
54
+
55
+ def enabled_providers(github_config: dict | None, jenkins_config: dict | None) -> list[CiProvider]:
56
+ providers: list[CiProvider] = []
57
+ if github_config is None or github_config.get("enabled") is not False:
58
+ providers.append(GitHubActionsProvider(github_config))
59
+ if jenkins_config and jenkins_config.get("enabled") is not False:
60
+ providers.append(JenkinsProvider(jenkins_config))
61
+ return providers
62
+
63
+
64
+ def get_ci_runs(
65
+ repo_path: Path,
66
+ commit_hashes: set[str],
67
+ github_config: dict | None,
68
+ jenkins_config: dict | None,
69
+ ) -> dict[str, list[CiRunInfo]]:
70
+ by_sha: dict[str, list[CiRunInfo]] = {}
71
+ for provider in enabled_providers(github_config, jenkins_config):
72
+ for sha, runs in provider.fetch_completed_runs(repo_path, commit_hashes).items():
73
+ by_sha.setdefault(sha, []).extend(runs)
74
+ return by_sha
75
+
76
+
77
+ def poll_ci_runs(
78
+ repo_path: Path,
79
+ run_refs: list[str] | None,
80
+ head_sha: str | None,
81
+ github_config: dict | None,
82
+ jenkins_config: dict | None,
83
+ ) -> list[CiRunInfo]:
84
+ """Poll the CI providers. `run_refs=None` discovers the active runs of every
85
+ provider; otherwise each ref is `"<provider>:<run_id>"` and only the
86
+ providers named there are asked, about just those runs."""
87
+ providers = enabled_providers(github_config, jenkins_config)
88
+ if run_refs is None:
89
+ return [run for provider in providers for run in provider.poll_runs(repo_path, None, head_sha)]
90
+
91
+ ids_by_provider: dict[str, list[str]] = {}
92
+ for ref in run_refs:
93
+ provider_name, _, run_id = ref.partition(":")
94
+ if run_id:
95
+ ids_by_provider.setdefault(provider_name, []).append(run_id)
96
+ return [
97
+ run
98
+ for provider in providers
99
+ if provider.name in ids_by_provider
100
+ for run in provider.poll_runs(repo_path, ids_by_provider[provider.name], head_sha)
101
+ ]
102
+
103
+
104
+ def get_ci_run_stages(
105
+ repo_path: Path,
106
+ provider: str,
107
+ run_id: str,
108
+ github_config: dict | None,
109
+ jenkins_config: dict | None,
110
+ ) -> list[CiStage] | None:
111
+ if provider == "github_actions" and (github_config is None or github_config.get("enabled") is not False):
112
+ return github_actions.get_run_stages(repo_path, github_config, run_id)
113
+ if provider == "jenkins" and jenkins_config and jenkins_config.get("enabled") is not False:
114
+ return jenkins.get_build_stages(repo_path, jenkins_config, run_id)
115
+ return None
116
+
117
+
118
+ def _active_runs_for_repo(repo: RepoSummary, github_config: dict | None, jenkins_config: dict | None) -> list[ActivePipeline]:
119
+ runs = poll_ci_runs(Path(repo.path), None, None, github_config, jenkins_config)
120
+ return [ActivePipeline(repo_id=repo.id, repo_name=repo.name, run=run) for run in runs]
121
+
122
+
123
+ def _pipeline_key(pipeline: ActivePipeline) -> tuple[str, str]:
124
+ run = pipeline.run
125
+ if run.run_id:
126
+ return (run.provider, run.run_id)
127
+ return (run.provider, run.url)
128
+
129
+
130
+ def get_active_pipelines(
131
+ repos: list[RepoSummary],
132
+ github_config: dict | None,
133
+ jenkins_config: dict | None,
134
+ ) -> list[ActivePipeline]:
135
+ """Queued/running pipelines across every known repo. Repos are queried
136
+ concurrently since each one is a network round trip."""
137
+ if not repos:
138
+ return []
139
+ with ThreadPoolExecutor(max_workers=min(8, len(repos))) as pool:
140
+ per_repo = pool.map(lambda repo: _active_runs_for_repo(repo, github_config, jenkins_config), repos)
141
+ pipelines = [item for group in per_repo for item in group]
142
+ deduped_by_key: dict[tuple[str, str], ActivePipeline] = {}
143
+ for pipeline in pipelines:
144
+ deduped_by_key.setdefault(_pipeline_key(pipeline), pipeline)
145
+ deduped = list(deduped_by_key.values())
146
+ return sorted(deduped, key=lambda item: item.run.created_at or "", reverse=True)
@@ -4,6 +4,7 @@ from pathlib import Path
4
4
 
5
5
  from git import NULL_TREE, Repo
6
6
 
7
+ from .git_data import _parse_numstat, _run_git_or_empty
7
8
  from .schemas import CommitDetail, FileChange, PersonInfo
8
9
 
9
10
  _STATUS_MAP = {
@@ -22,15 +23,33 @@ def get_commit_detail(repo_path: Path, sha: str) -> CommitDetail:
22
23
 
23
24
  if commit.parents:
24
25
  diffs = commit.parents[0].diff(commit)
26
+ numstat_raw = _run_git_or_empty(
27
+ repo_path, "diff", "--numstat", "-z", "-M",
28
+ commit.parents[0].hexsha, commit.hexsha,
29
+ )
25
30
  else:
26
31
  diffs = commit.diff(NULL_TREE)
32
+ numstat_raw = _run_git_or_empty(
33
+ repo_path, "diff-tree", "--root", "--no-commit-id", "-r", "--numstat", "-z", "-M",
34
+ commit.hexsha,
35
+ )
36
+ stats = _parse_numstat(numstat_raw)
27
37
 
28
38
  files: list[FileChange] = []
29
39
  for d in diffs:
30
40
  status = _STATUS_MAP.get(d.change_type or "M", "modified")
31
41
  path = d.b_path or d.a_path or "?"
32
42
  old_path = d.a_path if d.a_path and d.a_path != path else None
33
- files.append(FileChange(path=path, status=status, old_path=old_path))
43
+ counts = stats.get(path)
44
+ files.append(
45
+ FileChange(
46
+ path=path,
47
+ status=status,
48
+ old_path=old_path,
49
+ additions=counts[0] if counts else None,
50
+ deletions=counts[1] if counts else None,
51
+ )
52
+ )
34
53
  files.sort(key=lambda f: f.path)
35
54
 
36
55
  subject = commit.summary if isinstance(commit.summary, str) else commit.summary.decode()
@@ -0,0 +1 @@
1
+ :root,:root[data-theme=dark]{--text:#c8ccd4;--text-dim:#7d8590;--text-h:#f0f2f5;--bg:#0d1117;--panel-bg:#12151b;--hover-bg:#1c2129;--active-bg:#202632;--border:#262b33;--accent:#4c9aff;--accent-fg:#0d1117;--danger:#ff5630;--danger-fg:#ffb4b4;--danger-bg:#4a1f24;--warning:#ffd666;--success:#36b37e;--menu-bg:#12151b;--input-bg:#0d1117;--shadow:#00000080;--shadow-soft:#00000073;--lightningcss-light: ;--lightningcss-dark:initial;color-scheme:dark}:root[data-theme=light]{--text:#3a3f46;--text-dim:#6e7781;--text-h:#1f2328;--bg:#fff;--panel-bg:#f6f8fa;--hover-bg:#eef0f2;--active-bg:#e4e7eb;--border:#d0d7de;--accent:#0969da;--accent-fg:#fff;--danger:#cf222e;--danger-fg:#a40e26;--danger-bg:#ffebe9;--warning:#9a6700;--success:#1a7f37;--menu-bg:#fff;--input-bg:#fff;--lightningcss-light:initial;--lightningcss-dark: ;color-scheme:light}:root{color:var(--text);background:var(--bg);-webkit-font-smoothing:antialiased;font:13px/1.4 -apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif}*{box-sizing:border-box}.sidebar-content,.graph-and-list{scrollbar-width:thin;scrollbar-color:color-mix(in srgb, var(--text-dim) 40%, transparent) transparent;scrollbar-width:thin;scrollbar-color:transparent transparent}.sidebar-content:hover,.graph-and-list:hover{scrollbar-color:color-mix(in srgb, var(--text-dim) 40%, transparent) transparent}body{margin:0}#root{width:100vw;height:100vh}.app{width:100vw;height:100vh;display:flex;overflow:hidden}.menu-overlay{background:var(--shadow);z-index:100;justify-content:center;align-items:flex-start;padding-top:12vh;transition:background-color .12s;display:flex;position:fixed;inset:0}.menu-overlay.previewing-theme{background:0 0}.menu-dialog{background:var(--menu-bg);border:1px solid var(--border);width:640px;max-width:calc(100vw - 32px);height:640px;max-height:64vh;box-shadow:0 16px 48px var(--shadow);border-radius:8px;display:flex;overflow:hidden}.menu-sidebar{background:var(--bg);border-right:1px solid var(--border);flex:none;width:140px;padding:8px 0}.command-palette-overlay{background:var(--shadow);z-index:110;justify-content:center;align-items:flex-start;padding-top:12vh;display:flex;position:fixed;inset:0}.command-palette-dialog{background:var(--menu-bg);border:1px solid var(--border);width:560px;max-width:calc(100vw - 32px);max-height:60vh;box-shadow:0 16px 48px var(--shadow);border-radius:8px;flex-direction:column;display:flex;overflow:hidden}.command-palette-input{background:var(--input-bg);border:0;border-bottom:1px solid var(--border);color:var(--text);font:inherit;border-radius:0;flex:none;padding:14px 18px;font-size:15px}.command-palette-input:focus{outline:none}.command-palette-results{flex:1;min-height:0;padding:6px;overflow-y:auto}.command-palette-item{color:var(--text);cursor:pointer;border-radius:5px;justify-content:space-between;align-items:center;gap:12px;padding:8px 10px;font-size:13px;display:flex}.command-palette-item.active{background:var(--active-bg);color:var(--text-h)}.command-palette-item-label{text-overflow:ellipsis;white-space:nowrap;min-width:0;overflow:hidden}.command-palette-item-meta{text-overflow:ellipsis;white-space:nowrap;max-width:55%;color:var(--text-dim);flex:none;font-size:11px;overflow:hidden}.menu-section-item{color:var(--text-dim);cursor:pointer;padding:8px 16px;font-size:13px}.menu-section-item:hover{background:var(--hover-bg)}.menu-section-item.active{background:var(--active-bg);color:var(--text-h)}.menu-content{flex:1;min-width:0;padding:20px 24px;overflow-y:auto}.menu-section{flex-direction:column;gap:16px;display:flex}.menu-section>*{margin:0}.menu-hint{color:var(--text-dim);margin:0 0 16px;font-size:12px}.menu-path-list{flex-direction:column;gap:4px;margin-bottom:12px;display:flex}.menu-path-row{background:var(--bg);border:1px solid var(--border);border-radius:4px;justify-content:space-between;align-items:center;gap:8px;padding:6px 10px;display:flex}.menu-path-text{text-overflow:ellipsis;white-space:nowrap;font-family:ui-monospace,Menlo,Consolas,monospace;font-size:12px;overflow:hidden}.menu-path-remove{cursor:pointer;color:var(--text-dim);text-align:center;border-radius:3px;flex:none;width:18px;height:18px;line-height:18px}.menu-path-remove:hover{background:var(--hover-bg);color:var(--text-h)}.menu-empty{color:var(--text-dim);padding:6px 0;font-size:13px}.keybinding-value{color:var(--text-dim);white-space:nowrap;font-family:ui-monospace,Menlo,Consolas,monospace;font-size:12px}.keybinding-actions{flex:none;gap:6px;display:flex}.menu-add-path{gap:8px;display:flex}.menu-add-path input{background:var(--input-bg);border:1px solid var(--border);min-width:0;color:var(--text);font:inherit;border-radius:4px;flex:1;padding:6px 8px;font-size:12px}.menu-add-path input:focus{border-color:var(--accent);outline:none}.menu-add-path .menu-add-button{background:var(--accent);color:var(--accent-fg);cursor:pointer;border:none;border-radius:4px;padding:0 14px;font-size:12px;font-weight:600}.menu-add-path .menu-secondary-button{cursor:pointer;border-radius:4px;padding:0 14px;font-size:12px}.menu-error{color:var(--danger-fg);margin-top:10px;font-size:12px}.menu-notice{border:1px solid color-mix(in srgb, var(--accent) 45%, transparent);background:color-mix(in srgb, var(--accent) 12%, transparent);color:var(--text);border-radius:6px;margin:0 0 14px;padding:10px 12px;font-size:12px}.menu-test-result{border:1px solid color-mix(in srgb, var(--text-dim) 45%, transparent);background:color-mix(in srgb, var(--text-dim) 12%, transparent);color:var(--text);border-radius:6px;margin:10px 0 14px;padding:10px 12px;font-size:12px}.menu-test-result.success{border-color:color-mix(in srgb, var(--accent) 45%, transparent);background:color-mix(in srgb, var(--accent) 12%, transparent)}.menu-test-result.error{border-color:color-mix(in srgb, var(--danger-fg) 45%, transparent);background:color-mix(in srgb, var(--danger-fg) 12%, transparent)}.menu-field{color:var(--text-dim);flex-direction:column;gap:5px;margin-bottom:14px;font-size:12px;display:flex}.menu-field>span:first-child{color:var(--text-h);font-size:13px}.menu-field .menu-hint{margin:0 0 3px}.menu-field input,.menu-field textarea,.github-repo-row input{background:var(--input-bg);border:1px solid var(--border);color:var(--text);font:inherit;border-radius:4px;padding:6px 8px;font-size:12px}.menu-field textarea{resize:vertical;min-height:44px}.menu-field input:focus,.menu-field textarea:focus,.github-repo-row input:focus{border-color:var(--accent);outline:none}.create-tag-dialog{background:var(--menu-bg);border:1px solid var(--border);width:420px;max-width:calc(100vw - 32px);max-height:80vh;box-shadow:0 16px 48px var(--shadow);border-radius:8px;flex-direction:column;padding:20px 24px;display:flex;overflow-y:auto}.create-tag-dialog h3{color:var(--text-h);margin:0 0 6px;font-size:15px}.directory-browser-path{color:var(--text-dim);word-break:break-all;font-family:ui-monospace,Menlo,Consolas,monospace;font-size:12px}.directory-browser-list{border:1px solid var(--border);border-radius:4px;flex-direction:column;gap:2px;max-height:280px;margin:12px 0;padding:4px;display:flex;overflow-y:auto}.directory-browser-entry{cursor:pointer;color:var(--text);border-radius:4px;padding:6px 8px;font-size:13px}.directory-browser-entry:hover{background:var(--hover-bg);color:var(--text-h)}.branch-delete-modal{background:var(--menu-bg);border:1px solid var(--border);width:420px;max-width:calc(100vw - 32px);box-shadow:0 16px 48px var(--shadow);border-radius:8px;flex-direction:column;padding:20px 24px;display:flex}.branch-delete-modal h3{color:var(--text-h);margin:0 0 6px;font-size:15px}.modal-check-row{color:var(--text);align-items:center;gap:8px;margin-top:10px;font-size:12px;display:flex}.menu-primary-button.danger{border-color:var(--danger);color:var(--danger-fg)}.github-repo-mappings{flex-direction:column;gap:8px;display:flex}.github-repo-row{grid-template-columns:minmax(160px,1fr) minmax(80px,.45fr) minmax(80px,.45fr) auto;gap:8px;display:grid}.menu-actions{justify-content:flex-end;gap:8px;margin-top:14px;display:flex}.menu-primary-button,.menu-secondary-button{font:inherit;cursor:pointer;border-radius:4px;padding:6px 10px;font-size:12px}.menu-primary-button{background:var(--accent);border:1px solid var(--accent);color:var(--accent-fg);font-weight:600}.menu-secondary-button{background:var(--bg);border:1px solid var(--border);color:var(--text)}.menu-secondary-button:hover{background:var(--hover-bg);color:var(--text-h)}.menu-secondary-button.danger,.menu-secondary-button.danger:hover{border-color:var(--danger);color:var(--danger-fg)}.menu-setting{justify-content:space-between;align-items:center;gap:16px;display:flex}.menu-setting-main{min-width:0}.menu-setting-label{color:var(--text-h);font-size:13px}.menu-setting .menu-hint{margin:2px 0 0}.agent-hooks-section{flex-direction:column;gap:10px;display:flex}.agent-hooks-section>.menu-actions{margin-top:0}.agent-hooks-section .menu-setting-main .menu-hint{margin:2px 0 0}.agent-hook-grid{gap:12px;margin-top:12px;display:grid}.agent-hook-card{border:1px solid var(--border);background:var(--panel-bg);border-radius:8px;padding:12px}.agent-hook-card-heading{justify-content:space-between;align-items:flex-start;gap:12px;display:flex}.agent-hook-status{border:1px solid var(--border);color:var(--text-muted);border-radius:999px;flex:none;padding:2px 8px;font-size:11px}.agent-hook-status.installed{border-color:color-mix(in srgb, var(--success) 45%, transparent);color:var(--success)}.agent-hook-meta{color:var(--text-muted);overflow-wrap:anywhere;margin-top:6px;font-size:12px}.agent-hook-snippet{border:1px solid var(--border);background:var(--bg);max-height:220px;color:var(--text);white-space:pre-wrap;border-radius:6px;margin:10px 0 0;padding:10px;font-size:11px;overflow:auto}.theme-options{gap:6px;display:flex}.theme-options button{background:var(--bg);border:1px solid var(--border);color:var(--text);font:inherit;cursor:pointer;border-radius:4px;padding:5px 12px;font-size:12px}.theme-options button.active{background:var(--accent);border-color:var(--accent);color:var(--accent-fg);font-weight:600}.menu-setting-stacked{flex-direction:column;align-items:stretch;gap:10px}.theme-picker{flex-direction:column;gap:8px;min-width:0;display:flex}.theme-picker-toolbar{gap:8px;display:flex}.theme-picker-toolbar input[type=search]{background:var(--input-bg);border:1px solid var(--border);min-width:0;color:var(--text);font:inherit;border-radius:4px;flex:1;padding:5px 8px;font-size:12px}.theme-picker-toolbar input[type=search]:focus{border-color:var(--accent);outline:none}.theme-list{border:1px solid var(--border);border-radius:6px;max-height:300px;padding:4px;overflow-y:auto}.theme-group-label{letter-spacing:.04em;text-transform:uppercase;color:var(--text-dim);padding:8px 8px 4px;font-size:11px}.theme-row-wrap{align-items:center;display:flex}.theme-row{min-width:0;color:var(--text);font:inherit;text-align:left;cursor:pointer;background:0 0;border:1px solid #0000;border-radius:4px;flex:1;align-items:center;gap:10px;padding:5px 8px;font-size:12px;display:flex}.theme-row:hover,.theme-row:focus-visible{background:var(--hover-bg);color:var(--text-h);outline:none}.theme-row.active{background:var(--active-bg);border-color:var(--accent);color:var(--text-h)}.theme-row-name{text-overflow:ellipsis;white-space:nowrap;flex:1;min-width:0;overflow:hidden}.theme-row-kind{color:var(--text-dim);font-size:11px}.theme-row-pin{color:var(--text-dim);cursor:pointer;background:0 0;border:none;padding:4px 6px;font-size:14px;line-height:1}.theme-row-pin:hover,.theme-row-pin.pinned{color:var(--warning)}.theme-row-remove{color:var(--text-dim);cursor:pointer;background:0 0;border:none;padding:4px 8px;font-size:16px;line-height:1}.theme-row-remove:hover{color:var(--danger)}.theme-swatch{border:1px solid;border-radius:4px;flex:none;align-items:center;gap:3px;height:20px;padding:0 5px 0 0;display:inline-flex;overflow:hidden}.theme-swatch-panel{align-self:stretch;width:8px}.theme-swatch-dot{border-radius:50%;width:7px;height:7px}.switch{cursor:pointer;flex:none;align-items:center;display:inline-flex}.switch input{display:none}.switch-track{background:var(--border);border-radius:999px;width:34px;height:18px;transition:background .15s;position:relative}.switch-thumb{background:#fff;border-radius:50%;width:14px;height:14px;transition:transform .15s;position:absolute;top:2px;left:2px}.switch input:checked+.switch-track{background:var(--accent)}.switch input:checked+.switch-track .switch-thumb{transform:translate(16px)}.sidebar{background:var(--panel-bg);border-right:1px solid var(--border);-webkit-user-select:none;user-select:none;flex-direction:column;flex:none;width:250px;min-width:200px;display:flex;overflow:hidden}.sidebar-content{flex:auto;min-height:0;overflow-y:auto}.sidebar-tabs{border-top:1px solid var(--border);background:var(--panel-bg);flex:none;display:flex}.sidebar-tab{color:var(--text-dim);cursor:pointer;font:inherit;background:0 0;border:0;border-top:2px solid #0000;flex:1;justify-content:center;align-items:center;gap:6px;margin-top:-1px;padding:8px 10px;font-size:12px;display:inline-flex}.sidebar-tab:hover{color:var(--text-h);background:var(--hover-bg)}.sidebar-tab.active{color:var(--text-h);border-top-color:var(--accent)}.sidebar-tab-badge,.tab-agent-badge{background:var(--accent);min-width:15px;color:var(--accent-fg);text-align:center;border-radius:999px;flex:none;padding:0 5px;font-size:10px;line-height:15px}.sidebar-tab-badge.idle,.tab-agent-badge.idle{background:var(--border);color:var(--text-dim)}.sidebar-resize-handle{cursor:col-resize;z-index:4;flex:0 0 5px;margin-left:-3px}.sidebar-resize-handle:hover,.sidebar-resize-handle:active{background:var(--accent);opacity:.5}.repo-search{align-items:center;gap:6px;padding:10px 16px 4px;display:flex}.agent-activity-panel{border:1px solid var(--border);background:color-mix(in srgb, var(--accent) 6%, transparent);border-radius:8px;margin:10px 10px 6px;padding:10px}.agent-activity-heading{color:var(--text-h);justify-content:space-between;align-items:center;gap:8px;margin-bottom:8px;font-size:12px;font-weight:700;display:flex}.agent-scan-time{color:var(--text-dim);font-size:10px;font-weight:500}.agent-discovery-row{color:var(--text-dim);align-items:center;font-size:11px;display:flex}.agent-error{color:var(--danger-fg);margin-top:8px;font-size:11px}.agent-results{flex-direction:column;gap:6px;margin-top:8px;display:flex}.agent-summary,.agent-empty{color:var(--text-dim);font-size:11px}.agent-worktree-card{border:1px solid var(--border);background:var(--panel-bg);border-radius:6px;padding:7px}.agent-worktree-card.active{border-color:color-mix(in srgb, var(--accent) 55%, var(--border));background:color-mix(in srgb, var(--accent) 8%, var(--panel-bg));box-shadow:0 0 0 1px color-mix(in srgb, var(--accent) 25%, transparent)}.agent-scan-card{border-top:1px solid var(--border);border-left:2px solid #0000;flex-direction:column;gap:6px;padding-top:6px;padding-left:8px;display:flex}.agent-scan-card.active{border-left-color:var(--accent);background:color-mix(in srgb, var(--accent) 6%, transparent);border-radius:0 6px 6px 0}.agent-scan-title{color:var(--text-h);font-size:11px;font-weight:700}.agent-command{color:var(--text-dim);text-overflow:ellipsis;white-space:nowrap;font-family:ui-monospace,Menlo,Consolas,monospace;font-size:9px;overflow:hidden}.agent-session-directory{color:var(--text-dim);text-overflow:ellipsis;white-space:nowrap;font-size:10px;overflow:hidden}.agent-worktree-title{color:var(--text-h);text-overflow:ellipsis;white-space:nowrap;font-size:12px;font-weight:700;overflow:hidden}.agent-worktree-meta{color:var(--text-dim);text-overflow:ellipsis;white-space:nowrap;font-size:10px;line-height:1.45;overflow:hidden}.agent-hover-target{pointer-events:all}.agent-hover-card{z-index:60;pointer-events:none;flex-direction:column;gap:10px;display:flex;position:fixed}.agent-state-pill{background:color-mix(in srgb, var(--accent) 18%, transparent);border-radius:8px;margin-left:6px;padding:0 6px;font-size:10px}.agent-state-pill.idle{background:var(--border);color:var(--text-dim)}.agent-evidence-row{flex-wrap:wrap;gap:4px;margin-top:5px;display:flex}.agent-evidence-pill{border:1px solid color-mix(in srgb, var(--accent) 35%, transparent);color:var(--text-dim);border-radius:999px;padding:1px 5px;font-size:9px}.repo-search input{background:var(--input-bg);border:1px solid var(--border);min-width:0;color:var(--text);font:inherit;border-radius:4px;flex:1;padding:4px 8px;font-size:12px}.repo-search input:focus{border-color:var(--accent);outline:none}.repo-bulk-button{border:1px solid var(--border);background:var(--bg);width:26px;height:26px;color:var(--text-dim);cursor:pointer;border-radius:4px;flex:none;line-height:1}.repo-bulk-button:hover{border-color:var(--accent);color:var(--text-h)}.repo-list h2{text-transform:uppercase;letter-spacing:.06em;color:var(--text-dim);margin:0;padding:16px 16px 8px;font-size:13px}.repo-item{cursor:pointer;height:40px;color:var(--text);border-left:3px solid #0000;align-items:center;gap:8px;margin:1px 0;padding:0 16px;display:flex}.repo-item:hover{background:var(--hover-bg)}.repo-item.active{background:var(--active-bg);border-left-color:var(--accent);color:var(--text-h)}.repo-item[draggable=true]{cursor:grab}.repo-item[draggable=true]:active{cursor:grabbing}.repo-item.dragging{opacity:.45}.repo-names{flex-direction:column;flex:1;justify-content:center;min-width:0;line-height:1.15;display:flex}.repo-name{text-overflow:ellipsis;white-space:nowrap;font-size:13px;overflow:hidden}.repo-name-line{align-items:center;gap:5px;min-width:0;display:flex}.repo-agent-dot{background:var(--accent);width:7px;height:7px;box-shadow:0 0 0 3px color-mix(in srgb, var(--accent) 18%, transparent);border-radius:50%;flex:none}.repo-agent-dot.idle{background:var(--text-dim);box-shadow:none;opacity:.6}.repo-branch{color:var(--text-dim);text-overflow:ellipsis;white-space:nowrap;font-size:10px;overflow:hidden}.repo-list .repo-group-heading{box-sizing:border-box;align-items:center;gap:8px;padding:8px 16px;transition:height .12s,padding .12s,opacity .12s,background-color .12s;display:flex;overflow:hidden}.repo-group-heading:hover{background:var(--hover-bg)}.repo-group-name{text-overflow:ellipsis;white-space:nowrap;cursor:grab;text-transform:uppercase;letter-spacing:normal;flex:auto;align-items:center;min-width:0;margin:-8px 0;padding:8px 0;font-size:12px;font-weight:500;display:flex;overflow:hidden}.repo-group-display-name{text-transform:uppercase}.repo-group-name:active{cursor:grabbing}.repo-group-name-input{min-width:0;font:inherit;letter-spacing:normal;color:var(--text-h);background:var(--bg);border:1px solid var(--accent);border-radius:4px;outline:none;flex:auto;margin:-3px 0 -3px -5px;padding:2px 4px}.repo-group-collapse{color:var(--text-dim);cursor:pointer;background:0 0;border:none;flex:none;justify-content:center;align-items:center;padding:2px;display:flex}.repo-group-collapse:hover{color:var(--text-h)}.repo-group-collapse svg{transition:transform .12s}.repo-group-collapse.collapsed svg{transform:rotate(-90deg)}.repo-select,.repo-group-select{accent-color:var(--accent);cursor:pointer;flex:none;margin:0}.repo-select{margin-left:auto}.repo-group-heading.dragging{opacity:.45}.repo-group-section{padding-top:4px;padding-bottom:8px;transition:background-color .15s}.repo-group-section.collapsed{padding-bottom:0}.repo-bookmark-overlay{z-index:80;position:fixed;inset:0}.repo-bookmark-menu{background:var(--menu-bg);border:1px solid var(--border);min-width:180px;max-width:260px;box-shadow:0 12px 32px var(--shadow-soft);border-radius:6px;padding:6px;position:fixed}.repo-bookmark-menu-item{width:100%;min-height:28px;color:var(--text);font:inherit;text-align:left;cursor:pointer;background:0 0;border:none;border-radius:4px;grid-template-columns:18px 1fr;align-items:center;gap:6px;padding:4px 8px;font-size:12px;display:grid}.repo-bookmark-menu-item:hover{background:var(--hover-bg);color:var(--text-h)}.repo-bookmark-menu-item:disabled{cursor:default;opacity:.5}.repo-bookmark-menu-item:disabled:hover{color:var(--text);background:0 0}.repo-bookmark-menu-separator{background:var(--border);height:1px;margin:5px 4px}.repo-empty{color:var(--text-dim);padding:8px 16px;font-size:13px}.main{flex-direction:column;flex:1;min-width:0;display:flex}.main-header{background:var(--panel-bg);border-bottom:1px solid var(--border);flex:none;align-items:stretch;display:flex}.tabs-bar{flex:auto;min-width:0;display:flex;overflow-x:auto}.tab{border-right:1px solid var(--border);color:var(--text-dim);cursor:pointer;white-space:nowrap;-webkit-user-select:none;user-select:none;box-sizing:border-box;align-items:center;gap:8px;padding:5px 8px 5px 12px;transition:width .12s,padding .12s,opacity .12s,background-color .12s;display:flex;overflow:hidden}.tab:active{cursor:grabbing}body.tab-dragging,body.tab-dragging *{cursor:grabbing!important}.tab.dragging{opacity:.45}.tab-ghost{z-index:1000;pointer-events:none;opacity:.85;background:var(--active-bg);transition:none;position:fixed;top:0;left:0;box-shadow:0 4px 12px #00000059}.tab.preview{font-style:italic}.tab.unavailable{opacity:.65}.tab.active{background:var(--active-bg);color:var(--text-h);box-shadow:inset 0 -2px var(--accent)}.tab-names{flex-direction:column;line-height:1.2;display:flex}.tab-branch{color:var(--text-dim);font-size:10px}.tab-close{text-align:center;border-radius:3px;width:16px;height:16px;line-height:16px}.tab-close:hover{background:var(--hover-bg);color:var(--text-h)}.search-box{border-left:1px solid var(--border);flex:none;align-items:center;gap:6px;padding:0 10px;display:flex;position:relative}.search-input{background:var(--input-bg);border:1px solid var(--border);width:200px;color:var(--text);font:inherit;border-radius:4px;padding:4px 8px}.search-input:focus{border-color:var(--accent);outline:none}.search-count{color:var(--text-dim);text-align:center;min-width:14px;font-size:12px}.agent-active-marker{pointer-events:none}.agent-active-marker.idle{opacity:.45}.filter-button{border:1px solid var(--border);background:var(--bg);width:26px;height:26px;color:var(--text-dim);cursor:pointer;border-radius:4px;justify-content:center;align-items:center;display:inline-flex;position:relative}.filter-button:hover,.filter-button.active{color:var(--text-h);border-color:var(--accent)}.filter-count{background:var(--accent);color:#fff;text-align:center;border-radius:999px;min-width:15px;height:15px;padding:0 4px;font-size:10px;line-height:15px;position:absolute;top:-7px;right:-7px}.filter-popover{z-index:25;background:var(--menu-bg);border:1px solid var(--border);min-width:360px;max-width:440px;max-height:320px;box-shadow:0 8px 24px var(--shadow-soft);border-radius:6px;flex-direction:column;gap:4px;padding:8px;display:flex;position:absolute;top:calc(100% + 8px);right:10px;overflow-y:auto}.filter-field{grid-template-columns:130px 1fr;align-items:start;gap:8px;padding:2px 0;display:grid}.filter-field span{color:var(--text-dim);white-space:nowrap;padding-top:6px;font-size:12px}.filter-field input{background:var(--input-bg);border:1px solid var(--border);min-width:0;color:var(--text);font:inherit;border-radius:4px;padding:5px 7px;font-size:12px}.filter-field input:focus{border-color:var(--accent);outline:none}.filter-hint{color:var(--text-dim);margin:2px 0;font-size:11px;line-height:1.4}.multiselect{min-width:0;position:relative}.multiselect-trigger{background:var(--bg);border:1px solid var(--border);width:100%;min-width:0;color:var(--text);font:inherit;cursor:pointer;border-radius:4px;justify-content:space-between;align-items:center;gap:6px;padding:5px 7px;font-size:12px;display:flex}.multiselect-trigger:hover,.multiselect-trigger.active{border-color:var(--accent)}.multiselect-trigger-label{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.multiselect-caret{color:var(--text-dim);flex:none}.multiselect-overlay{z-index:30;position:fixed;inset:0}.multiselect-panel{z-index:31;background:var(--menu-bg);border:1px solid var(--border);max-height:180px;box-shadow:0 8px 24px var(--shadow-soft);border-radius:6px;padding:4px;position:absolute;top:calc(100% + 4px);left:0;right:0;overflow-y:auto}.multiselect-option{color:var(--text);cursor:pointer;border-radius:4px;align-items:center;gap:6px;padding:5px 6px;font-size:12px;display:flex;overflow:hidden}.multiselect-option:hover{background:var(--active-bg)}.multiselect-option span{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.filter-clear-button{border:1px solid var(--border);background:var(--bg);color:var(--text-dim);cursor:pointer;font:inherit;border-radius:4px;align-self:flex-end;padding:4px 7px;font-size:12px}.filter-clear-button:hover{color:var(--text-h);border-color:var(--accent)}.filter-option{text-overflow:ellipsis;white-space:nowrap;width:100%;color:var(--text);cursor:pointer;font:inherit;text-align:left;background:0 0;border:0;border-radius:4px;padding:6px 8px;font-size:12px;overflow:hidden}.filter-option:hover,.filter-option.active{background:var(--active-bg);color:var(--text-h)}.content{flex:1;min-height:0;display:flex;overflow:hidden}.empty-state{color:var(--text-dim);margin:auto}.unavailable-state{text-align:center;max-width:360px;line-height:1.4}.graph-and-list{flex:1;display:flex;overflow-y:auto}.graph-column{background:var(--panel-bg);flex:none}.list-column{flex:1;min-width:0}.error-banner{background:var(--danger-bg);color:var(--danger-fg);padding:8px 16px;font-size:13px}.loading-banner{color:var(--text-dim);padding:8px 16px;font-size:13px}.load-more-sentinel{color:var(--text-dim);align-items:center;min-height:1px;padding:0 16px;font-size:11px;font-style:italic;display:flex}.load-more-sentinel--visible{height:var(--load-more-height)}.ghost-row-spacer{color:var(--text-dim);opacity:0;align-items:center;height:0;padding:0 16px;font-size:11px;font-style:italic;transition:height .18s,opacity .18s;display:flex;overflow:hidden}.ghost-row-spacer--visible{height:var(--ghost-row-height);opacity:.7}.graph-panel{transition:height .18s}.fetch-band-shift{transition:transform .18s}@keyframes push-edge-flash{0%,to{opacity:.45;stroke-width:2px}50%{opacity:1;stroke-width:4px}}.push-edge{animation:.9s ease-in-out infinite push-edge-flash}.push-spinner{filter:drop-shadow(0 0 3px color-mix(in srgb, var(--accent) 55%, transparent))}.ci-active-dot{filter:drop-shadow(0 0 4px color-mix(in srgb, var(--accent) 80%, transparent))}.commit-row{border-bottom:1px solid var(--border);border-left:3px solid #0000}.commit-row.match{border-left-color:var(--warning)}.commit-row.match .commit-subject{color:var(--warning)}@keyframes commit-row-flash{0%{background-color:color-mix(in srgb, var(--accent) 35%, transparent)}to{background-color:#0000}}.commit-row.flash .commit-row-main{animation:.7s ease-out commit-row-flash}.commit-row-main{cursor:pointer;align-items:center;gap:10px;padding:0 16px;display:flex;overflow:visible}.commit-row-main:hover{background:var(--hover-bg)}.commit-row.expanded .commit-row-main{background:var(--active-bg)}.uncommitted-row .commit-row-main{color:var(--text-h)}.uncommitted-badge{background:color-mix(in srgb, var(--text-dim) 15%, transparent);border-color:var(--text-dim);color:var(--text-dim)}.commit-panel-container{container-type:inline-size}.commit-panel{grid-template-columns:minmax(0,1fr) minmax(240px,1.25fr);gap:24px;max-width:820px;display:grid}@container (width<=560px){.commit-panel{grid-template-columns:minmax(0,1fr);gap:16px}}.commit-panel-compose{flex-direction:column;gap:10px;min-width:0;display:flex}.commit-panel-heading{justify-content:space-between;align-items:center;gap:12px;display:flex}.commit-panel-label{letter-spacing:.08em;text-transform:uppercase;color:var(--text-dim);font-family:ui-monospace,Menlo,Consolas,monospace;font-size:10px}.commit-panel-subject-count{color:var(--text-dim);font-family:ui-monospace,Menlo,Consolas,monospace;font-size:11px}.commit-panel-subject-count.over{color:var(--danger)}.commit-panel-message{resize:vertical;background:var(--input-bg);border:1px solid var(--border);min-height:116px;color:var(--text-h);font:inherit;border-radius:8px;padding:10px 12px;font-size:13px;line-height:1.5}.commit-panel-message:focus{border-color:var(--accent);outline:none}.commit-panel-actions{gap:8px;display:flex}.commit-panel-actions button:disabled{cursor:default;opacity:.45;pointer-events:none}.commit-panel-commit{flex:1;justify-content:center;align-items:center;gap:8px;display:flex}.commit-panel-commit-count{opacity:.8;font-family:ui-monospace,Menlo,Consolas,monospace;font-size:11px;font-weight:500}.commit-panel-hint{color:var(--text-dim);font-size:12px;line-height:1.5}.commit-panel-changes{flex-direction:column;min-width:0;display:flex}.commit-panel-changes-header{align-items:center;gap:8px;height:20px;margin-bottom:4px;padding:0 4px;display:flex}.commit-panel-toggle-all{cursor:pointer;display:flex}.commit-panel-changes-title{flex:1}.commit-panel-totals{gap:8px;font-family:ui-monospace,Menlo,Consolas,monospace;font-size:11px;display:flex}.lines-added{color:var(--success)}.lines-deleted{color:var(--danger)}.line-counts{gap:6px;font-family:ui-monospace,Menlo,Consolas,monospace;font-size:11px;display:flex}.commit-file .line-counts{flex:none}.commit-panel-checkbox{border:1px solid var(--text-dim);width:14px;height:14px;color:var(--accent-fg);text-align:center;border-radius:3px;flex:none;font-size:10px;line-height:12px;transition:background-color .12s}.commit-panel-checkbox.on{border-color:var(--accent);background:var(--accent)}.commit-panel-files{flex-direction:column;font-size:13px;display:flex}.commit-panel-file{cursor:pointer;border-radius:3px;align-items:center;gap:8px;height:20px;padding:0 4px;display:flex}.commit-panel-file:hover{background:var(--hover-bg)}.commit-panel-toggle-all:focus-visible,.commit-panel-file:focus-visible{outline:1px solid var(--accent);outline-offset:-1px}.commit-panel-file .file-status{flex:none}.commit-panel-file.status-untracked .file-status,.commit-panel-file.status-added .file-status{color:var(--success)}.commit-panel-file.status-deleted .file-status{color:var(--danger)}.commit-panel-file.status-modified .file-status{color:var(--warning)}.commit-panel-file.status-renamed .file-status{color:var(--accent)}.commit-panel-file-path{white-space:nowrap;flex:1;align-items:baseline;gap:6px;min-width:0;display:flex;overflow:hidden}.commit-panel-file-name{min-width:0;color:var(--text-h);text-overflow:ellipsis;flex:0 auto;font-size:13px;overflow:hidden}.commit-panel-file-name:hover{text-decoration:underline}.commit-panel-file-dir{min-width:0;color:var(--text-dim);text-overflow:ellipsis;font-family:ui-monospace,Menlo,Consolas,monospace;font-size:11px;overflow:hidden}.commit-panel-file-stats{flex:none;align-items:center;gap:8px;font-family:ui-monospace,Menlo,Consolas,monospace;font-size:11px;display:flex}.commit-panel-file-stats .lines-added,.commit-panel-file-stats .lines-deleted{text-align:right;width:30px}.commit-panel-ratio{gap:2px;display:flex}.commit-panel-ratio>span{background:var(--active-bg);border-radius:1px;width:6px;height:6px}.commit-panel-ratio>.ratio-add{background:var(--success)}.commit-panel-ratio>.ratio-del{background:var(--danger)}.commit-hash{color:var(--text-dim);flex:none}.commit-subject{text-overflow:ellipsis;white-space:nowrap;min-width:0;color:var(--text-h);flex:auto;overflow:hidden}.commit-refs{flex:none;gap:4px;display:flex}.commit-author{text-overflow:ellipsis;min-width:0;color:var(--text-dim);white-space:nowrap;flex:0 0 120px;font-size:12px;overflow:hidden}.commit-date{color:var(--text-dim);white-space:nowrap;flex:none;font-size:12px}.badge{white-space:nowrap;border:1px solid;border-radius:999px;align-items:center;gap:3px;padding:1px 7px;font-size:11px;display:inline-flex}.branch-badge.remote{border-style:dashed}.badge-icon{flex:none}.ci-run-badge{color:#24292f;white-space:nowrap;cursor:pointer;background:#fff;border:1px solid #fff;border-radius:999px;align-items:center;gap:4px;padding:1px 7px 1px 3px;font-size:11px;display:inline-flex;position:relative}.ci-run-icon{justify-content:center;align-items:center;width:14px;height:14px;display:inline-flex}.ci-provider-logo{object-fit:contain}.ci-run-status{font-weight:800;line-height:1}.ci-run-tooltip{z-index:95;white-space:normal;cursor:default;width:480px;position:fixed}.ci-run-tooltip-body{flex-direction:column;gap:8px;display:flex}.commit-detail{background:var(--panel-bg);border-top:1px dashed var(--border);padding:12px 16px 16px 46px}.commit-detail-loading{color:var(--text-dim);height:20px;font-size:13px}.commit-meta{grid-template-columns:100px 1fr;gap:4px 12px;margin:0 0 12px;font-size:13px;display:grid}.commit-meta dt{color:var(--text-dim)}.commit-meta dd{color:var(--text);word-break:break-all;margin:0}.mono{font-family:ui-monospace,Menlo,Consolas,monospace}.commit-message{color:var(--text-h);white-space:pre-wrap;overflow-wrap:anywhere;margin:0 0 12px;font-size:13px;line-height:18px}.commit-files{font-size:13px}.commit-file{align-items:center;gap:8px;height:20px;display:flex}.file-status{text-align:center;width:14px;font-family:ui-monospace,Menlo,Consolas,monospace;font-weight:600}.commit-file.status-added .file-status{color:var(--success)}.commit-file.status-deleted .file-status{color:var(--danger)}.commit-file.status-modified .file-status{color:var(--warning)}.commit-file.status-renamed .file-status{color:var(--accent)}.commit-file.clickable,.file-path.clickable{cursor:pointer}.commit-file.clickable:hover .file-path,.file-path.clickable:hover{color:var(--text-h);text-decoration:underline}.file-diff-overlay{align-items:center;padding-top:0}.file-diff-dialog{background:var(--menu-bg);border:1px solid var(--border);width:min(1400px,100vw - 48px);height:calc(100vh - 64px);box-shadow:0 16px 48px var(--shadow);border-radius:8px;flex-direction:column;display:flex;overflow:hidden}.file-diff-header{border-bottom:1px solid var(--border);align-items:center;gap:10px;padding:10px 14px;display:flex}.file-diff-title{min-width:0;color:var(--text-h);overflow-wrap:anywhere;flex:1;font-family:ui-monospace,Menlo,Consolas,monospace;font-size:13px}.file-diff-body{min-height:0;overflow:hidden}.file-diff-full-toggle{flex:none;gap:8px!important}.file-diff-full-toggle label{white-space:nowrap}.file-diff-message{color:var(--text-dim);text-align:center;padding:24px;font-size:13px}.file-diff-main{flex:1;min-height:0;display:flex}.file-diff-files{border-right:1px solid var(--border);flex:0 0 260px;padding:6px 0;font-size:12px;overflow:auto}.file-diff-file{cursor:pointer;color:var(--text);align-items:center;gap:8px;padding:3px 12px;display:flex}.file-diff-file:hover{background:var(--hover-bg)}.file-diff-file.active{background:var(--active-bg);color:var(--text-h)}.file-diff-file-name{white-space:nowrap;text-overflow:ellipsis;min-width:0;overflow:hidden}.file-diff-file.status-added .file-status{color:var(--success)}.file-diff-file.status-deleted .file-status{color:var(--danger)}.file-diff-file.status-modified .file-status{color:var(--warning)}.file-diff-file.status-renamed .file-status{color:var(--accent)}.file-diff-body{flex-direction:column;flex:1;min-width:0;display:flex}.file-diff-panes{flex:1;min-height:0;display:flex;position:relative}.file-diff-overview{z-index:0;border-left:1px solid var(--border);background:color-mix(in srgb, var(--text-dim) 6%, transparent);width:28px;position:absolute;top:0;bottom:0;right:0}.file-diff-pane{z-index:1;position:relative}.file-diff-pane::-webkit-scrollbar{width:10px;height:10px}.file-diff-pane-right::-webkit-scrollbar{width:28px}.file-diff-pane::-webkit-scrollbar-track{background:0 0}.file-diff-pane::-webkit-scrollbar-corner{background:0 0}.file-diff-pane::-webkit-scrollbar-thumb{background:color-mix(in srgb, var(--text-dim) 25%, transparent);border-radius:5px}.file-diff-pane::-webkit-scrollbar-thumb:hover{background:color-mix(in srgb, var(--text-dim) 45%, transparent)}.overview-bar{width:45%;min-height:2px;position:absolute}.overview-delete{background:var(--danger);left:2px}.overview-insert{background:var(--success);right:2px}.file-diff-pane{flex:1 1 0;min-width:0;overflow:auto}.file-diff-pane-left{border-right:1px solid var(--border)}.file-diff-table{border-collapse:collapse;width:max-content;min-width:100%;font-family:ui-monospace,Menlo,Consolas,monospace;font-size:12px;line-height:18px}.file-diff-table tr{height:18px}.file-diff-table td{vertical-align:top;padding:0 6px}.diff-num{text-align:right;color:var(--text-dim);-webkit-user-select:none;user-select:none}.diff-code{white-space:pre;color:var(--text)}.diff-delete{background:color-mix(in srgb, var(--danger) 18%, transparent)}.diff-insert{background:color-mix(in srgb, var(--success) 18%, transparent)}.diff-empty{background:color-mix(in srgb, var(--text-dim) 8%, transparent)}.file-diff-table tr.diff-hunk{height:26px}.diff-hunk td{vertical-align:middle;color:var(--accent);background:color-mix(in srgb, var(--accent) 10%, transparent);border-top:1px solid var(--border);border-bottom:1px solid var(--border);padding:0 8px}.terminal-container{border-top:1px solid var(--border);background:var(--panel-bg);flex-direction:column;flex:none;height:260px;display:flex;position:relative}.terminal-container.collapsed{height:auto}.terminal-container.collapsed .terminal-body{display:none}.terminal-resize-handle{cursor:row-resize;z-index:5;height:6px;position:absolute;top:-3px;left:0;right:0}.terminal-resize-handle:hover,.terminal-resize-handle:active{background:var(--accent);opacity:.5}.terminal-header{height:26px;color:var(--text-dim);cursor:pointer;-webkit-user-select:none;user-select:none;justify-content:space-between;align-items:center;padding:0 16px;font-size:12px;display:flex}.terminal-body{flex:1;min-height:0;position:relative}.terminal-instance{height:100%}.terminal-instance.hidden{display:none}.terminal-panel{height:100%;padding:4px 8px}.context-menu{z-index:111;background:var(--menu-bg);border:1px solid var(--border);min-width:180px;box-shadow:0 8px 24px var(--shadow);border-radius:6px;padding:4px;position:fixed}.context-menu-item{color:var(--text);cursor:pointer;border-radius:4px;padding:6px 10px;font-size:13px}.context-menu-item-with-mark{grid-template-columns:18px 1fr;align-items:center;gap:6px;display:grid}.context-menu-item-with-trailing-mark,.context-menu-item-with-arrow{justify-content:space-between;align-items:center;gap:16px;display:flex}.context-menu-submenu{position:relative}.context-menu-submenu-panel{display:none;position:absolute;top:-4px;left:calc(100% + 4px)}.context-menu-submenu:hover>.context-menu-submenu-panel{display:block}.context-menu-separator{background:var(--border);height:1px;margin:4px}.context-menu-item:hover{background:var(--hover-bg);color:var(--text-h)}.pipelines-panel{border:1px solid var(--border);background:color-mix(in srgb, var(--accent) 6%, transparent);border-radius:8px;margin:10px 10px 6px;padding:10px}.pipeline-card{border:1px solid var(--border);background:var(--panel-bg);border-radius:6px;flex-direction:column;gap:4px;padding:7px;display:flex}.pipeline-title{min-width:0;color:var(--text-h);align-items:center;gap:6px;font-size:12px;font-weight:700;text-decoration:none;display:flex}.pipeline-title:hover .pipeline-name{text-decoration:underline}.pipeline-icon{flex:none;justify-content:center;align-items:center;width:14px;height:14px;display:inline-flex}.pipeline-name{text-overflow:ellipsis;white-space:nowrap;flex:auto;min-width:0;overflow:hidden}.pipeline-status{flex:none;font-size:10px;font-weight:600}.pipeline-meta{color:var(--text-dim);font-size:10px;line-height:1.45}.stage-strip{flex-direction:column;gap:4px;display:flex}.stage-strip-row{flex-wrap:wrap;align-items:center;gap:3px 4px;font-size:10px;display:flex}.stage-arrow{color:var(--text-dim)}.stage-chip{border:1px solid var(--border);color:var(--text-h);border-radius:999px;align-items:center;gap:3px;padding:1px 6px;display:inline-flex}.stage-chip.pending{color:var(--text-dim)}.stage-mark{font-weight:800;line-height:1}.stage-duration{color:var(--text-dim)}.stage-steps{color:var(--text-dim);font-size:10px}.stage-steps summary{cursor:pointer}.stage-step{color:var(--text);align-items:center;gap:4px;padding-left:12px;display:flex}.welcome-dialog{flex-direction:column;width:560px;height:620px;max-height:85vh;position:relative}.welcome-close{text-align:center;width:22px;height:22px;color:var(--text-dim);cursor:pointer;border-radius:4px;line-height:22px;position:absolute;top:10px;right:12px}.welcome-close:hover{background:var(--hover-bg);color:var(--text-h)}.welcome-header{flex:none;padding:20px 24px 0}.welcome-header h2{color:var(--text-h);margin:0 0 4px;font-size:17px}.welcome-progress-bar{gap:6px;margin:14px 0 8px;display:flex}.welcome-progress-segment{background:var(--border);border-radius:999px;flex:1;height:3px}.welcome-progress-segment.filled{background:var(--accent)}.welcome-progress-caption{letter-spacing:.06em;text-transform:uppercase;color:var(--text-dim);font-size:11px}.welcome-body{flex:1;min-height:0;padding:16px 24px;overflow-y:auto}.welcome-body h3{color:var(--text-h);margin:0 0 6px;font-size:15px}.welcome-footer{border-top:1px solid var(--border);flex:none;justify-content:space-between;align-items:center;gap:12px;padding:14px 24px;display:flex}.welcome-footer-nav{gap:8px;display:flex}.welcome-skip{color:var(--text-dim);cursor:pointer;font-size:13px}.welcome-skip:hover{color:var(--text-h);text-decoration:underline}.theme-quickpicks{flex-direction:column;gap:14px;display:flex}.theme-quickpick-grid{grid-template-columns:repeat(3,1fr);gap:10px;display:grid}.theme-quickpick-card{background:var(--bg);border:1px solid var(--border);cursor:pointer;border-radius:6px;flex-direction:column;gap:8px;padding:8px;display:flex}.theme-quickpick-card:hover{border-color:color-mix(in srgb, var(--accent) 45%, var(--border))}.theme-quickpick-card.active{border-color:var(--accent);box-shadow:0 0 0 1px var(--accent)}.theme-quickpick-preview{border:1px solid;border-radius:4px;align-items:flex-end;gap:4px;height:44px;padding:6px;display:flex}.theme-quickpick-bar{border-radius:999px;flex:1;height:4px}.theme-quickpick-chip{opacity:.7;border-radius:999px;width:16px;height:4px}.theme-quickpick-label{color:var(--text-h);text-align:left;font-size:12px;font-weight:600}.theme-quickpick-editor-section{flex-direction:column;gap:8px;display:flex}.theme-quickpick-mini-grid{border:1px solid var(--border);border-radius:6px;grid-template-columns:repeat(4,1fr);align-content:start;gap:6px;max-height:180px;padding:6px;display:grid;overflow-y:auto}.theme-quickpick-mini-card{background:var(--bg);border:1px solid var(--border);cursor:pointer;border-radius:5px;flex-direction:column;gap:4px;min-width:0;padding:5px;display:flex}.theme-quickpick-mini-card:hover{border-color:color-mix(in srgb, var(--accent) 45%, var(--border))}.theme-quickpick-mini-card.active{border-color:var(--accent);box-shadow:0 0 0 1px var(--accent)}.theme-quickpick-mini-preview{border:1px solid;border-radius:3px;align-items:flex-end;height:20px;padding:3px;display:flex}.theme-quickpick-mini-bar{border-radius:999px;flex:1;height:3px}.theme-quickpick-mini-label{color:var(--text-dim);text-align:left;white-space:nowrap;text-overflow:ellipsis;font-size:10px;overflow:hidden}.theme-quickpick-mini-card.active .theme-quickpick-mini-label{color:var(--text-h)}.agent-quickpicks{flex-direction:column;gap:10px;display:flex}.agent-quickpick-grid{grid-template-columns:repeat(2,1fr);gap:10px;display:grid}.agent-quickpick-card{background:var(--bg);border:1px solid var(--border);border-radius:6px;flex-direction:column;gap:10px;padding:12px;display:flex}.agent-quickpick-heading{align-items:center;gap:8px;display:flex}.agent-quickpick-logo{border:1px solid var(--border);background:#fff;border-radius:6px;flex:none;justify-content:center;align-items:center;width:24px;height:24px;display:flex}.agent-quickpick-logo img{object-fit:contain;width:16px;height:16px;display:block}.agent-quickpick-actions{align-items:center;gap:8px;display:flex}.agent-quickpick-tracking{color:var(--success);font-size:12px;font-weight:600}.agent-quickpick-manual{border-top:1px solid var(--border);padding-top:10px}.theme-loading{color:var(--muted,inherit);align-items:center;gap:8px;padding:8px 4px;font-size:12px;display:flex}.theme-spinner{border:2px solid;border-right-color:#0000;border-radius:50%;width:12px;height:12px;animation:.7s linear infinite theme-spin}@keyframes theme-spin{to{transform:rotate(360deg)}}.xterm{cursor:text;-webkit-user-select:none;user-select:none;position:relative}.xterm.focus,.xterm:focus{outline:none}.xterm .xterm-helpers{z-index:5;position:absolute;top:0}.xterm .xterm-helper-textarea{opacity:0;z-index:-5;white-space:nowrap;resize:none;border:0;width:0;height:0;margin:0;padding:0;position:absolute;top:0;left:-9999em;overflow:hidden}.xterm .composition-view{color:#fff;white-space:nowrap;z-index:1;background:#000;display:none;position:absolute}.xterm .composition-view.active{display:block}.xterm .xterm-viewport{cursor:default;background-color:#000;position:absolute;inset:0;overflow-y:scroll}.xterm .xterm-screen{position:relative}.xterm .xterm-screen canvas{position:absolute;top:0;left:0}.xterm-char-measure-element{visibility:hidden;line-height:normal;display:inline-block;position:absolute;top:0;left:-9999em}.xterm.enable-mouse-events{cursor:default}.xterm.xterm-cursor-pointer,.xterm .xterm-cursor-pointer{cursor:pointer}.xterm.column-select.focus{cursor:crosshair}.xterm .xterm-accessibility:not(.debug),.xterm .xterm-message{z-index:10;color:#0000;pointer-events:none;position:absolute;inset:0}.xterm .xterm-accessibility-tree:not(.debug) ::selection{color:#0000}.xterm .xterm-accessibility-tree{-webkit-user-select:text;user-select:text;white-space:pre;font-family:monospace}.xterm .xterm-accessibility-tree>div{transform-origin:0;width:fit-content}.xterm .live-region{width:1px;height:1px;position:absolute;left:-9999px;overflow:hidden}.xterm-dim{opacity:1!important}.xterm-underline-1{text-decoration:underline}.xterm-underline-2{-webkit-text-decoration:underline double;text-decoration:underline double}.xterm-underline-3{-webkit-text-decoration:underline wavy;text-decoration:underline wavy}.xterm-underline-4{-webkit-text-decoration:underline dotted;text-decoration:underline dotted}.xterm-underline-5{-webkit-text-decoration:underline dashed;text-decoration:underline dashed}.xterm-overline{text-decoration:overline}.xterm-overline.xterm-underline-1{text-decoration:underline overline}.xterm-overline.xterm-underline-2{-webkit-text-decoration:overline double underline;text-decoration:overline double underline}.xterm-overline.xterm-underline-3{-webkit-text-decoration:overline wavy underline;text-decoration:overline wavy underline}.xterm-overline.xterm-underline-4{-webkit-text-decoration:overline dotted underline;text-decoration:overline dotted underline}.xterm-overline.xterm-underline-5{-webkit-text-decoration:overline dashed underline;text-decoration:overline dashed underline}.xterm-strikethrough{text-decoration:line-through}.xterm-screen .xterm-decoration-container .xterm-decoration{z-index:6;position:absolute}.xterm-screen .xterm-decoration-container .xterm-decoration.xterm-decoration-top-layer{z-index:7}.xterm-decoration-overview-ruler{z-index:8;pointer-events:none;position:absolute;top:0;right:0}.xterm-decoration-top{z-index:2;position:relative}.xterm .xterm-scrollable-element>.scrollbar{cursor:default}.xterm .xterm-scrollable-element>.scrollbar>.scra{cursor:pointer;font-size:11px!important}.xterm .xterm-scrollable-element>.visible{opacity:1;z-index:11;background:0 0;transition:opacity .1s linear}.xterm .xterm-scrollable-element>.invisible{opacity:0;pointer-events:none}.xterm .xterm-scrollable-element>.invisible.fade{transition:opacity .8s linear}.xterm .xterm-scrollable-element>.shadow{display:none;position:absolute}.xterm .xterm-scrollable-element>.shadow.top{width:100%;height:3px;box-shadow:var(--vscode-scrollbar-shadow,#000) 0 6px 6px -6px inset;display:block;top:0;left:3px}.xterm .xterm-scrollable-element>.shadow.left{width:3px;height:100%;box-shadow:var(--vscode-scrollbar-shadow,#000) 6px 0 6px -6px inset;display:block;top:3px;left:0}.xterm .xterm-scrollable-element>.shadow.top-left-corner{width:3px;height:3px;display:block;top:0;left:0}.xterm .xterm-scrollable-element>.shadow.top.left{box-shadow:var(--vscode-scrollbar-shadow,#000) 6px 0 6px -6px inset}