git-juggler 0.1.9__tar.gz → 0.2.1__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 (171) hide show
  1. git_juggler-0.2.1/PKG-INFO +102 -0
  2. {git_juggler-0.1.9 → git_juggler-0.2.1}/pyproject.toml +1 -0
  3. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/backend/git_juggler/app.py +33 -5
  4. git_juggler-0.2.1/src/backend/git_juggler/ci.py +135 -0
  5. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/backend/git_juggler/file_diff.py +11 -4
  6. git_juggler-0.2.1/src/backend/git_juggler/frontend_dist/assets/index-C-FENuHS.css +1 -0
  7. git_juggler-0.2.1/src/backend/git_juggler/frontend_dist/assets/index-CnmsXu_6.js +21 -0
  8. git_juggler-0.2.1/src/backend/git_juggler/frontend_dist/git-juggler.png +0 -0
  9. git_juggler-0.2.1/src/backend/git_juggler/frontend_dist/index.html +14 -0
  10. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/backend/git_juggler/github_actions.py +82 -4
  11. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/backend/git_juggler/jenkins.py +55 -1
  12. git_juggler-0.2.1/src/backend/git_juggler/pick_folder.py +48 -0
  13. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/backend/git_juggler/schemas.py +8 -0
  14. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/backend/git_juggler/terminal.py +60 -5
  15. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/backend/tests/test_ci.py +7 -7
  16. git_juggler-0.2.1/src/backend/tests/test_ci_poll.py +170 -0
  17. git_juggler-0.2.1/src/backend/tests/test_ci_poll_route.py +52 -0
  18. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/backend/tests/test_file_diff.py +19 -0
  19. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/backend/tests/test_github_actions.py +2 -2
  20. git_juggler-0.2.1/src/backend/tests/test_pick_folder.py +34 -0
  21. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/index.html +2 -2
  22. git_juggler-0.2.1/src/frontend/public/git-juggler.png +0 -0
  23. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/App.css +291 -22
  24. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/App.tsx +13 -12
  25. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/api/client.ts +29 -3
  26. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/api/types.ts +7 -0
  27. git_juggler-0.2.1/src/frontend/src/components/Badges/CiRunBadge.tsx +92 -0
  28. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/components/Branches/DeleteBranchModal.tsx +5 -1
  29. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/components/CommandPalette/CommandPalette.tsx +3 -1
  30. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/components/ContextMenu/CommitContextMenu.tsx +43 -18
  31. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/components/ContextMenu/CreateTagModal.tsx +4 -9
  32. git_juggler-0.2.1/src/frontend/src/components/Diff/FileDiffModal.tsx +173 -0
  33. git_juggler-0.2.1/src/frontend/src/components/Menu/CiPollField.tsx +18 -0
  34. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/components/Menu/DirectoryBrowserModal.tsx +4 -1
  35. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/components/Menu/GitHubSettings.tsx +4 -1
  36. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/components/Menu/JenkinsSettings.tsx +4 -1
  37. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/components/Menu/MainMenu.tsx +14 -1
  38. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/components/Menu/ThemePicker.tsx +4 -3
  39. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/components/Menu/ThemeQuickPicks.tsx +30 -27
  40. git_juggler-0.2.1/src/frontend/src/components/Pipelines/StageGraph.tsx +159 -0
  41. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/components/Search/SearchBox.tsx +22 -1
  42. git_juggler-0.2.1/src/frontend/src/components/Sidebar/NewGroupModal.tsx +54 -0
  43. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/components/Sidebar/RepoList.tsx +4 -6
  44. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/components/Tags/DeleteTagModal.tsx +4 -1
  45. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/components/Terminal/TerminalPanel.tsx +47 -30
  46. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/components/Welcome/WelcomeWizard.tsx +4 -1
  47. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/index.css +21 -0
  48. git_juggler-0.2.1/src/frontend/src/lib/bundledThemes.ts +63 -0
  49. git_juggler-0.2.1/src/frontend/src/lib/ciPoll.ts +52 -0
  50. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/lib/sideBySide.ts +29 -0
  51. git_juggler-0.2.1/src/frontend/src/lib/stageGraphLayout.ts +117 -0
  52. git_juggler-0.2.1/src/frontend/src/lib/stageWindow.ts +27 -0
  53. git_juggler-0.2.1/src/frontend/src/lib/swallowNextClick.ts +21 -0
  54. git_juggler-0.2.1/src/frontend/src/state/overlayStack.ts +39 -0
  55. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/state/store.ts +175 -15
  56. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/state/themes.ts +8 -7
  57. git_juggler-0.2.1/src/frontend/tests/ciPoll.test.ts +55 -0
  58. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/tests/sideBySide.test.ts +26 -1
  59. git_juggler-0.2.1/src/frontend/tests/stageGraphLayout.test.ts +95 -0
  60. git_juggler-0.2.1/src/frontend/tests/stageWindow.test.ts +22 -0
  61. git_juggler-0.1.9/PKG-INFO +0 -11
  62. git_juggler-0.1.9/src/backend/git_juggler/ci.py +0 -66
  63. git_juggler-0.1.9/src/backend/git_juggler/frontend_dist/assets/index-ByhhD79y.css +0 -1
  64. git_juggler-0.1.9/src/backend/git_juggler/frontend_dist/assets/index-C2mljjaE.js +0 -21
  65. git_juggler-0.1.9/src/backend/git_juggler/frontend_dist/index.html +0 -14
  66. git_juggler-0.1.9/src/frontend/src/components/Badges/CiRunBadge.tsx +0 -64
  67. git_juggler-0.1.9/src/frontend/src/components/Diff/FileDiffModal.tsx +0 -105
  68. {git_juggler-0.1.9 → git_juggler-0.2.1}/.github/workflows/build-package.yml +0 -0
  69. {git_juggler-0.1.9 → git_juggler-0.2.1}/.gitignore +0 -0
  70. {git_juggler-0.1.9 → git_juggler-0.2.1}/CLAUDE.md +0 -0
  71. {git_juggler-0.1.9 → git_juggler-0.2.1}/LICENSE +0 -0
  72. {git_juggler-0.1.9 → git_juggler-0.2.1}/README.md +0 -0
  73. {git_juggler-0.1.9 → git_juggler-0.2.1}/notes.txt +0 -0
  74. {git_juggler-0.1.9 → git_juggler-0.2.1}/plans/github-actions-settings-and-build-status.md +0 -0
  75. {git_juggler-0.1.9 → git_juggler-0.2.1}/plans/jenkins-settings-and-build-status.md +0 -0
  76. {git_juggler-0.1.9 → git_juggler-0.2.1}/scripts/build-package.sh +0 -0
  77. {git_juggler-0.1.9 → git_juggler-0.2.1}/scripts/dev.sh +0 -0
  78. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/backend/git_juggler/__init__.py +0 -0
  79. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/backend/git_juggler/agent_hook_events.py +0 -0
  80. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/backend/git_juggler/agent_hooks.py +0 -0
  81. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/backend/git_juggler/agent_tracking/__init__.py +0 -0
  82. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/backend/git_juggler/agent_tracking/activity_models.py +0 -0
  83. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/backend/git_juggler/agent_tracking/claude_sessions.py +0 -0
  84. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/backend/git_juggler/agent_tracking/claude_transcripts.py +0 -0
  85. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/backend/git_juggler/agent_tracking/git_resolver.py +0 -0
  86. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/backend/git_juggler/agent_tracking/opencode_sessions.py +0 -0
  87. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/backend/git_juggler/agent_tracking/paths.py +0 -0
  88. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/backend/git_juggler/agent_tracking/text.py +0 -0
  89. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/backend/git_juggler/browse.py +0 -0
  90. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/backend/git_juggler/commit_detail.py +0 -0
  91. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/backend/git_juggler/config.py +0 -0
  92. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/backend/git_juggler/dev_app.py +0 -0
  93. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/backend/git_juggler/frontend_dist/agent-logos/claude.png +0 -0
  94. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/backend/git_juggler/frontend_dist/agent-logos/opencode.webp +0 -0
  95. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/backend/git_juggler/frontend_dist/favicon.svg +0 -0
  96. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/backend/git_juggler/frontend_dist/icons.svg +0 -0
  97. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/backend/git_juggler/git_data.py +0 -0
  98. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/backend/git_juggler/git_utils.py +0 -0
  99. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/backend/git_juggler/main.py +0 -0
  100. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/backend/git_juggler/repos.py +0 -0
  101. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/backend/git_juggler/themes.py +0 -0
  102. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/backend/tests/test_agent_hooks.py +0 -0
  103. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/backend/tests/test_agent_tracking.py +0 -0
  104. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/backend/tests/test_claude_sessions_data.py +0 -0
  105. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/backend/tests/test_git_data.py +0 -0
  106. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/backend/tests/test_jenkins.py +0 -0
  107. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/backend/tests/test_opencode_sessions.py +0 -0
  108. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/backend/tests/test_preferences.py +0 -0
  109. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/backend/tests/test_repos.py +0 -0
  110. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/backend/tests/test_themes.py +0 -0
  111. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/.gitignore +0 -0
  112. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/README.md +0 -0
  113. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/package-lock.json +0 -0
  114. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/package.json +0 -0
  115. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/public/agent-logos/claude.png +0 -0
  116. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/public/agent-logos/opencode.webp +0 -0
  117. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/public/favicon.svg +0 -0
  118. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/public/icons.svg +0 -0
  119. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/components/Agents/AgentActivityPanel.tsx +0 -0
  120. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/components/Agents/AgentHoverCard.tsx +0 -0
  121. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/components/Agents/agentFormat.ts +0 -0
  122. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/components/Badges/BranchBadge.tsx +0 -0
  123. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/components/Badges/StashBadge.tsx +0 -0
  124. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/components/Badges/TagBadge.tsx +0 -0
  125. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/components/Badges/ciStatus.tsx +0 -0
  126. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/components/Badges/icons.tsx +0 -0
  127. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/components/Commits/CommitDetail.tsx +0 -0
  128. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/components/Commits/CommitList.tsx +0 -0
  129. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/components/Commits/CommitRow.tsx +0 -0
  130. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/components/Commits/UncommittedRow.tsx +0 -0
  131. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/components/ContextMenu/BranchContextMenu.tsx +0 -0
  132. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/components/ContextMenu/RepoContextMenu.tsx +0 -0
  133. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/components/ContextMenu/TagContextMenu.tsx +0 -0
  134. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/components/Graph/GraphPanel.tsx +0 -0
  135. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/components/Graph/branchColor.ts +0 -0
  136. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/components/Graph/computeColumns.ts +0 -0
  137. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/components/Graph/laneWidth.ts +0 -0
  138. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/components/Menu/AgentQuickPicks.tsx +0 -0
  139. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/components/Menu/AgentsSettings.tsx +0 -0
  140. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/components/Menu/RepoPathsSettings.tsx +0 -0
  141. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/components/Pipelines/PipelinesPanel.tsx +0 -0
  142. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/components/Pipelines/StageStrip.tsx +0 -0
  143. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/components/Search/MultiSelect.tsx +0 -0
  144. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/components/Search/TriSwitch.tsx +0 -0
  145. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/components/Sidebar/Sidebar.tsx +0 -0
  146. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/components/Tabs/TabsBar.tsx +0 -0
  147. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/components/inputs/NumberField.tsx +0 -0
  148. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/components/inputs/TextField.tsx +0 -0
  149. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/components/inputs/ToggleField.tsx +0 -0
  150. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/components/inputs/styles.ts +0 -0
  151. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/index.tsx +0 -0
  152. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/lib/appTheme.ts +0 -0
  153. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/lib/branchVisibility.ts +0 -0
  154. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/lib/color.ts +0 -0
  155. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/lib/dismissOnOutsideClick.ts +0 -0
  156. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/lib/flip.ts +0 -0
  157. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/lib/formatDate.ts +0 -0
  158. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/lib/preferenceSender.ts +0 -0
  159. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/lib/scrollToCommit.ts +0 -0
  160. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/lib/stripAnsi.ts +0 -0
  161. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/lib/vscodeTheme.ts +0 -0
  162. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/state/preferenceSync.ts +0 -0
  163. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/state/preferences.ts +0 -0
  164. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/tests/agentFormat.test.ts +0 -0
  165. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/tests/computeColumns.test.ts +0 -0
  166. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/tests/preferenceSender.test.ts +0 -0
  167. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/tests/vscodeTheme.test.ts +0 -0
  168. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/tsconfig.app.json +0 -0
  169. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/tsconfig.json +0 -0
  170. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/tsconfig.node.json +0 -0
  171. {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/vite.config.ts +0 -0
@@ -0,0 +1,102 @@
1
+ Metadata-Version: 2.5
2
+ Name: git-juggler
3
+ Version: 0.2.1
4
+ Summary: A local Git dashboard for people juggling agents and too many repos.
5
+ License-Expression: MIT
6
+ License-File: LICENSE
7
+ Requires-Python: >=3.10
8
+ Requires-Dist: fastapi>=0.110
9
+ Requires-Dist: gitpython>=3.1
10
+ Requires-Dist: pywinpty>=2.0; sys_platform == 'win32'
11
+ Requires-Dist: uvicorn[standard]>=0.29
12
+ Description-Content-Type: text/markdown
13
+
14
+ # git-juggler
15
+
16
+ A local Git dashboard for people juggling agents and too many repos.
17
+
18
+ git-juggler is a local dashboard for agent-heavy workflows and multi-repo projects. Watch agents work live, follow every repo’s git graph, and see what changed across your workspace without opening a dozen editors.
19
+
20
+ ## How to install
21
+
22
+ git-juggler is published on PyPI. The recommended way to install it is with
23
+ [pipx](https://pipx.pypa.io), which puts the `git-juggler` command on your
24
+ PATH in its own isolated environment, without touching your system Python:
25
+
26
+ ```
27
+ pipx install git-juggler
28
+ git-juggler <path-to-scan> # http://127.0.0.1:8000
29
+ ```
30
+
31
+ If you don't have pipx yet:
32
+
33
+ **macOS**
34
+ ```
35
+ brew install pipx
36
+ pipx ensurepath
37
+ ```
38
+
39
+ **Linux**
40
+ ```
41
+ sudo apt install pipx # Debian/Ubuntu
42
+ sudo dnf install pipx # Fedora
43
+ pipx ensurepath
44
+ ```
45
+ Many recent distros (like macOS with Homebrew) block a plain global
46
+ `pip install` outside a virtual environment (PEP 668); pipx is the
47
+ supported way around that.
48
+
49
+ **Windows**
50
+ ```
51
+ py -m pip install --user pipx
52
+ py -m pipx ensurepath
53
+ ```
54
+
55
+ Restart your terminal after `ensurepath` so the updated PATH takes effect.
56
+ To upgrade later: `pipx upgrade git-juggler`.
57
+
58
+ ## Dev mode
59
+
60
+ ```
61
+ python3 -m venv .venv
62
+ .venv/bin/pip install -e .
63
+ .venv/bin/git-juggler <path-to-scan> --reload # http://127.0.0.1:8000
64
+ ```
65
+
66
+ ```
67
+ cd src/frontend
68
+ npm install
69
+ npm run dev # http://localhost:5173 (proxies /api and /ws)
70
+ ```
71
+
72
+ Open http://localhost:5173.
73
+
74
+ ## Production (single port)
75
+
76
+ ```
77
+ cd src/frontend && npm install && npm run build
78
+ cd ../.. && .venv/bin/git-juggler <path-to-scan> # http://127.0.0.1:8000
79
+ ```
80
+
81
+ ## Build a Python package
82
+
83
+ The Python wheel includes the built frontend assets. The generated assets are
84
+ not committed; `scripts/build-package.sh` builds them and copies them into the
85
+ backend package before creating the distributions.
86
+
87
+ ```
88
+ python3 -m venv .venv
89
+ .venv/bin/pip install build
90
+ ./scripts/build-package.sh
91
+ ```
92
+
93
+ The wheel and source distribution are written to `dist/`.
94
+
95
+ Install the wheel locally with:
96
+
97
+ ```
98
+ pipx install dist/git_juggler-0.1.0-py3-none-any.whl --force
99
+ git-juggler <path-to-scan> # http://127.0.0.1:8000
100
+ ```
101
+
102
+ GitHub Actions runs the same build and uploads the distributions as artifacts.
@@ -2,6 +2,7 @@
2
2
  name = "git-juggler"
3
3
  dynamic = ["version"]
4
4
  description = "A local Git dashboard for people juggling agents and too many repos."
5
+ readme = "README.md"
5
6
  requires-python = ">=3.10"
6
7
  license = "MIT"
7
8
  dependencies = [
@@ -2,6 +2,7 @@ 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
@@ -13,7 +14,8 @@ 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,6 +33,7 @@ from .schemas import (
31
33
  AgentHooksResponse,
32
34
  AgentRepositoryScanResponse,
33
35
  BrowseDirectoryResponse,
36
+ PickFolderResponse,
34
37
  CiRunInfo,
35
38
  CiStage,
36
39
  CommitDetail,
@@ -81,6 +84,13 @@ def create_app(root_path: Path, frontend_dist: Path | None = None) -> FastAPI:
81
84
  def api_browse(path: str | None = None) -> BrowseDirectoryResponse:
82
85
  return browse_directory(path)
83
86
 
87
+ @app.post("/api/pick-folder", response_model=PickFolderResponse)
88
+ def api_pick_folder() -> PickFolderResponse:
89
+ try:
90
+ return PickFolderResponse(path=pick_folder())
91
+ except NativePickerUnavailable as e:
92
+ raise HTTPException(status_code=501, detail=str(e))
93
+
84
94
  terminal_shell = "cmd" if sys.platform == "win32" else "posix"
85
95
 
86
96
  def _current_config() -> ConfigResponse:
@@ -201,21 +211,25 @@ def create_app(root_path: Path, frontend_dist: Path | None = None) -> FastAPI:
201
211
 
202
212
  @app.get("/api/repos/{repo_id}/commits/{sha}/diff", response_model=FileDiff)
203
213
  def api_commit_file_diff(
204
- repo_id: str, sha: str, path: str, old_path: str | None = None
214
+ repo_id: str,
215
+ sha: str,
216
+ path: str,
217
+ old_path: str | None = None,
218
+ full: bool = False,
205
219
  ) -> FileDiff:
206
220
  repo_path = _resolve_repo_path(repo_id)
207
221
  try:
208
- return get_commit_file_diff(repo_path, sha, path, old_path)
222
+ return get_commit_file_diff(repo_path, sha, path, old_path, full)
209
223
  except Exception as exc: # noqa: BLE001 - surfaced as a 404 either way
210
224
  raise HTTPException(status_code=404, detail="diff not found") from exc
211
225
 
212
226
  @app.get("/api/repos/{repo_id}/diff", response_model=FileDiff)
213
227
  def api_working_file_diff(
214
- repo_id: str, path: str, old_path: str | None = None
228
+ repo_id: str, path: str, old_path: str | None = None, full: bool = False
215
229
  ) -> FileDiff:
216
230
  repo_path = _resolve_repo_path(repo_id)
217
231
  try:
218
- return get_working_file_diff(repo_path, path, old_path)
232
+ return get_working_file_diff(repo_path, path, old_path, full)
219
233
  except Exception as exc: # noqa: BLE001 - surfaced as a 404 either way
220
234
  raise HTTPException(status_code=404, detail="diff not found") from exc
221
235
 
@@ -229,6 +243,20 @@ def create_app(root_path: Path, frontend_dist: Path | None = None) -> FastAPI:
229
243
  config.load_jenkins_config(),
230
244
  )
231
245
 
246
+ # Live poll of a repo's pipelines. Without `run`, every active run of the
247
+ # repo (`head_sha` narrows it where the provider can filter): used right
248
+ # after a commit action, when the run may not be known yet. With one or more
249
+ # `run=<provider>:<run_id>` (CiRunInfo.provider / run_id), only those runs,
250
+ # including their final status once finished.
251
+ @app.get("/api/repos/{repo_id}/ci/poll", response_model=list[CiRunInfo])
252
+ def api_ci_poll(
253
+ repo_id: str,
254
+ run: Annotated[list[str] | None, Query()] = None,
255
+ head_sha: str | None = None,
256
+ ) -> list[CiRunInfo]:
257
+ path = _resolve_repo_path(repo_id)
258
+ return poll_ci_runs(path, run, head_sha, config.load_github_config(), config.load_jenkins_config())
259
+
232
260
  # Stages (GitHub jobs / Jenkins pipeline stages) of one run. `run_id` is the
233
261
  # value CiRunInfo.run_id carried; 404 when the provider has no stage data.
234
262
  @app.get("/api/repos/{repo_id}/ci/stages", response_model=list[CiStage])
@@ -0,0 +1,135 @@
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 get_active_pipelines(
124
+ repos: list[RepoSummary],
125
+ github_config: dict | None,
126
+ jenkins_config: dict | None,
127
+ ) -> list[ActivePipeline]:
128
+ """Queued/running pipelines across every known repo. Repos are queried
129
+ concurrently since each one is a network round trip."""
130
+ if not repos:
131
+ return []
132
+ with ThreadPoolExecutor(max_workers=min(8, len(repos))) as pool:
133
+ per_repo = pool.map(lambda repo: _active_runs_for_repo(repo, github_config, jenkins_config), repos)
134
+ pipelines = [item for group in per_repo for item in group]
135
+ return sorted(pipelines, key=lambda item: item.run.created_at or "", reverse=True)
@@ -15,6 +15,9 @@ MAX_PATCH_BYTES = 1_000_000
15
15
  # for repos that have no HEAD yet.
16
16
  _EMPTY_TREE = "4b825dc642cb6eb9a060e54bf8d69288fbee4904"
17
17
 
18
+ # Context lines big enough to make git emit the whole file as a single hunk.
19
+ _FULL_CONTEXT = "-U1000000"
20
+
18
21
  _BINARY_RE = re.compile(r"^(Binary files .* differ|GIT binary patch)$", re.MULTILINE)
19
22
 
20
23
 
@@ -37,19 +40,23 @@ def _run_diff(repo: Repo, *args: str) -> bytes:
37
40
  return out if isinstance(out, bytes) else out.encode()
38
41
 
39
42
 
43
+ def _context(full: bool) -> list[str]:
44
+ return [_FULL_CONTEXT] if full else []
45
+
46
+
40
47
  def _paths(path: str, old_path: str | None) -> list[str]:
41
48
  return [old_path, path] if old_path and old_path != path else [path]
42
49
 
43
50
 
44
- def get_commit_file_diff(repo_path: Path, sha: str, path: str, old_path: str | None = None) -> FileDiff:
51
+ def get_commit_file_diff(repo_path: Path, sha: str, path: str, old_path: str | None = None, full: bool = False) -> FileDiff:
45
52
  repo = Repo(repo_path)
46
53
  commit = repo.commit(sha)
47
54
  base = commit.parents[0].hexsha if commit.parents else _EMPTY_TREE
48
- raw = _run_diff(repo, "-M", base, commit.hexsha, "--", *_paths(path, old_path))
55
+ raw = _run_diff(repo, "-M", *_context(full), base, commit.hexsha, "--", *_paths(path, old_path))
49
56
  return _build_result(raw)
50
57
 
51
58
 
52
- def get_working_file_diff(repo_path: Path, path: str, old_path: str | None = None) -> FileDiff:
59
+ def get_working_file_diff(repo_path: Path, path: str, old_path: str | None = None, full: bool = False) -> FileDiff:
53
60
  repo = Repo(repo_path)
54
61
  root = Path(repo.working_tree_dir or repo_path).resolve()
55
62
  target = (root / path).resolve()
@@ -64,5 +71,5 @@ def get_working_file_diff(repo_path: Path, path: str, old_path: str | None = Non
64
71
  return _build_result(raw)
65
72
 
66
73
  base = "HEAD" if repo.head.is_valid() else _EMPTY_TREE
67
- raw = _run_diff(repo, "-M", base, "--", *_paths(path, old_path))
74
+ raw = _run_diff(repo, "-M", *_context(full), base, "--", *_paths(path, old_path))
68
75
  return _build_result(raw)
@@ -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-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;background:var(--panel-bg);border:1px solid var(--border);box-shadow:0 8px 24px var(--shadow-soft);color:var(--text);pointer-events:none;border-radius:8px;flex-direction:column;gap:10px;padding:10px 12px;font-size:12px;display:flex;position:fixed}.agent-hover-session{border-left:2px solid #0000;border-radius:0 4px 4px 0;flex-direction:column;gap:3px;padding:4px 6px;display:flex}.agent-hover-session+.agent-hover-session{border-top:1px solid var(--border);margin-top:6px}.agent-hover-session.active{border-left-color:var(--accent);background:color-mix(in srgb, var(--accent) 8%, transparent)}.agent-hover-title,.agent-hover-worktree-title{color:var(--text-h);align-items:center;gap:6px;font-weight:700;display:flex}.agent-hover-title>span:first-child,.agent-hover-worktree-title>span:first-child{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.agent-hover-card .agent-state-pill{flex:none;margin-left:0}.agent-hover-meta{color:var(--text-dim);overflow-wrap:anywhere;font-size:11px;line-height:1.45}.agent-hover-mono{font-family:ui-monospace,Menlo,Consolas,monospace;font-size:10px}.agent-hover-prompt{color:var(--text);overflow-wrap:anywhere;white-space:pre-line;font-size:11px;font-style:italic;line-height:1.4}.agent-hover-worktree{border:1px solid var(--border);border-radius:6px;flex-direction:column;gap:2px;margin-top:4px;padding:6px 8px;display:flex}.agent-hover-worktree.active{border-color:color-mix(in srgb, var(--accent) 55%, var(--border));background:color-mix(in srgb, var(--accent) 8%, transparent)}.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:28px;color:var(--text);border-left:3px solid #0000;align-items:center;gap:8px;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:12px;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-bookmark{width:18px;height:20px;color:var(--text-dim);opacity:.5;cursor:pointer;background:0 0;border:none;flex:none;justify-content:center;align-items:center;margin-left:4px;padding:0;display:flex}.repo-bookmark:hover{opacity:1}.repo-bookmark.pinned{color:var(--accent);opacity:1}.repo-bookmark svg{fill:currentColor;width:12px;height:12px}.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;flex:auto;align-items:center;min-width:0;margin:-8px 0;padding:8px 0;display:flex;overflow:hidden}.repo-group-name:active{cursor:grabbing}.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.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}.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)}.uncommitted-detail-body{align-items:flex-start;gap:12px;display:flex}.uncommitted-action-panel{border:1px solid var(--border);background:var(--bg);border-radius:6px;flex-direction:column;flex:0 0 220px;gap:8px;padding:8px;display:flex}.uncommitted-message-label{color:var(--text-dim);font-size:11px}.uncommitted-message-input{resize:vertical;background:var(--panel-bg);border:1px solid var(--border);min-height:72px;color:var(--text);font:inherit;border-radius:4px;padding:6px 8px;font-size:12px}.uncommitted-message-input:focus{border-color:var(--accent);outline:none}.uncommitted-action-buttons{gap:8px;display:flex}.uncommitted-action-buttons button{background:var(--active-bg);border:1px solid var(--border);color:var(--text-h);cursor:pointer;font:inherit;border-radius:4px;flex:1;padding:5px 8px;font-size:12px}.uncommitted-action-buttons button:disabled{cursor:not-allowed;opacity:.45}.uncommitted-files{flex:auto;min-width:0}.uncommitted-file-checkbox{flex:none}.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-text{font-size:10px;font-weight:800}.ci-run-status{font-weight:800;line-height:1}.ci-run-tooltip{z-index:20;background:var(--menu-bg);border:1px solid var(--border);width:max-content;min-width:420px;box-shadow:0 8px 24px var(--shadow-soft);color:var(--text);white-space:normal;border-radius:6px;padding:8px 10px;display:none;position:absolute;top:calc(100% + 8px);left:0}.ci-run-badge:hover .ci-run-tooltip{flex-direction:column;gap:8px;display:flex}.ci-run-tooltip:before{content:"";height:9px;position:absolute;top:-9px;left:0;right:0}.ci-run-props{grid-template-columns:1fr 1fr;gap:2px 14px;display:grid}.ci-run-props-column{flex-direction:column;gap:2px;min-width:0;display:flex}.ci-run-item{color:var(--text-dim);flex-direction:column;gap:2px;font-size:11px;display:flex}.ci-run-item+.ci-run-item{border-top:1px solid var(--border);padding-top:8px}.ci-run-title{color:var(--text-h);font-weight:600}.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:140px;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: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}.stage-graph{margin:6px 0 2px;font-size:10px;transition:width .22s;display:block;overflow:visible}.stage-graph-item{transition:transform .22s}.stage-graph-link{transition:transform .22s,width .22s}.stage-graph-ring{fill:var(--menu-bg);stroke-width:2px}.stage-graph-ring.running{animation:1.4s ease-in-out infinite stage-graph-pulse}@keyframes stage-graph-pulse{50%{opacity:.55}}.stage-graph-spinner{fill:none;stroke-width:2px;stroke-linecap:round;stroke-dasharray:9 22;transform-box:fill-box;transform-origin:50%;animation:.9s linear infinite stage-graph-spin}@keyframes stage-graph-spin{to{transform:rotate(360deg)}}.stage-graph-mark{text-anchor:middle;dominant-baseline:central;font-size:10px;font-weight:800}.stage-graph-name,.stage-graph-time,.stage-graph-step-name{text-anchor:middle;fill:var(--text-dim)}.stage-graph-job.current .stage-graph-name{fill:var(--text-h);font-weight:700}.stage-graph-job.pending,.stage-graph-summary.pending{opacity:.55}.stage-graph-hit{fill:#0000}.stage-graph-step-dot{stroke-width:2px;transform-box:fill-box;transform-origin:50%;transition:transform .2s}.stage-graph-step-dot.pending{opacity:.55}.stage-graph-step-dot.running{animation:1.4s ease-in-out infinite stage-graph-pulse}.stage-graph-step-dot.hovered{transform:scale(1.5)}.stage-graph-step-name{fill:var(--text-h);opacity:0;transition:opacity .18s}.stage-graph-step-name.shown{opacity:1}.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}