git-history-ui 4.0.1 → 5.0.1

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 (106) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/CODE_OF_CONDUCT.md +53 -0
  3. package/CONTRIBUTING.md +132 -0
  4. package/LICENSE +21 -0
  5. package/README.md +75 -15
  6. package/SECURITY.md +51 -0
  7. package/build/frontend/chunk-6DL2ZTKG.js +1 -0
  8. package/build/frontend/chunk-ACWXPHRR.js +1 -0
  9. package/build/frontend/{chunk-CFCRPNUQ.js → chunk-F6HZ5OVS.js} +1 -1
  10. package/build/frontend/{chunk-UHWTLUOW.js → chunk-GHSRMAIR.js} +1 -1
  11. package/build/frontend/chunk-J5ZVP6TN.js +9 -0
  12. package/build/frontend/chunk-PRY6VAFG.js +1 -0
  13. package/build/frontend/chunk-PXNKNORB.js +1 -0
  14. package/build/frontend/chunk-S56XRIZ7.js +9 -0
  15. package/build/frontend/chunk-STUSEB5P.js +1 -0
  16. package/build/frontend/chunk-UM7ESEXJ.js +5 -0
  17. package/build/frontend/chunk-VGAHCEAA.js +1 -0
  18. package/build/frontend/chunk-XLGWMKXV.js +1 -0
  19. package/build/frontend/chunk-Y6GOUMAT.js +1 -0
  20. package/build/frontend/index.html +2 -2
  21. package/build/frontend/main-W5NQX3FE.js +1 -0
  22. package/build/frontend/styles-RQGRQK4Z.css +1 -0
  23. package/dist/backend/aggregations.d.ts +1 -0
  24. package/dist/backend/aggregations.d.ts.map +1 -0
  25. package/dist/backend/annotations.d.ts +10 -1
  26. package/dist/backend/annotations.d.ts.map +1 -0
  27. package/dist/backend/annotations.js +84 -9
  28. package/dist/backend/annotations.js.map +1 -1
  29. package/dist/backend/breakage.d.ts +70 -0
  30. package/dist/backend/breakage.d.ts.map +1 -0
  31. package/dist/backend/breakage.js +235 -0
  32. package/dist/backend/breakage.js.map +1 -0
  33. package/dist/backend/cache/sqliteIndex.d.ts +2 -1
  34. package/dist/backend/cache/sqliteIndex.d.ts.map +1 -0
  35. package/dist/backend/cache/sqliteIndex.js +36 -23
  36. package/dist/backend/cache/sqliteIndex.js.map +1 -1
  37. package/dist/backend/dev-server.d.ts +1 -0
  38. package/dist/backend/dev-server.d.ts.map +1 -0
  39. package/dist/backend/gitService.d.ts +15 -6
  40. package/dist/backend/gitService.d.ts.map +1 -0
  41. package/dist/backend/gitService.js +148 -45
  42. package/dist/backend/gitService.js.map +1 -1
  43. package/dist/backend/grouping/prGrouping.d.ts +1 -0
  44. package/dist/backend/grouping/prGrouping.d.ts.map +1 -0
  45. package/dist/backend/grouping/prGrouping.js +41 -19
  46. package/dist/backend/grouping/prGrouping.js.map +1 -1
  47. package/dist/backend/impact.d.ts +4 -1
  48. package/dist/backend/impact.d.ts.map +1 -0
  49. package/dist/backend/impact.js +30 -16
  50. package/dist/backend/impact.js.map +1 -1
  51. package/dist/backend/insights.d.ts +1 -0
  52. package/dist/backend/insights.d.ts.map +1 -0
  53. package/dist/backend/insights.js +8 -3
  54. package/dist/backend/insights.js.map +1 -1
  55. package/dist/backend/llm/anthropicProvider.d.ts +2 -0
  56. package/dist/backend/llm/anthropicProvider.d.ts.map +1 -0
  57. package/dist/backend/llm/anthropicProvider.js +10 -4
  58. package/dist/backend/llm/anthropicProvider.js.map +1 -1
  59. package/dist/backend/llm/heuristicProvider.d.ts +2 -0
  60. package/dist/backend/llm/heuristicProvider.d.ts.map +1 -0
  61. package/dist/backend/llm/heuristicProvider.js +54 -4
  62. package/dist/backend/llm/heuristicProvider.js.map +1 -1
  63. package/dist/backend/llm/index.d.ts +7 -0
  64. package/dist/backend/llm/index.d.ts.map +1 -0
  65. package/dist/backend/llm/index.js +15 -5
  66. package/dist/backend/llm/index.js.map +1 -1
  67. package/dist/backend/llm/openaiProvider.d.ts +3 -0
  68. package/dist/backend/llm/openaiProvider.d.ts.map +1 -0
  69. package/dist/backend/llm/openaiProvider.js +29 -4
  70. package/dist/backend/llm/openaiProvider.js.map +1 -1
  71. package/dist/backend/llm/types.d.ts +3 -1
  72. package/dist/backend/llm/types.d.ts.map +1 -0
  73. package/dist/backend/presets.d.ts +1 -0
  74. package/dist/backend/presets.d.ts.map +1 -0
  75. package/dist/backend/search/datePhrase.d.ts +1 -0
  76. package/dist/backend/search/datePhrase.d.ts.map +1 -0
  77. package/dist/backend/search/nlSearch.d.ts +1 -0
  78. package/dist/backend/search/nlSearch.d.ts.map +1 -0
  79. package/dist/backend/server.d.ts +1 -0
  80. package/dist/backend/server.d.ts.map +1 -0
  81. package/dist/backend/server.js +100 -26
  82. package/dist/backend/server.js.map +1 -1
  83. package/dist/backend/snapshot.d.ts +4 -1
  84. package/dist/backend/snapshot.d.ts.map +1 -0
  85. package/dist/backend/snapshot.js +14 -9
  86. package/dist/backend/snapshot.js.map +1 -1
  87. package/dist/cli.d.ts +1 -0
  88. package/dist/cli.d.ts.map +1 -0
  89. package/docs/API.md +70 -0
  90. package/docs/architecture.md +85 -0
  91. package/docs/configuration.md +54 -0
  92. package/docs/troubleshooting.md +85 -0
  93. package/package.json +44 -8
  94. package/build/frontend/chunk-4JCUQ6Y5.js +0 -1
  95. package/build/frontend/chunk-6VRZZB3T.js +0 -1
  96. package/build/frontend/chunk-EZUFC4CQ.js +0 -1
  97. package/build/frontend/chunk-GRG6HTLW.js +0 -9
  98. package/build/frontend/chunk-I4VBIHH2.js +0 -1
  99. package/build/frontend/chunk-OBPR2XVH.js +0 -5
  100. package/build/frontend/chunk-QNEGGJHP.js +0 -2
  101. package/build/frontend/chunk-UIWFTWLF.js +0 -1
  102. package/build/frontend/chunk-UNROVCUZ.js +0 -7
  103. package/build/frontend/chunk-UWM4ZZWH.js +0 -1
  104. package/build/frontend/chunk-VHEMN3MU.js +0 -1
  105. package/build/frontend/main-SQFBVJA6.js +0 -1
  106. package/build/frontend/styles-DUWPKHDX.css +0 -1
package/CHANGELOG.md CHANGED
@@ -4,6 +4,42 @@ All notable changes to this project are documented in this file.
4
4
  The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and
5
5
  this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## [5.0.1] - 2026-05-03
8
+
9
+ ### Added
10
+
11
+ - Breakage analysis endpoints and UI hooks for investigating file-level change
12
+ history.
13
+ - Shared frontend observable caching for commit, timeline, insight, and
14
+ annotation requests.
15
+
16
+ ### Fixed
17
+
18
+ - Timeline `Diff vs HEAD` file modifications now render in a properly sized
19
+ virtual-scroll diff viewer.
20
+ - Shared commit links resolve against the selected repository when the app is
21
+ launched with `--cwd`.
22
+ - Diff parsing, rename handling, and large-repo indexing paths were tightened
23
+ for repository-scale testing.
24
+
25
+ ## [5.0.0] - 2026-05-03
26
+
27
+ ### Added
28
+
29
+ - AI explanations now render as formatted markdown with an internal scrollbar
30
+ for long responses.
31
+ - OpenAI and Anthropic model selection can be overridden with
32
+ `GHUI_LLM_MODEL`, `OPENAI_MODEL`, or `ANTHROPIC_MODEL`.
33
+ - Project governance docs, PR templates, issue templates, and expanded test
34
+ coverage were added for a more release-ready package.
35
+
36
+ ### Changed
37
+
38
+ - OpenAI defaults to `gpt-4.1-nano` and Anthropic defaults to the available
39
+ Sonnet model `claude-sonnet-4-6`.
40
+ - AI summaries and commit explanations use larger token budgets and tighter
41
+ prompts to avoid truncated prose.
42
+
7
43
  ## [4.0.1] - 2026-05-02
8
44
 
9
45
  ### Fixed
@@ -0,0 +1,53 @@
1
+ # Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ We pledge to make participation in this project a harassment-free experience
6
+ for everyone, regardless of age, body size, visible or invisible disability,
7
+ ethnicity, sex characteristics, gender identity and expression, level of
8
+ experience, education, socio-economic status, nationality, personal appearance,
9
+ race, religion, or sexual identity and orientation.
10
+
11
+ ## Our Standards
12
+
13
+ Examples of behavior that contributes to a positive environment include:
14
+
15
+ - Demonstrating empathy and kindness toward other people
16
+ - Being respectful of differing opinions, viewpoints, and experiences
17
+ - Giving and gracefully accepting constructive feedback
18
+ - Accepting responsibility and apologizing to those affected by mistakes
19
+ - Focusing on what is best for the overall community
20
+
21
+ Examples of unacceptable behavior include:
22
+
23
+ - The use of sexualized language or imagery, and sexual attention or advances
24
+ - Trolling, insulting or derogatory comments, and personal or political attacks
25
+ - Public or private harassment
26
+ - Publishing others' private information without explicit permission
27
+ - Other conduct which could reasonably be considered inappropriate in a
28
+ professional setting
29
+
30
+ ## Enforcement Responsibilities
31
+
32
+ Project maintainers are responsible for clarifying and enforcing standards of
33
+ acceptable behavior and may remove, edit, or reject comments, commits, code,
34
+ wiki edits, issues, and other contributions that are not aligned with this Code
35
+ of Conduct.
36
+
37
+ ## Scope
38
+
39
+ This Code of Conduct applies within all project spaces, and also applies when an
40
+ individual is officially representing the project in public spaces.
41
+
42
+ ## Enforcement
43
+
44
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
45
+ reported to the project maintainer at
46
+ [ankit.sharma199803@gmail.com](mailto:ankit.sharma199803@gmail.com).
47
+
48
+ All complaints will be reviewed and investigated promptly and fairly.
49
+
50
+ ## Attribution
51
+
52
+ This Code of Conduct is adapted from the
53
+ [Contributor Covenant](https://www.contributor-covenant.org), version 2.1.
@@ -0,0 +1,132 @@
1
+ # Contributing to git-history-ui
2
+
3
+ Thanks for your interest in improving git-history-ui! This guide will help you
4
+ get started.
5
+
6
+ ## Development Setup
7
+
8
+ ```bash
9
+ # 1. Fork and clone
10
+ git clone https://github.com/<your-user>/git-history-ui.git
11
+ cd git-history-ui
12
+
13
+ # 2. Use the correct Node version
14
+ nvm use # reads .nvmrc → Node 20
15
+
16
+ # 3. Install dependencies
17
+ npm install
18
+ npm install --prefix frontend
19
+
20
+ # 4. Start the dev server (backend + frontend with hot reload)
21
+ npm run dev
22
+ ```
23
+
24
+ The backend runs on `http://localhost:3000` and the Angular dev server on
25
+ `http://localhost:4200` with API requests proxied to the backend.
26
+
27
+ ## Project Structure
28
+
29
+ ```
30
+ src/
31
+ cli.ts # CLI entry point (Commander)
32
+ backend/
33
+ server.ts # Express app + API routes
34
+ gitService.ts # All git operations
35
+ llm/ # LLM provider abstraction
36
+ search/ # Natural-language search
37
+ cache/ # SQLite indexer
38
+ ...
39
+ __tests__/ # Backend tests (Jest)
40
+
41
+ frontend/
42
+ src/app/
43
+ components/ # Angular standalone components
44
+ services/ # Angular services
45
+ models/ # Shared TypeScript interfaces
46
+ ```
47
+
48
+ ## Making Changes
49
+
50
+ 1. **Create a feature branch** from `main`:
51
+
52
+ ```bash
53
+ git checkout -b feat/my-feature
54
+ ```
55
+
56
+ 2. **Write code.** Follow the existing style — Prettier and ESLint will
57
+ enforce formatting on commit (via the pre-commit hook).
58
+
59
+ 3. **Write tests.** Backend coverage must stay above 90% (CI enforces this).
60
+ Add tests in `src/__tests__/` for any new backend logic.
61
+
62
+ 4. **Run checks locally** before pushing:
63
+
64
+ ```bash
65
+ npm run lint # ESLint
66
+ npm run typecheck # tsc --noEmit
67
+ npm test # Jest (backend)
68
+ npm test --prefix frontend -- --watch=false # Karma (frontend)
69
+ ```
70
+
71
+ 5. **Commit with a conventional message:**
72
+
73
+ ```
74
+ feat: add branch comparison view
75
+ fix: handle empty commit body in NL search
76
+ docs: update API endpoint reference
77
+ chore: bump express to 4.22
78
+ ```
79
+
80
+ The format is `type(optional-scope): description`. See
81
+ [Conventional Commits](https://www.conventionalcommits.org/) for the full
82
+ spec.
83
+
84
+ 6. **Push and open a PR** against `main`. Fill in the PR template and link
85
+ any related issues.
86
+
87
+ ## Commit Message Convention
88
+
89
+ We use [Conventional Commits](https://www.conventionalcommits.org/):
90
+
91
+ | Prefix | When to use |
92
+ | ---------- | ------------------------------------ |
93
+ | `feat:` | New user-facing feature |
94
+ | `fix:` | Bug fix |
95
+ | `docs:` | Documentation only |
96
+ | `style:` | Formatting, whitespace |
97
+ | `refactor:`| Code change that neither fixes nor adds |
98
+ | `perf:` | Performance improvement |
99
+ | `test:` | Adding or updating tests |
100
+ | `chore:` | Build, CI, dependency updates |
101
+
102
+ ## Code Style
103
+
104
+ - **TypeScript** throughout (backend + frontend).
105
+ - **Prettier** formats on save / on commit.
106
+ - **ESLint** with `@typescript-eslint` and `prettier` integration.
107
+ - Prefer `const` over `let`, avoid `any` when practical.
108
+ - Keep functions small and testable.
109
+
110
+ ## Tests
111
+
112
+ - **Backend:** Jest + ts-jest. Tests live in `src/__tests__/`.
113
+ - Use `supertest` for HTTP endpoint tests.
114
+ - Use `nock` for external HTTP mocking (GitHub API, LLM providers).
115
+ - Use `helpers/repo.ts` to create ephemeral git repos.
116
+ - **Frontend:** Karma + Jasmine. Specs live alongside components (`*.spec.ts`).
117
+
118
+ ## Reporting Bugs
119
+
120
+ Use the [bug report template](https://github.com/beingmartinbmc/git-history-ui/issues/new?template=bug_report.yml)
121
+ on GitHub. Include your Node version, OS, and the output of
122
+ `npx git-history-ui --version`.
123
+
124
+ ## Requesting Features
125
+
126
+ Use the [feature request template](https://github.com/beingmartinbmc/git-history-ui/issues/new?template=feature_request.yml)
127
+ on GitHub. Describe the use case, not just the solution.
128
+
129
+ ## License
130
+
131
+ By contributing, you agree that your contributions will be licensed under the
132
+ [MIT License](LICENSE).
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ankit Sharma
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -21,6 +21,24 @@ Zero setup. Runs locally. Your code never leaves your machine unless you opt in.
21
21
  npx git-history-ui@latest
22
22
  ```
23
23
 
24
+ ## Table of contents
25
+
26
+ - [10-second workflow](#-10-second-workflow)
27
+ - [Preview](#-preview)
28
+ - [Why this exists](#-why-this-exists)
29
+ - [What makes it different](#-what-makes-it-different)
30
+ - [Quick Start](#-quick-start)
31
+ - [How it compares](#-how-it-compares)
32
+ - [All features](#-all-features)
33
+ - [Usage](#-usage)
34
+ - [Docs](#-docs)
35
+ - [Production](#-production)
36
+ - [Development](#-development)
37
+ - [Requirements](#-requirements)
38
+ - [Contributing](#-contributing)
39
+ - [Security](#-security)
40
+ - [License](#-license)
41
+
24
42
  ## ⚡ 10-second workflow
25
43
 
26
44
  1. Run `npx git-history-ui` inside any git repo
@@ -86,14 +104,16 @@ directory — no installs, no config, no account.
86
104
 
87
105
  ## ⚖️ How it compares
88
106
 
89
- - **vs GitHub UI:** NL search and PR grouping work *with* your unpushed
90
- commits. Time travel and impact analysis aren't on GitHub at all.
91
- - **vs `tig` / `git log`:** visual lanes, browser diffs, optional AI
92
- explanations, insights dashboard.
93
- - **vs desktop clients (GitKraken, SourceTree, Fork):** starts on demand,
94
- no project import, no account, no native install. AI features are
95
- pay-as-you-go on *your* key nothing about your code leaves your
96
- machine unless you opt in.
107
+ | Capability | `git-history-ui` | GitHub UI | `tig` / `git log` | Desktop clients |
108
+ | --- | --- | --- | --- | --- |
109
+ | Works with local and unpushed commits | Yes | No | Yes | Usually |
110
+ | Natural-language history search | Yes | No | No | Rare |
111
+ | PR / feature grouping for local history | Yes | Partial | No | Partial |
112
+ | Time-travel snapshot diffing | Yes | No | No | Rare |
113
+ | Commit impact analysis | Yes | No | No | Rare |
114
+ | Browser-based unified / split diffs | Yes | Yes | No | Yes |
115
+ | Optional AI summaries on your key | Yes | No | No | Rare |
116
+ | No account, import, or desktop install | Yes | No | Yes | No |
97
117
 
98
118
  ## 📦 All features
99
119
 
@@ -172,17 +192,48 @@ npx git-history-ui@latest --no-open # don't open the browser
172
192
  npx git-history-ui@latest --help # full flag list
173
193
  ```
174
194
 
195
+ ### CLI reference
196
+
197
+ ```text
198
+ Usage: git-history-ui [options] [command]
199
+
200
+ Beautiful git history visualization in your browser
201
+
202
+ Options:
203
+ -v, --version output the version number
204
+ -p, --port <number> port to run server on (default: "3000")
205
+ -H, --host <host> host to bind to (default: "localhost")
206
+ -f, --file <path> filter commits by a specific file
207
+ -s, --since <date> filter commits since a date (YYYY-MM-DD)
208
+ -a, --author <name> filter commits by author
209
+ --no-open do not automatically open browser
210
+ --cwd <path> path to the git repository (defaults to cwd)
211
+ --llm <provider> LLM provider: heuristic, anthropic, openai (default:
212
+ auto)
213
+ --preset <name> load filters from a saved preset
214
+ --save-preset <name> save the current flags as a preset for next time
215
+ -h, --help display help for command
216
+
217
+ Commands:
218
+ presets <action> [name] manage saved CLI presets
219
+ ```
220
+
175
221
  ### Optional: bring your own AI key
176
222
 
177
223
  ```bash
178
- # Anthropic (recommended; uses claude-3-5-haiku by default)
224
+ # Anthropic (uses Claude Sonnet 4 by default)
179
225
  export ANTHROPIC_API_KEY=sk-ant-...
180
226
 
181
- # Or OpenAI (uses gpt-4o-mini by default)
227
+ # Or OpenAI (uses GPT 4.1 Nano by default)
182
228
  export OPENAI_API_KEY=sk-...
183
229
 
184
230
  # Force a specific provider when both are set
185
231
  export GHUI_LLM_PROVIDER=anthropic # anthropic | openai | heuristic
232
+
233
+ # Optional model overrides
234
+ export GHUI_LLM_MODEL=claude-sonnet-4-6
235
+ export ANTHROPIC_MODEL=claude-sonnet-4-6
236
+ export OPENAI_MODEL=gpt-4.1-nano
186
237
  ```
187
238
 
188
239
  ### Optional: GitHub PR enrichment
@@ -193,6 +244,13 @@ export GITHUB_TOKEN=ghp_... # fine-grained PAT, read-only on the repo
193
244
 
194
245
  This hydrates the *Grouped* view with PR titles, authors, and labels.
195
246
 
247
+ ## 📚 Docs
248
+
249
+ - [API reference](./docs/API.md)
250
+ - [Architecture](./docs/architecture.md)
251
+ - [Configuration](./docs/configuration.md)
252
+ - [Troubleshooting](./docs/troubleshooting.md)
253
+
196
254
  ## 🏭 Production
197
255
 
198
256
  ```bash
@@ -225,11 +283,13 @@ cd frontend && npm test
225
283
 
226
284
  ## 🤝 Contributing
227
285
 
228
- 1. Fork the repository
229
- 2. Create a feature branch
230
- 3. Make your changes
231
- 4. Run tests
232
- 5. Submit a pull request
286
+ Contributions are welcome. See [CONTRIBUTING.md](CONTRIBUTING.md) for local
287
+ setup, commit conventions, test commands, and PR expectations.
288
+
289
+ ## 🔐 Security
290
+
291
+ Please do not open public issues for security vulnerabilities. See
292
+ [SECURITY.md](SECURITY.md) for the responsible disclosure process.
233
293
 
234
294
  ## 📄 License
235
295
 
package/SECURITY.md ADDED
@@ -0,0 +1,51 @@
1
+ # Security Policy
2
+
3
+ ## Supported Versions
4
+
5
+ | Version | Supported |
6
+ | ------- | ------------------ |
7
+ | 4.x | :white_check_mark: |
8
+ | 3.x | :x: |
9
+ | < 3.0 | :x: |
10
+
11
+ ## Reporting a Vulnerability
12
+
13
+ If you discover a security vulnerability in `git-history-ui`, **please do not
14
+ open a public issue.** Instead, report it responsibly so we can fix it before
15
+ it's disclosed.
16
+
17
+ **Email:** [ankit.sharma199803@gmail.com](mailto:ankit.sharma199803@gmail.com)
18
+
19
+ Please include:
20
+
21
+ - A description of the vulnerability
22
+ - Steps to reproduce
23
+ - The version(s) affected
24
+ - Any potential impact you've identified
25
+
26
+ You should receive an acknowledgment within **48 hours**. We'll work with you
27
+ to understand the issue and coordinate a fix and disclosure timeline.
28
+
29
+ ## Scope
30
+
31
+ The following are in scope:
32
+
33
+ - The `git-history-ui` npm package and CLI
34
+ - The Express backend server (`src/backend/`)
35
+ - API key handling (Anthropic, OpenAI, GitHub tokens)
36
+ - The local SQLite index (`~/.git-history-ui/`)
37
+
38
+ The following are **out of scope**:
39
+
40
+ - Third-party services (Anthropic, OpenAI, GitHub APIs)
41
+ - The user's own git repository contents
42
+ - The Chrome extension and GitHub App scaffolds (experimental, not published)
43
+
44
+ ## Principles
45
+
46
+ - **Local-first.** Your repository data never leaves your machine unless you
47
+ explicitly configure an LLM provider key.
48
+ - **No telemetry.** We do not collect analytics, crash reports, or usage data.
49
+ - **API keys are yours.** Keys are read from environment variables and sent
50
+ directly to the provider you chose. They are never logged, stored, or
51
+ transmitted elsewhere.
@@ -0,0 +1 @@
1
+ import{$ as l,Cc as o,Dc as s,W as m,yc as r,zc as g}from"./chunk-UM7ESEXJ.js";var p=class a{http=l(g);base="/api";cache=new o(100);bundle(t={}){let e=new r;for(let[i,n]of Object.entries(t))n!=null&&(e=e.set(i,String(n)));return this.cache.get(`insights:${e.toString()}`,()=>this.http.get(`${this.base}/insights`,{params:e}),s.VOLATILE)}fileStats(t){let e=new r().set("file",t);return this.cache.get(`file-stats:${t}`,()=>this.http.get(`${this.base}/file-stats`,{params:e}),s.VOLATILE)}impact(t){return this.cache.get(`impact:${t}`,()=>this.http.get(`${this.base}/impact/${t}`),s.IMMUTABLE)}breakage(t,e={}){let i=new r().set("file",t);return e.limit!==void 0&&(i=i.set("limit",String(e.limit))),this.cache.get(`breakage:${i.toString()}`,()=>this.http.get(`${this.base}/breakage`,{params:i}),s.VOLATILE)}summarizeDiff(t){return this.http.post(`${this.base}/summarize-diff`,{text:t})}explainCommit(t){return this.cache.get(`explain:${t}`,()=>this.http.post(`${this.base}/explain-commit/${t}`,{}),s.IMMUTABLE)}invalidate(){this.cache.clear()}static \u0275fac=function(e){return new(e||a)};static \u0275prov=m({token:a,factory:a.\u0275fac,providedIn:"root"})};export{p as a};
@@ -0,0 +1 @@
1
+ import{a as ot,b as rt,c as ft}from"./chunk-F6HZ5OVS.js";import{a as lt,b as ct,c as mt,d as pt,e as dt}from"./chunk-S56XRIZ7.js";import{a as et,b as tt,c as nt,h as it}from"./chunk-GHSRMAIR.js";import{a as at}from"./chunk-VGAHCEAA.js";import{a as G}from"./chunk-STUSEB5P.js";import{g as gt}from"./chunk-Y6GOUMAT.js";import{b as Je,e as Ze}from"./chunk-PXNKNORB.js";import{a as st}from"./chunk-6DL2ZTKG.js";import{$ as M,Cc as _e,D as Ae,Db as te,Dc as be,Eb as ne,Fb as ie,Gb as he,Hb as J,Ib as W,Jb as Qe,Kb as l,Lb as h,Ma as We,Mb as z,Nb as ue,Oa as pe,Ob as Ue,Qa as a,Qb as De,R as je,Rb as Te,Sb as Fe,T as Ie,Va as qe,W as me,Wb as j,Xb as Ke,Yb as q,Za as L,_b as ve,ab as Ye,cb as v,cc as K,dc as Y,ea as P,fa as k,ga as Ge,lb as de,oa as S,ob as d,pb as s,pc as Q,q as Ee,qb as r,qc as N,rb as b,rc as Z,sa as Be,tc as H,vb as ge,wb as fe,xb as A,yb as w,yc as Xe,zb as x,zc as xe}from"./chunk-UM7ESEXJ.js";var Ce=class i{http=M(xe);base="/api";cache=new _e(200);list(e){return this.cache.get(e,()=>this.http.get(`${this.base}/annotations/${e}`),be.VOLATILE)}add(e,t,n){return this.http.post(`${this.base}/annotations/${e}`,{author:t,body:n}).pipe(Ie(()=>this.cache.invalidate(e)))}remove(e,t){return this.http.delete(`${this.base}/annotations/${e}/${t}`).pipe(Ie(()=>this.cache.invalidate(e)))}static \u0275fac=function(t){return new(t||i)};static \u0275prov=me({token:i,factory:i.\u0275fac,providedIn:"root"})};var vt=["svg"];function xt(i,e){i&1&&(s(0,"div",11),l(1," No graph data \u2014 changed files have no detectable internal imports. "),r())}var ye=class i{impact=null;svgRef;simulation=null;hasData=!1;ngAfterViewInit(){this.render()}ngOnChanges(){this.render()}ngOnDestroy(){this.simulation?.stop()}render(){if(!this.svgRef)return;let e=this.svgRef.nativeElement,t=gt(e);t.selectAll("*").remove();let n=this.impact;if(!n||n.dependencyRipple.length===0&&n.modules.length===0){this.hasData=!1;return}this.hasData=!0;let o=new Map,c=(m,E,V)=>{let R=o.get(m);return R||(R={id:m,group:E,label:V},o.set(m,R)),R};for(let m of n.files)c(`f:${m}`,"changed",Re(m));for(let m of n.modules)c(`m:${m}`,"module",m);for(let m of n.dependencyRipple)c(`f:${m.from}`,"changed",Re(m.from)),c(`f:${m.to}`,"imported",Re(m.to));let g=[];for(let m of n.dependencyRipple)g.push({source:`f:${m.from}`,target:`f:${m.to}`,type:"imports"});for(let m of n.files){let E=Ct(m);n.modules.includes(E)&&g.push({source:`f:${m}`,target:`m:${E}`,type:"in-module"})}let u=Array.from(o.values()),O=e.parentElement?.clientWidth??640,p=u.filter(m=>m.group==="module").sort(Le),f=u.filter(m=>m.group==="changed").sort(Le),_=u.filter(m=>m.group==="imported").sort(Le),C=Math.max(p.length,f.length,_.length,4),T=44,$=58,F={module:68,changed:430,imported:780},I=Math.max(O,F.imported+Math.max(320,_t(_))+80),B=Math.max(360,$+C*T+40);t.attr("width",I).attr("height",B).attr("viewBox",`0 0 ${I} ${B}`),t.append("rect").attr("width",I).attr("height",B).attr("fill","transparent"),t.append("g").selectAll("text").data([{label:"Modules",x:F.module},{label:"Changed files",x:F.changed},{label:"Import dependencies",x:F.imported}]).join("text").attr("class","column-title").attr("x",m=>m.x).attr("y",28).text(m=>m.label),ze(p,F.module,$,T),ze(f,F.changed,$,T),ze(_,F.imported,$,T);let $e=t.append("g").selectAll("path").data(g).join("path").attr("class",m=>`impact-link ${m.type}`).attr("d",m=>{let E=o.get(String(m.source)),V=o.get(String(m.target));return E&&V?bt(E,V):""});t.append("g").selectAll("circle").data(u).join("circle").attr("r",m=>m.group==="module"?7:5).attr("fill",m=>m.group==="changed"?"var(--accent)":m.group==="imported"?"#f59e0b":"#8b5cf6").attr("stroke","var(--bg-surface-2)").attr("stroke-width",1.5).attr("cx",m=>m.x??0).attr("cy",m=>m.y??0).append("title").text(m=>`${m.group}: ${m.id.slice(2)}`);let ce=t.append("g").selectAll("rect").data(u).join("rect").attr("class","label-bg").attr("width",m=>ht(m)).attr("height",18).attr("rx",5).attr("ry",5).attr("x",m=>(m.x??0)+10).attr("y",m=>(m.y??0)-11),y=t.append("g").selectAll("text").data(u).join("text").attr("class",m=>`node-label ${m.group}`).attr("x",m=>(m.x??0)+14).attr("y",m=>(m.y??0)+4).text(m=>m.label);this.simulation?.stop(),this.simulation=null}static \u0275fac=function(t){return new(t||i)};static \u0275cmp=L({type:i,selectors:[["app-impact-graph"]],viewQuery:function(t,n){if(t&1&&te(vt,7),t&2){let o;ne(o=ie())&&(n.svgRef=o.first)}},inputs:{impact:"impact"},features:[Be],decls:17,vars:1,consts:[["svg",""],[1,"graph-head"],[1,"legend"],[1,"legend-item"],[1,"dot","dot-changed"],[1,"dot","dot-imported"],[1,"dot","dot-module"],[1,"hint"],["tabindex","0",1,"canvas-wrap"],["aria-label","Commit impact graph"],["class","empty",4,"ngIf"],[1,"empty"]],template:function(t,n){t&1&&(s(0,"div",1)(1,"div",2)(2,"span",3),b(3,"span",4),l(4,"changed file"),r(),s(5,"span",3),b(6,"span",5),l(7,"import dependency"),r(),s(8,"span",3),b(9,"span",6),l(10,"module"),r()(),s(11,"span",7),l(12,"Scroll to explore. Lines show module membership and imports."),r()(),s(13,"div",8),Ge(),b(14,"svg",9,0),v(16,xt,2,0,"div",10),r()),t&2&&(a(16),d("ngIf",!n.hasData))},dependencies:[H,N],styles:["[_nghost-%COMP%]{display:block}.graph-head[_ngcontent-%COMP%]{display:flex;justify-content:space-between;align-items:center;gap:.75rem;margin-bottom:.5rem}.legend[_ngcontent-%COMP%]{display:flex;flex-wrap:wrap;gap:.75rem;align-items:center;font-size:11px;color:var(--fg-muted)}.legend-item[_ngcontent-%COMP%]{display:inline-flex;align-items:center;gap:4px}.hint[_ngcontent-%COMP%]{flex:0 0 auto;color:var(--fg-subtle);font-size:11px}.dot[_ngcontent-%COMP%]{display:inline-block;width:8px;height:8px;border-radius:50%;margin-right:4px}.dot-changed[_ngcontent-%COMP%]{background:var(--accent)}.dot-imported[_ngcontent-%COMP%]{background:#f59e0b}.dot-module[_ngcontent-%COMP%]{background:#8b5cf6}.canvas-wrap[_ngcontent-%COMP%]{position:relative;max-height:430px;min-height:320px;background:radial-gradient(circle at 20% 0%,color-mix(in oklab,var(--accent) 12%,transparent),transparent 34%),var(--bg-surface-2);border-radius:var(--radius-md);border:1px solid var(--border-soft);overflow:auto;overscroll-behavior:contain}.canvas-wrap[_ngcontent-%COMP%]:focus-visible{outline:2px solid var(--border-focus);outline-offset:2px}svg[_ngcontent-%COMP%]{display:block}.empty[_ngcontent-%COMP%]{position:absolute;inset:0;display:grid;place-items:center;color:var(--fg-muted);font-size:11px;pointer-events:none}[_nghost-%COMP%] .column-title{fill:var(--fg-muted);font-size:11px;font-weight:700;letter-spacing:.06em;text-transform:uppercase}[_nghost-%COMP%] .node-label{font-size:10px;fill:var(--fg-secondary);font-family:var(--font-mono, monospace);pointer-events:none;paint-order:stroke;stroke:var(--bg-surface-2);stroke-width:3px;stroke-linejoin:round}[_nghost-%COMP%] .node-label.changed{fill:var(--fg-primary);font-weight:700}[_nghost-%COMP%] .label-bg{fill:color-mix(in oklab,var(--bg-surface) 78%,transparent);stroke:var(--border-soft);stroke-width:1px;opacity:.92}[_nghost-%COMP%] .impact-link{fill:none;stroke-linecap:round}[_nghost-%COMP%] .impact-link.imports{stroke:#f59e0b;stroke-width:2.2px;stroke-opacity:.78}[_nghost-%COMP%] .impact-link.in-module{stroke:#8b5cf6;stroke-width:2px;stroke-opacity:.62;stroke-dasharray:5 5}"],changeDetection:0})};function Re(i){let e=i.split("/");return e[e.length-1]}function ht(i){let e=i.label.length;return Math.max(112,Math.min(520,e*6.7+18))}function _t(i){return Math.max(0,...i.map(e=>ht(e)))}function Le(i,e){return i.label.localeCompare(e.label)}function ze(i,e,t,n){for(let[o,c]of i.entries())c.x=e,c.y=t+o*n}function bt(i,e){let t=i.x??0,n=i.y??0,o=e.x??0,c=e.y??0,g=Math.max(72,Math.abs(o-t)*.42),u=o>=t?1:-1;return`M ${t} ${n} C ${t+u*g} ${n}, ${o-u*g} ${c}, ${o} ${c}`}function Ct(i){let e=i.split("/");return e.length===1?"(root)":e.slice(0,Math.min(e.length-1,3)).join("/")}var Me=class i{constructor(e){this.sanitizer=e}cache=new Map;transform(e){if(!e)return"";let t=this.cache.get(e);if(t)return t;let n=this.sanitizer.bypassSecurityTrustHtml(yt(e));if(this.cache.set(e,n),this.cache.size>50){let o=this.cache.keys().next().value;o&&this.cache.delete(o)}return n}static \u0275fac=function(t){return new(t||i)(qe(Je,16))};static \u0275pipe=Ye({name:"markdown",type:i,pure:!0})};function yt(i){let e=Mt(i);return e=e.replace(/```(\w*)\n([\s\S]*?)```/g,(t,n,o)=>`<pre><code>${o.trim()}</code></pre>`),e=e.replace(/`([^`]+)`/g,"<code>$1</code>"),e=e.replace(/^### (.+)$/gm,"<h4>$1</h4>"),e=e.replace(/^## (.+)$/gm,"<h3>$1</h3>"),e=e.replace(/^# (.+)$/gm,"<h2>$1</h2>"),e=e.replace(/\*\*\*(.+?)\*\*\*/g,"<strong><em>$1</em></strong>"),e=e.replace(/\*\*(.+?)\*\*/g,"<strong>$1</strong>"),e=e.replace(/\*(.+?)\*/g,"<em>$1</em>"),e=e.replace(/^(\s*)[-*] (.+)$/gm,(t,n,o)=>`<li data-depth="${Math.floor(n.length/2)}">${o}</li>`),e=e.replace(/((?:<li[^>]*>.*<\/li>\n?)+)/g,"<ul>$1</ul>"),e=e.replace(/^\d+\.\s+(.+)$/gm,"<li>$1</li>"),e=e.replace(/((?:<li>.*<\/li>\n?){2,})/g,t=>t.includes("data-depth")?t:`<ol>${t}</ol>`),e=e.replace(/\n{2,}/g,"</p><p>"),e=`<p>${e}</p>`,e=e.replace(/<p>\s*(<(?:h[2-4]|ul|ol|pre|blockquote)[^>]*>)/g,"$1"),e=e.replace(/(<\/(?:h[2-4]|ul|ol|pre|blockquote)>)\s*<\/p>/g,"$1"),e=e.replace(/<p>\s*<\/p>/g,""),e=e.replace(/\n/g,"<br>"),e}function Mt(i){return i.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}function wt(i,e){if(i&1&&(s(0,"span",36),l(1),r()),i&2){let t=e.$implicit;a(),h(t)}}function Ot(i,e){if(i&1&&(s(0,"span",37),l(1),r()),i&2){let t=e.$implicit;a(),h(t)}}function Pt(i,e){i&1&&(s(0,"span",38),l(1,"merge"),r())}function kt(i,e){if(i&1&&(s(0,"pre",39),l(1),r()),i&2){let t=x().ngIf;a(),h(t.body)}}function St(i,e){if(i&1){let t=A();s(0,"div",40)(1,"span",41),l(2,"AI"),r(),b(3,"div",42),j(4,"markdown"),s(5,"button",43),w("click",function(){P(t);let o=x(2);return k(o.explanation.set(null))}),l(6,"\xD7"),r()()}if(i&2){let t=e.ngIf;a(3),d("innerHTML",Ke(4,1,t),We)}}function Et(i,e){if(i&1&&(s(0,"div",44),l(1),r()),i&2){let t=e.ngIf;a(),h(t)}}function It(i,e){if(i&1&&(s(0,"li"),l(1),r()),i&2){let t=e.$implicit;a(),h(t)}}function Dt(i,e){if(i&1){let t=A();s(0,"li",28),w("click",function(){let o=P(t).$implicit,c=x(3);return k(c.state.selectHash(o.hash))}),s(1,"code"),l(2),r(),s(3,"span"),l(4),r()()}if(i&2){let t=e.$implicit;a(2),h(t.hash.slice(0,7)),a(2),h(t.subject)}}function Tt(i,e){if(i&1&&(s(0,"div",45)(1,"div",46)(2,"span"),l(3,"Impact"),r(),s(4,"span",47),l(5),r()(),b(6,"app-impact-graph",48),s(7,"div",49)(8,"div")(9,"h4"),l(10,"Modules"),r(),s(11,"ul",50),v(12,It,2,1,"li",51),r()(),s(13,"div")(14,"h4"),l(15,"Related commits"),r(),s(16,"ul",52),v(17,Dt,5,2,"li",53),r()()()()),i&2){let t=e.ngIf;a(5),Ue(" ",t.files.length," files \xB7 ",t.modules.length," modules \xB7 ",t.relatedCommits.length," related commits "),a(),d("impact",t),a(6),d("ngForOf",t.modules),a(5),d("ngForOf",t.relatedCommits)}}function Ft(i,e){if(i&1&&(s(0,"span",54),l(1),r()),i&2){let t=x(2);a(),h(t.files().length)}}function Rt(i,e){if(i&1&&(s(0,"span",64),l(1),r()),i&2){let t=x().$implicit;a(),z("+",t.additions)}}function Lt(i,e){if(i&1&&(s(0,"span",65),l(1),r()),i&2){let t=x().$implicit;a(),z("\u2212",t.deletions)}}function zt(i,e){if(i&1){let t=A();s(0,"div",55)(1,"button",56),w("click",function(){let o=P(t).$implicit,c=x(2);return k(c.selectFile(o))}),b(2,"span",57),s(3,"span",58),l(4),r(),s(5,"span",59),v(6,Rt,2,1,"span",60)(7,Lt,2,1,"span",61),r()(),s(8,"button",62),w("click",function(){let o=P(t).$implicit,c=x(2);return k(c.openFileHistory(o.file))}),l(9," \u23F1 "),r(),s(10,"button",63),w("click",function(){let o=P(t).$implicit,c=x(2);return k(c.openFileBreakage(o.file))}),l(11," \u26A0 "),r()()}if(i&2){let t,n=e.$implicit,o=x(2);a(),W("selected",n.file===((t=o.activeFile())==null?null:t.file)),a(),de("data-status",n.status),a(),d("title",n.file),a(),h(n.file),a(2),d("ngIf",n.additions),a(),d("ngIf",n.deletions)}}function Nt(i,e){i&1&&(s(0,"div",66),l(1,"No files changed."),r())}function Ht(i,e){i&1&&(s(0,"div",66),l(1,"Loading\u2026"),r())}function $t(i,e){if(i&1){let t=A();s(0,"div",67)(1,"div",68)(2,"strong"),l(3),r(),s(4,"span",69),l(5),j(6,"date"),r(),s(7,"button",70),w("click",function(){let o=P(t).$implicit,c=x(2);return k(c.deleteComment(o.id))}),l(8," \xD7 "),r()(),s(9,"p",71),l(10),r()()}if(i&2){let t=e.$implicit;a(3),h(t.author),a(2),h(q(6,3,t.createdAt,"short")),a(5),h(t.body)}}function Vt(i,e){if(i&1){let t=A();ge(0),s(1,"header",2)(2,"div",3)(3,"span",4),l(4),r(),s(5,"span",5),v(6,wt,2,1,"span",6)(7,Ot,2,1,"span",7)(8,Pt,2,0,"span",8),r()(),s(9,"h2",9),l(10),r(),s(11,"div",10)(12,"span"),l(13),r(),s(14,"span",11),l(15,"\u2022"),r(),s(16,"span"),l(17),j(18,"date"),r()(),v(19,kt,2,1,"pre",12),s(20,"div",13)(21,"button",14),w("click",function(){P(t);let o=x();return k(o.onExplain())}),l(22),r(),s(23,"button",14),w("click",function(){P(t);let o=x();return k(o.onLoadImpact())}),l(24),r(),s(25,"button",15),w("click",function(){P(t);let o=x();return k(o.copyShareLink())}),l(26),r()(),v(27,St,7,3,"div",16)(28,Et,2,1,"div",17),r(),v(29,Tt,18,6,"div",18),s(30,"div",19)(31,"aside",20)(32,"div",21)(33,"span"),l(34,"Files"),r(),v(35,Ft,2,1,"span",22),r(),s(36,"div",23),v(37,zt,12,7,"div",24)(38,Nt,2,0,"div",25)(39,Ht,2,0,"div",25),r()(),s(40,"section",26)(41,"details",27)(42,"summary",28),w("click",function(o){P(t);let c=x();return k(c.toggleAnnotations(o))}),l(43),r(),s(44,"div",29),v(45,$t,11,6,"div",30),s(46,"div",31)(47,"input",32),Fe("ngModelChange",function(o){P(t);let c=x();return Te(c.commentAuthor,o)||(c.commentAuthor=o),k(o)}),r(),s(48,"textarea",33),Fe("ngModelChange",function(o){P(t);let c=x();return Te(c.commentDraft,o)||(c.commentDraft=o),k(o)}),r(),s(49,"button",34),w("click",function(){P(t);let o=x();return k(o.addComment())}),l(50," Post "),r()()()(),b(51,"app-diff-viewer",35),r()(),fe()}if(i&2){let t=e.ngIf,n=x();a(4),h(t.shortHash),a(2),d("ngForOf",t.tags),a(),d("ngForOf",t.branches),a(),d("ngIf",t.isMerge),a(2),h(t.subject),a(3),ue("",t.author," <",t.authorEmail,">"),a(4),h(q(18,29,t.date,"medium")),a(2),d("ngIf",t.body),a(2),d("disabled",n.explaining()),a(),z(" ",n.explaining()?"...":"\u2728 Explain change"," "),a(),d("disabled",n.loadingImpact()),a(),z(" ",n.loadingImpact()?"...":n.impact()?"Refresh impact":"Show impact"," "),a(2),z(" ",n.shareCopied()?"Copied!":"\u{1F517} Share"," "),a(),d("ngIf",n.explanation()),a(),d("ngIf",n.explainError()),a(),d("ngIf",n.impact()),a(6),d("ngIf",n.files().length),a(2),d("ngForOf",n.files())("ngForTrackBy",n.trackByFile),a(),d("ngIf",!n.files().length&&!n.loading()),a(),d("ngIf",n.loading()),a(2),d("open",n.annotationsOpen()),a(2),z(" \u{1F4AC} Notes (",n.comments().length,") "),a(2),d("ngForOf",n.comments()),a(2),De("ngModel",n.commentAuthor),a(),De("ngModel",n.commentDraft),a(),d("disabled",!n.commentDraft.trim()),a(2),d("fileInput",n.activeFile())}}function At(i,e){i&1&&(s(0,"div",72)(1,"p",73),l(2,"No commit selected"),r(),s(3,"p",74),l(4," Pick a commit from the list, or press "),s(5,"kbd",75),l(6,"\u2318K"),r(),l(7," to open the command palette. "),r()())}var we=class i{state=M(G);git=M(at);insightsApi=M(st);annotationsApi=M(Ce);router=M(Ze);commit=this.state.selected;impact=S(null);loadingImpact=S(!1);explanation=S(null);explainError=S(null);explaining=S(!1);comments=S([]);annotationsOpen=S(!1);shareCopied=S(!1);commentDraft="";commentAuthor="me";loading=S(!1);files=rt(ot(this.commit).pipe(je(e=>e?(this.loading.set(!0),this.git.getDiff(e.hash).pipe(Ae(()=>Ee([])))):(this.loading.set(!1),Ee([])))),{initialValue:[]});activeFileIndex=S(0);activeFile=K(()=>{let e=this.files();if(!e.length)return null;let t=Math.min(this.activeFileIndex(),e.length-1);return e[t]});constructor(){Y(()=>{this.files(),this.activeFileIndex.set(0),this.loading.set(!1)}),Y(()=>{let e=this.commit();if(this.impact.set(null),this.explanation.set(null),this.explainError.set(null),this.shareCopied.set(!1),!e){this.comments.set([]);return}this.annotationsApi.list(e.hash).subscribe({next:t=>this.comments.set(t),error:()=>this.comments.set([])})})}trackByFile(e,t){return t.file}selectFile(e){let t=this.files().findIndex(n=>n.file===e.file);t>=0&&this.activeFileIndex.set(t)}openFileHistory(e){this.router.navigate(["/file",encodeURIComponent(e)])}openFileBreakage(e){this.router.navigate(["/file",encodeURIComponent(e)],{queryParams:{tab:"breakage"}})}shortPath(e){if(e.length<=32)return e;let t=e.split("/");return t.length<=2?e:t[0]+"/.../"+t.slice(-2).join("/")}onLoadImpact(){let e=this.commit();e&&(this.loadingImpact.set(!0),this.insightsApi.impact(e.hash).subscribe({next:t=>{this.impact.set(t),this.loadingImpact.set(!1)},error:()=>this.loadingImpact.set(!1)}))}onExplain(){let e=this.commit();!e||this.explaining()||(this.explaining.set(!0),this.explainError.set(null),this.insightsApi.explainCommit(e.hash).subscribe({next:t=>{this.explanation.set(t.summary),this.explaining.set(!1)},error:t=>{this.explainError.set(t?.error?.error??"AI explanation unavailable. Set ANTHROPIC_API_KEY or OPENAI_API_KEY."),this.explaining.set(!1)}}))}copyShareLink(){let e=this.commit();if(!e)return;let t=`${window.location.origin}/?commit=${e.hash}`;navigator.clipboard?.writeText(t).then(()=>{this.shareCopied.set(!0),setTimeout(()=>this.shareCopied.set(!1),1500)}).catch(()=>{this.shareCopied.set(!1)})}toggleAnnotations(e){setTimeout(()=>this.annotationsOpen.set(!this.annotationsOpen()),0)}addComment(){let e=this.commit();!e||!this.commentDraft.trim()||this.annotationsApi.add(e.hash,this.commentAuthor||"anonymous",this.commentDraft.trim()).subscribe({next:t=>{this.comments.set([...this.comments(),t]),this.commentDraft=""}})}deleteComment(e){let t=this.commit();t&&this.annotationsApi.remove(t.hash,e).subscribe({next:()=>this.comments.set(this.comments().filter(n=>n.id!==e))})}static \u0275fac=function(t){return new(t||i)};static \u0275cmp=L({type:i,selectors:[["app-commit-detail"]],decls:3,vars:2,consts:[["empty",""],[4,"ngIf","ngIfElse"],[1,"head"],[1,"row"],[1,"hash"],[1,"badges"],["class","badge tag",4,"ngFor","ngForOf"],["class","badge branch",4,"ngFor","ngForOf"],["class","badge merge",4,"ngIf"],[1,"subject"],[1,"meta"],[1,"dot"],["class","body",4,"ngIf"],[1,"actions"],[1,"btn","btn-ghost","btn-sm",3,"click","disabled"],[1,"btn","btn-ghost","btn-sm",3,"click"],["class","ai-card",4,"ngIf"],["class","ai-card error",4,"ngIf"],["class","impact-card",4,"ngIf"],[1,"split"],[1,"files"],[1,"files-header"],["class","count",4,"ngIf"],[1,"files-list"],["class","file-row",4,"ngFor","ngForOf","ngForTrackBy"],["class","files-empty",4,"ngIf"],[1,"diff"],[1,"annotations",3,"open"],[3,"click"],[1,"annot-body"],["class","comment",4,"ngFor","ngForOf"],[1,"comment-form"],["placeholder","Your name",1,"input",3,"ngModelChange","ngModel"],["placeholder","Add a note for your team\u2026",1,"input",3,"ngModelChange","ngModel"],[1,"btn",3,"click","disabled"],[3,"fileInput"],[1,"badge","tag"],[1,"badge","branch"],[1,"badge","merge"],[1,"body"],[1,"ai-card"],[1,"ai-pill"],[1,"ai-text",3,"innerHTML"],[1,"btn","btn-ghost","btn-icon","close",3,"click"],[1,"ai-card","error"],[1,"impact-card"],[1,"impact-head"],[1,"impact-meta"],[3,"impact"],[1,"impact-body"],[1,"modules"],[4,"ngFor","ngForOf"],[1,"related"],[3,"click",4,"ngFor","ngForOf"],[1,"count"],[1,"file-row"],[1,"file",3,"click"],[1,"status-dot"],[1,"path",3,"title"],[1,"counts"],["class","add",4,"ngIf"],["class","del",4,"ngIf"],["title","View file history",1,"file-history",3,"click"],["title","Why did this break? Open breakage analysis","aria-label","Open breakage analysis for this file",1,"file-history","breakage",3,"click"],[1,"add"],[1,"del"],[1,"files-empty"],[1,"comment"],[1,"comment-head"],[1,"comment-date"],["title","Delete",1,"btn","btn-ghost","btn-icon",3,"click"],[1,"comment-body"],[1,"placeholder"],[1,"title"],[1,"hint"],[1,"kbd"]],template:function(t,n){if(t&1&&v(0,Vt,52,32,"ng-container",1)(1,At,8,0,"ng-template",null,0,ve),t&2){let o=he(2);d("ngIf",n.commit())("ngIfElse",o)}},dependencies:[H,Q,N,it,et,tt,nt,dt,ye,Z,Me],styles:['[_nghost-%COMP%]{display:flex;flex-direction:column;height:100%;min-height:0;background:transparent}.head[_ngcontent-%COMP%]{position:sticky;top:0;z-index:3;padding:.9rem 1rem .8rem;border-bottom:1px solid var(--border-soft);background:color-mix(in oklab,var(--bg-glass) 95%,transparent);-webkit-backdrop-filter:blur(14px);backdrop-filter:blur(14px)}.row[_ngcontent-%COMP%]{display:flex;align-items:center;gap:.5rem;flex-wrap:wrap;margin-bottom:.4rem}.hash[_ngcontent-%COMP%]{font-family:var(--font-mono);font-size:12px;color:var(--fg-muted);padding:2px 6px;background:color-mix(in oklab,var(--bg-surface-2) 78%,transparent);border:1px solid var(--border-soft);border-radius:999px}.badges[_ngcontent-%COMP%]{display:flex;flex-wrap:wrap;gap:4px;max-height:44px;overflow:hidden}.badge[_ngcontent-%COMP%]{font-size:10px;font-weight:600;padding:2px 7px;border-radius:999px;border:1px solid transparent;max-width:180px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.badge.tag[_ngcontent-%COMP%]{background:#d9770626;color:var(--warning);border-color:color-mix(in oklab,var(--warning) 24%,transparent)}.badge.branch[_ngcontent-%COMP%]{background:var(--accent-soft);color:var(--accent);border-color:color-mix(in oklab,var(--accent) 24%,transparent)}.badge.merge[_ngcontent-%COMP%]{background:#8b5cf62e;color:#8b5cf6}.subject[_ngcontent-%COMP%]{font-size:clamp(17px,1.6vw,22px);margin:0 0 4px;font-weight:600}.meta[_ngcontent-%COMP%]{display:flex;gap:6px;align-items:center;font-size:12px;color:var(--fg-muted)}.meta[_ngcontent-%COMP%] .dot[_ngcontent-%COMP%]{opacity:.5}.body[_ngcontent-%COMP%]{white-space:pre-wrap;font-family:var(--font-mono);font-size:12px;color:var(--fg-secondary);background:var(--bg-surface-2);border:1px solid var(--border-soft);border-radius:var(--radius-sm);padding:.5rem .75rem;margin-top:.5rem;max-height:160px;overflow:auto}.split[_ngcontent-%COMP%]{flex:1;display:grid;grid-template-columns:minmax(240px,300px) minmax(0,1fr);grid-template-rows:minmax(0,1fr);gap:.75rem;padding:.75rem;min-height:0;overflow:hidden}.files[_ngcontent-%COMP%]{display:flex;flex-direction:column;min-height:0;background:var(--bg-panel);border:1px solid var(--border-soft);border-radius:var(--radius-md);box-shadow:var(--shadow-sm);overflow:hidden}.files-header[_ngcontent-%COMP%]{display:flex;justify-content:space-between;padding:.6rem .85rem;font-size:12px;color:var(--fg-muted);border-bottom:1px solid var(--border-soft);background:color-mix(in oklab,var(--bg-surface) 82%,transparent)}.count[_ngcontent-%COMP%]{background:var(--bg-surface-2);padding:0 6px;border-radius:999px;font-size:11px}.files-list[_ngcontent-%COMP%]{overflow:auto;flex:1;min-height:0}.file[_ngcontent-%COMP%]{display:grid;grid-template-columns:10px 1fr auto;gap:.5rem;align-items:center;width:100%;padding:.5rem .75rem;border:0;background:transparent;color:inherit;cursor:pointer;text-align:left;border-bottom:1px solid var(--border-soft);font-size:12px}.file[_ngcontent-%COMP%]:hover{background:color-mix(in oklab,var(--bg-hover) 74%,transparent)}.file.selected[_ngcontent-%COMP%]{background:color-mix(in oklab,var(--accent) 15%,transparent);box-shadow:inset 3px 0 0 var(--accent)}.file[_ngcontent-%COMP%] .path[_ngcontent-%COMP%]{font-family:var(--font-mono);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;direction:rtl;text-align:left}.status-dot[_ngcontent-%COMP%]{width:8px;height:8px;border-radius:50%;background:var(--accent)}.status-dot[data-status=added][_ngcontent-%COMP%]{background:var(--success)}.status-dot[data-status=deleted][_ngcontent-%COMP%]{background:var(--danger)}.status-dot[data-status=renamed][_ngcontent-%COMP%], .status-dot[data-status=copied][_ngcontent-%COMP%]{background:var(--warning)}.status-dot[data-status=binary][_ngcontent-%COMP%]{background:var(--fg-muted)}.counts[_ngcontent-%COMP%]{display:flex;gap:6px;font-family:var(--font-mono);font-size:11px}.counts[_ngcontent-%COMP%] .add[_ngcontent-%COMP%]{color:var(--success)}.counts[_ngcontent-%COMP%] .del[_ngcontent-%COMP%]{color:var(--danger)}.files-empty[_ngcontent-%COMP%]{padding:1rem;color:var(--fg-muted);font-size:12px;text-align:center}.diff[_ngcontent-%COMP%]{min-width:0;min-height:0;display:flex;flex-direction:column;overflow:hidden;border-radius:var(--radius-md);background:var(--bg-panel);border:1px solid var(--border-soft);box-shadow:var(--shadow-sm)}.placeholder[_ngcontent-%COMP%]{flex:1;display:grid;place-items:center;text-align:center;color:var(--fg-muted)}.placeholder[_ngcontent-%COMP%] .title[_ngcontent-%COMP%]{font-size:16px;margin-bottom:4px;color:var(--fg-secondary)}.placeholder[_ngcontent-%COMP%] .hint[_ngcontent-%COMP%]{font-size:13px}.actions[_ngcontent-%COMP%]{display:flex;flex-wrap:wrap;gap:.4rem;margin-top:.6rem}.btn-sm[_ngcontent-%COMP%]{font-size:11px;padding:.3rem .65rem}.ai-card[_ngcontent-%COMP%]{display:flex;align-items:flex-start;gap:.5rem;padding:.55rem .75rem;margin-top:.5rem;background:color-mix(in oklab,var(--accent) 12%,transparent);border-radius:var(--radius-sm);font-size:12px;color:var(--fg-secondary);max-height:min(280px,32vh);overflow:hidden}.ai-card.error[_ngcontent-%COMP%]{background:#ef44441f;color:var(--danger)}.ai-pill[_ngcontent-%COMP%]{flex-shrink:0;font-size:10px;font-weight:700;letter-spacing:.04em;background:var(--accent);color:var(--accent-fg);padding:1px 5px;border-radius:4px}.ai-text[_ngcontent-%COMP%]{flex:1;min-width:0;min-height:0;max-height:calc(min(280px,32vh) - 1.1rem);line-height:1.6;font-size:.85rem;overflow-y:auto;overflow-x:hidden;padding-right:.45rem;overscroll-behavior:contain;scrollbar-gutter:stable}.ai-text[_ngcontent-%COMP%]::-webkit-scrollbar{width:8px}.ai-text[_ngcontent-%COMP%]::-webkit-scrollbar-track{background:transparent}.ai-text[_ngcontent-%COMP%]::-webkit-scrollbar-thumb{background:color-mix(in oklab,var(--accent) 40%,transparent);border-radius:999px}.ai-text[_ngcontent-%COMP%] h2[_ngcontent-%COMP%], .ai-text[_ngcontent-%COMP%] h3[_ngcontent-%COMP%], .ai-text[_ngcontent-%COMP%] h4[_ngcontent-%COMP%]{margin:.6rem 0 .3rem;font-size:.9rem;font-weight:600;color:var(--text-primary, #e2e8f0)}.ai-text[_ngcontent-%COMP%] h2[_ngcontent-%COMP%]{font-size:1rem}.ai-text[_ngcontent-%COMP%] p[_ngcontent-%COMP%]{margin:.25rem 0}.ai-text[_ngcontent-%COMP%] ul[_ngcontent-%COMP%], .ai-text[_ngcontent-%COMP%] ol[_ngcontent-%COMP%]{margin:.3rem 0;padding-left:1.4rem}.ai-text[_ngcontent-%COMP%] li[_ngcontent-%COMP%]{margin:.15rem 0}.ai-text[_ngcontent-%COMP%] code[_ngcontent-%COMP%]{background:#8b5cf626;padding:1px 5px;border-radius:3px;font-size:.82em;font-family:var(--font-mono, "JetBrains Mono", monospace)}.ai-text[_ngcontent-%COMP%] pre[_ngcontent-%COMP%]{background:#00000040;padding:.5rem .75rem;border-radius:6px;overflow-x:auto;margin:.4rem 0}.ai-text[_ngcontent-%COMP%] pre[_ngcontent-%COMP%] code[_ngcontent-%COMP%]{background:none;padding:0}.ai-text[_ngcontent-%COMP%] strong[_ngcontent-%COMP%]{color:var(--text-primary, #e2e8f0)}.ai-card[_ngcontent-%COMP%] .close[_ngcontent-%COMP%]{flex-shrink:0;font-size:14px;line-height:1;padding:0 6px}.impact-card[_ngcontent-%COMP%]{margin:.6rem 1rem;background:var(--bg-panel);border:1px solid var(--border-soft);border-radius:var(--radius-lg);padding:.75rem 1rem;box-shadow:var(--shadow-sm)}.impact-head[_ngcontent-%COMP%]{display:flex;justify-content:space-between;font-weight:600;margin-bottom:.5rem;font-size:13px}.impact-meta[_ngcontent-%COMP%]{color:var(--fg-muted);font-weight:400;font-size:11px}.impact-body[_ngcontent-%COMP%]{display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:.75rem;font-size:12px}.impact-body[_ngcontent-%COMP%] h4[_ngcontent-%COMP%]{margin:0 0 .4rem;font-size:11px;color:var(--fg-muted);text-transform:uppercase;letter-spacing:.04em}.impact-body[_ngcontent-%COMP%] ul[_ngcontent-%COMP%]{list-style:none;margin:0;padding:0}.impact-body[_ngcontent-%COMP%] li[_ngcontent-%COMP%]{padding:2px 0;word-break:break-all}.modules[_ngcontent-%COMP%] li[_ngcontent-%COMP%]{font-family:var(--font-mono, monospace)}.ripple[_ngcontent-%COMP%] li[_ngcontent-%COMP%]{font-size:11px;color:var(--fg-secondary)}.related[_ngcontent-%COMP%] li[_ngcontent-%COMP%]{cursor:pointer;display:flex;gap:.4rem}.related[_ngcontent-%COMP%] li[_ngcontent-%COMP%]:hover{color:var(--accent)}.related[_ngcontent-%COMP%] code[_ngcontent-%COMP%]{font-family:var(--font-mono, monospace);color:var(--fg-muted);flex-shrink:0}.impact-body[_ngcontent-%COMP%] .muted[_ngcontent-%COMP%]{color:var(--fg-muted);font-style:italic;margin:0;font-size:11px}.file-row[_ngcontent-%COMP%]{display:flex}.file-row[_ngcontent-%COMP%] .file[_ngcontent-%COMP%]{flex:1}.file-history[_ngcontent-%COMP%]{background:transparent;border:0;border-bottom:1px solid var(--border-soft);cursor:pointer;color:var(--fg-muted);padding:0 .6rem;font-size:12px}.file-history[_ngcontent-%COMP%]:hover{background:var(--bg-elevated);color:var(--accent)}.file-history.breakage[_ngcontent-%COMP%]:hover{color:var(--danger, #dc2626)}.annotations[_ngcontent-%COMP%]{margin:.5rem;background:var(--bg-surface);border:1px solid var(--border-soft);border-radius:var(--radius-sm)}.annotations[_ngcontent-%COMP%] summary[_ngcontent-%COMP%]{padding:.4rem .7rem;cursor:pointer;font-size:12px;color:var(--fg-secondary);-webkit-user-select:none;user-select:none}.annotations[open][_ngcontent-%COMP%] summary[_ngcontent-%COMP%]{border-bottom:1px solid var(--border-soft)}.annot-body[_ngcontent-%COMP%]{padding:.5rem .7rem}.comment[_ngcontent-%COMP%]{padding:.4rem 0;border-bottom:1px dashed var(--border-soft);font-size:12px}.comment[_ngcontent-%COMP%]:last-of-type{border-bottom:0}.comment-head[_ngcontent-%COMP%]{display:flex;align-items:center;gap:.5rem}.comment-date[_ngcontent-%COMP%]{color:var(--fg-muted);font-size:11px;flex:1}.comment-body[_ngcontent-%COMP%]{margin:.2rem 0 0;line-height:1.4;white-space:pre-wrap}.comment-form[_ngcontent-%COMP%]{display:flex;gap:.4rem;flex-direction:column;margin-top:.5rem}.comment-form[_ngcontent-%COMP%] .input[_ngcontent-%COMP%]{width:100%;padding:.35rem .5rem;background:var(--bg-app);border:1px solid var(--border-soft);border-radius:var(--radius-sm);color:var(--fg-primary);font-family:inherit;font-size:12px}.comment-form[_ngcontent-%COMP%] textarea[_ngcontent-%COMP%]{min-height:60px;resize:vertical}.comment-form[_ngcontent-%COMP%] .btn[_ngcontent-%COMP%]{align-self:flex-end}'],changeDetection:0})};var jt=["canvas"],Gt=["scroll"];function Bt(i,e){if(i&1&&b(0,"span",12),i&2){let t=e.$implicit;J("background",t)}}function Wt(i,e){i&1&&(s(0,"span",19),l(1,"merge"),r())}function qt(i,e){if(i&1&&(s(0,"div",20),l(1),r()),i&2){let t=x().ngIf;a(),h(t.refs)}}function Yt(i,e){if(i&1&&(s(0,"div",13)(1,"div",14)(2,"code"),l(3),r(),v(4,Wt,2,0,"span",15),r(),s(5,"div",16),l(6),r(),s(7,"div",17)(8,"span"),l(9),r(),s(10,"span"),l(11),j(12,"date"),r()(),v(13,qt,2,1,"div",18),r()),i&2){let t=e.ngIf;J("left",t.x,"px")("top",t.y,"px"),a(3),h(t.shortHash),a(),d("ngIf",t.isMerge),a(2),h(t.subject),a(3),h(t.author),a(2),h(q(12,10,t.date,"MMM d, y, h:mm a")),a(2),d("ngIf",t.refs)}}function Qt(i,e){i&1&&(s(0,"div",21),l(1," No commits to draw. "),r())}var D=34,ae=24,Ne=5.5,se=16,le=16,He=["#4f46e5","#06b6d4","#f59e0b","#ef4444","#10b981","#8b5cf6","#ec4899","#0ea5e9"],Oe=class i{state=M(G);theme=M(ft);legendColors=He.slice(0,5);graphSummary=K(()=>{let e=this.state.commits().length,t=this.laneCount;return e?`${e.toLocaleString()} commits across ${t} lane${t===1?"":"s"}`:"Swim-lane visualization"});contentHeight=K(()=>{let e=this.state.commits().length;return e?le*2+e*D:0});canvasRef;scrollRef;nodes=[];rowByHash=new Map;laneCount=1;hoverRow=-1;hoverTip=S(null);layoutToken=0;idleHandle=null;canvasSize={width:0,height:0,dpr:0};canvasTransform="";cachedTheme=null;onCanvasClick=e=>this.onClick(e);onCanvasMove=e=>this.onMouseMove(e);onCanvasLeave=()=>this.onMouseLeave();scrollRaf=0;onScroll=()=>{this.scrollRaf||(this.scrollRaf=requestAnimationFrame(()=>{this.scrollRaf=0,this.draw()}))};constructor(){Y(()=>{this.scheduleLayout(this.state.commits())}),Y(()=>{this.state.selectedHash(),this.draw()}),Y(()=>{this.theme.resolved(),this.cachedTheme=null,this.draw()})}ngAfterViewInit(){let e=this.canvasRef.nativeElement;e.addEventListener("click",this.onCanvasClick),e.addEventListener("mousemove",this.onCanvasMove),e.addEventListener("mouseleave",this.onCanvasLeave),this.scrollRef?.nativeElement.addEventListener("scroll",this.onScroll,{passive:!0}),this.draw()}ngOnDestroy(){let e=this.canvasRef?.nativeElement;e?.removeEventListener("click",this.onCanvasClick),e?.removeEventListener("mousemove",this.onCanvasMove),e?.removeEventListener("mouseleave",this.onCanvasLeave),this.scrollRef?.nativeElement.removeEventListener("scroll",this.onScroll),this.scrollRaf&&cancelAnimationFrame(this.scrollRaf),this.cancelIdleLayout()}onResize(){this.draw()}scheduleLayout(e){let t=++this.layoutToken;if(this.cancelIdleLayout(),this.nodes=[],this.rowByHash.clear(),this.hoverRow=-1,this.hoverTip.set(null),!e.length){this.laneCount=1,this.draw();return}let n=[],o=new Map,c=(f,_)=>{let C=n[f];C&&o.delete(C),n[f]=_,_&&o.set(_,f)},g=f=>{let _=o.get(f);if(_!==void 0)return _;let C=n.indexOf(null);return C>=0?(c(C,f),C):(c(n.length,f),n.length-1)},u=f=>{let _=e[f],C=g(_.hash),T={commit:_,row:f,lane:C};this.nodes.push(T),this.rowByHash.set(_.hash,T);let[$,...F]=_.parents;c(C,$??null);for(let I of F)if(!o.has(I)){let B=n.indexOf(null);B>=0?c(B,I):c(n.length,I)}for(;n.length&&n[n.length-1]===null;)n.pop();C+1>this.laneCount&&(this.laneCount=C+1)};if(this.laneCount=1,e.length<=5e3){for(let f=0;f<e.length;f++)u(f);this.draw();return}let O=0,p=f=>{if(t!==this.layoutToken)return;let _=performance.now();for(;O<e.length;){u(O++);let C=f?.timeRemaining?.()??0;if(O%250===0&&C<=1&&performance.now()-_>8)break}this.draw(),O<e.length&&t===this.layoutToken&&(this.idleHandle=this.requestIdle(p))};this.idleHandle=this.requestIdle(p)}draw(){let e=this.canvasRef?.nativeElement;if(!e)return;let t=this.scrollRef?.nativeElement,n=window.devicePixelRatio||1,o=t?.scrollTop??0,c=t?.clientHeight??0,g=Math.max(t?.clientWidth??se*2+ae,se*2+ae),u=Math.max(c||D*8,D);this.ensureCanvasSize(e,g,u,n);let O=`translateY(${o}px)`;this.canvasTransform!==O&&(this.canvasTransform=O,e.style.transform=O);let p=e.getContext("2d"),f=this.cachedTheme??(this.cachedTheme=this.readTheme(e));if(p.setTransform(n,0,0,n,0,-o*n),p.clearRect(0,o,g,u),p.fillStyle=f.surface,p.fillRect(0,o,g,u),!this.nodes.length)return;let _=this.laneStep(g),C=this.nodeRadius(_),T=y=>this.xOfLane(y,g,_),$=y=>le+y*D+D/2,F=y=>He[y%He.length],I=this.state.selectedHash(),B=D*4,$e=Math.max(0,Math.floor((o-le-B)/D)),Ve=Math.min(this.nodes.length-1,Math.ceil((o+u-le+B)/D)),ce=this.nodes.slice($e,Ve+1);this.drawRows(p,g,T,$,C,f,I,ce),this.drawGuides(p,o,u,f),p.lineCap="round",p.lineJoin="round";for(let y of ce)for(let m of y.commit.parents){let E=this.rowByHash.get(m);if(!E)continue;let V=T(y.lane),R=$(y.row),ee=T(E.lane),X=$(E.row);p.strokeStyle=f.shadow,p.lineWidth=4,p.globalAlpha=.35,this.drawEdge(p,V,R,ee,X),p.strokeStyle=F(E.lane),p.lineWidth=y.commit.isMerge?2.6:2.2,p.globalAlpha=I&&I!==y.commit.hash&&I!==E.commit.hash?.55:.9,this.drawEdge(p,V,R,ee,X),p.globalAlpha=1}for(let y of ce){let m=T(y.lane),E=$(y.row),V=F(y.lane),R=y.commit.hash===I,ee=y.row===this.hoverRow,X=R?C+2:ee?C+1:C;p.beginPath(),p.arc(m,E,X+3,0,Math.PI*2),p.fillStyle=f.nodeRing,p.fill(),p.beginPath(),p.arc(m,E,X,0,Math.PI*2),p.fillStyle=y.commit.isMerge?f.surface:V,p.fill(),p.lineWidth=y.commit.isMerge||R?2.5:1.75,p.strokeStyle=V,p.stroke(),(R||ee)&&(p.beginPath(),p.arc(m,E,X+5,0,Math.PI*2),p.strokeStyle=V,p.globalAlpha=R?.42:.24,p.lineWidth=2,p.stroke(),p.globalAlpha=1)}}drawRows(e,t,n,o,c,g,u,O=this.nodes){for(let p of O){let f=o(p.row)-D/2;if(p.row%2===1&&(e.fillStyle=g.rowAlt,e.fillRect(0,f,t,D)),(p.row===this.hoverRow||p.commit.hash===u)&&(e.fillStyle=p.commit.hash===u?g.rowSelected:g.rowHover,this.roundRect(e,6,f+3,t-12,D-6,8),e.fill()),p.commit.branches.length||p.commit.tags.length){let _=n(p.lane)+c+8;this.drawRefPill(e,_,o(p.row),p.commit,g)}}}drawGuides(e,t,n,o){e.save(),e.strokeStyle=o.guide,e.lineWidth=1,e.setLineDash([3,5]);let c=t,g=t+n,u=e.canvas.clientWidth,O=this.laneStep(u);for(let p=0;p<this.laneCount;p++){let f=this.xOfLane(p,u,O);e.beginPath(),e.moveTo(f,c),e.lineTo(f,g),e.stroke()}e.restore()}ensureCanvasSize(e,t,n,o){let c=Math.floor(t*o),g=Math.floor(n*o);this.canvasSize.width===c&&this.canvasSize.height===g&&this.canvasSize.dpr===o||(this.canvasSize={width:c,height:g,dpr:o},e.width=c,e.height=g,e.style.width=`${t}px`,e.style.height=`${n}px`)}laneStep(e){if(this.laneCount<=1)return ae;let t=Math.max(1,e-se*2-Ne*2);return Math.min(ae,t/(this.laneCount-1))}nodeRadius(e){return Math.max(2,Math.min(Ne,e/2-.5))}xOfLane(e,t,n){if(this.laneCount<=1)return Math.min(t/2,se+ae/2);let o=(this.laneCount-1)*n;return Math.max(se+Ne,(t-o)/2)+e*n}requestIdle(e){let t=window.requestIdleCallback;return t?t(e,{timeout:100}):window.setTimeout(()=>e(),16)}cancelIdleLayout(){this.idleHandle!==null&&(window.cancelIdleCallback?window.cancelIdleCallback(this.idleHandle):clearTimeout(this.idleHandle),this.idleHandle=null)}drawEdge(e,t,n,o,c){if(e.beginPath(),e.moveTo(t,n),t===o)e.lineTo(o,c);else{let g=n+Math.min(D*.75,Math.max(12,(c-n)*.36));e.bezierCurveTo(t,g,o,g,o,c)}e.stroke()}drawRefPill(e,t,n,o,c){let g=o.tags[0]??o.branches.find(_=>!Ut(_));if(!g)return;let u=Math.max(0,e.canvas.clientWidth-t-8);if(u<28)return;let O=Kt(g,u>72?12:8);e.font="600 10px ui-sans-serif, system-ui, sans-serif";let p=Math.min(u,72,e.measureText(O).width+14),f=18;e.fillStyle=o.tags.length?c.warningSoft:c.accentSoft,this.roundRect(e,t,n-f/2,p,f,999),e.fill(),e.fillStyle=o.tags.length?c.warning:c.accent,e.fillText(O,t+7,n+3.5)}onClick(e){let t=this.nodeFromEvent(e);t&&this.state.selectHash(t.commit.hash)}onMouseMove(e){let t=this.nodeFromEvent(e),n=t?.row??-1;n!==this.hoverRow&&(this.hoverRow=n,this.canvasRef.nativeElement.style.cursor=t?"pointer":"default",this.draw()),this.hoverTip.set(t?this.tipFromEvent(e,t):null)}tipFromEvent(e,t){let n=this.scrollRef.nativeElement,o=n.getBoundingClientRect(),c=Math.min(280,Math.max(180,o.width-24)),g=e.clientX-o.left,u=e.clientY-o.top,O=Math.max(12,Math.min(g+14,o.width-c-12)),p=n.scrollTop+Math.max(12,Math.min(u+14,o.height-128)),f=t.commit;return{x:O,y:p,subject:f.subject,shortHash:f.shortHash,author:f.author,date:f.date,refs:[...f.tags,...f.branches].join(", "),isMerge:f.isMerge}}onMouseLeave(){this.hoverRow!==-1&&(this.hoverRow=-1,this.hoverTip.set(null),this.canvasRef.nativeElement.style.cursor="default",this.draw())}nodeFromEvent(e){let t=this.scrollRef?.nativeElement,n=t?.getBoundingClientRect();if(!n)return;let o=e.clientY-n.top+(t?.scrollTop??0),c=Math.floor((o-le)/D);return this.nodes[c]}readTheme(e){let t=getComputedStyle(e);return{accent:this.css(t,"--accent","#4f46e5"),accentSoft:this.css(t,"--accent-soft","#eef2ff"),guide:this.css(t,"--graph-guide","rgba(148, 163, 184, 0.28)"),nodeRing:this.css(t,"--graph-node-ring","#ffffff"),rowAlt:this.css(t,"--graph-row-alt","rgba(15, 23, 42, 0.025)"),rowHover:this.css(t,"--graph-row-hover","rgba(79, 70, 229, 0.08)"),rowSelected:this.css(t,"--graph-row-selected","rgba(79, 70, 229, 0.14)"),shadow:this.css(t,"--graph-shadow","rgba(15, 23, 42, 0.12)"),surface:this.css(t,"--bg-panel","#ffffff"),warning:this.css(t,"--warning","#d97706"),warningSoft:"rgba(217, 119, 6, 0.15)"}}css(e,t,n){return e.getPropertyValue(t).trim()||n}roundRect(e,t,n,o,c,g){let u=Math.min(g,o/2,c/2);e.beginPath(),e.moveTo(t+u,n),e.arcTo(t+o,n,t+o,n+c,u),e.arcTo(t+o,n+c,t,n+c,u),e.arcTo(t,n+c,t,n,u),e.arcTo(t,n,t+o,n,u),e.closePath()}static \u0275fac=function(t){return new(t||i)};static \u0275cmp=L({type:i,selectors:[["app-commit-graph"]],viewQuery:function(t,n){if(t&1&&(te(jt,7),te(Gt,7)),t&2){let o;ne(o=ie())&&(n.canvasRef=o.first),ne(o=ie())&&(n.scrollRef=o.first)}},hostBindings:function(t,n){t&1&&w("resize",function(){return n.onResize()},pe)},decls:15,vars:6,consts:[["scroll",""],["canvas",""],[1,"header"],[1,"title"],[1,"hint"],["aria-hidden","true",1,"legend"],["class","swatch",3,"background",4,"ngFor","ngForOf"],[1,"scroll"],[1,"phantom"],["aria-label","Commit graph","role","img"],["class","graph-tooltip",3,"left","top",4,"ngIf"],["class","empty",4,"ngIf"],[1,"swatch"],[1,"graph-tooltip"],[1,"tip-top"],["class","merge",4,"ngIf"],[1,"tip-subject"],[1,"tip-meta"],["class","tip-refs",4,"ngIf"],[1,"merge"],[1,"tip-refs"],[1,"empty"]],template:function(t,n){t&1&&(s(0,"div",2)(1,"div",3)(2,"span"),l(3,"Graph"),r(),s(4,"span",4),l(5),r()(),s(6,"div",5),v(7,Bt,1,2,"span",6),r()(),s(8,"div",7,0),b(10,"div",8)(11,"canvas",9,1),v(13,Yt,14,13,"div",10)(14,Qt,2,0,"div",11),r()),t&2&&(a(5),h(n.graphSummary()),a(2),d("ngForOf",n.legendColors),a(3),J("height",n.contentHeight(),"px"),a(3),d("ngIf",n.hoverTip()),a(),d("ngIf",!n.state.commits().length&&!n.state.loading()))},dependencies:[H,Q,N,Z],styles:['[_nghost-%COMP%]{display:flex;flex-direction:column;height:100%;min-height:0;background:transparent}.header[_ngcontent-%COMP%]{display:flex;align-items:center;justify-content:space-between;gap:.75rem;padding:.65rem .85rem;border-bottom:1px solid var(--border-soft);font-size:12px;color:var(--fg-muted);background:color-mix(in oklab,var(--bg-surface) 88%,transparent)}.title[_ngcontent-%COMP%]{display:flex;flex-direction:column;gap:1px;min-width:0}.title[_ngcontent-%COMP%] > span[_ngcontent-%COMP%]:first-child{color:var(--fg-primary);font-size:13px;font-weight:600}.hint[_ngcontent-%COMP%]{white-space:nowrap}.legend[_ngcontent-%COMP%]{display:flex;gap:4px;align-items:center;flex:0 0 auto}.swatch[_ngcontent-%COMP%]{width:8px;height:8px;border-radius:999px;box-shadow:0 0 0 2px var(--bg-surface)}.scroll[_ngcontent-%COMP%]{position:relative;flex:1;overflow-y:auto;overflow-x:hidden;min-height:0;background:radial-gradient(circle at 24px 24px,var(--graph-row-alt) 0 1px,transparent 1px 100%),linear-gradient(180deg,color-mix(in oklab,var(--bg-surface) 92%,transparent),color-mix(in oklab,var(--bg-surface-2) 76%,transparent));background-size:24px 24px}.phantom[_ngcontent-%COMP%]{width:100%;pointer-events:none}canvas[_ngcontent-%COMP%]{display:block;position:absolute;top:0;left:0;pointer-events:auto;will-change:transform}.graph-tooltip[_ngcontent-%COMP%]{position:absolute;width:min(280px,calc(100% - 1.5rem));padding:.65rem .75rem;border:1px solid color-mix(in oklab,var(--accent) 34%,var(--border-soft));border-radius:var(--radius-md);background:color-mix(in oklab,var(--bg-elevated) 96%,transparent);box-shadow:var(--shadow-lg);color:var(--fg-primary);pointer-events:none;z-index:20}.tip-top[_ngcontent-%COMP%], .tip-meta[_ngcontent-%COMP%]{display:flex;align-items:center;gap:.5rem;color:var(--fg-muted);font-size:11px}.tip-top[_ngcontent-%COMP%]{justify-content:space-between;margin-bottom:.35rem}.tip-top[_ngcontent-%COMP%] code[_ngcontent-%COMP%]{font-family:var(--font-mono, monospace);color:var(--accent)}.merge[_ngcontent-%COMP%]{padding:1px 6px;border-radius:999px;background:color-mix(in oklab,var(--accent) 15%,transparent);color:var(--accent);font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.04em}.tip-subject[_ngcontent-%COMP%]{margin-bottom:.4rem;font-size:13px;font-weight:600;line-height:1.35;overflow-wrap:anywhere}.tip-meta[_ngcontent-%COMP%]{flex-wrap:wrap;line-height:1.35}.tip-meta[_ngcontent-%COMP%] span[_ngcontent-%COMP%]:not(:last-child):after{content:"\\2022";margin-left:.5rem;opacity:.55}.tip-refs[_ngcontent-%COMP%]{margin-top:.45rem;color:var(--accent);font-size:11px;line-height:1.35;overflow-wrap:anywhere}.empty[_ngcontent-%COMP%]{position:absolute;inset:0;display:grid;place-items:center;padding:1rem;color:var(--fg-muted);font-size:12px;text-align:center}'],changeDetection:0})};function Ut(i){return i.startsWith("origin/")||i.includes("/origin/")}function Kt(i,e){return i.length>e?`${i.slice(0,Math.max(1,e-1))}...`:i}function Xt(i,e){i&1&&b(0,"span",24)}function Jt(i,e){if(i&1&&(s(0,"span",28),l(1),r()),i&2){let t=e.$implicit;d("title",t),a(),h(t)}}function Zt(i,e){if(i&1&&(s(0,"span",29),l(1),r()),i&2){let t=e.$implicit;d("title",t),a(),h(t)}}function en(i,e){if(i&1&&(s(0,"span",25),v(1,Jt,2,2,"span",26)(2,Zt,2,2,"span",27),r()),i&2){let t=x().$implicit;a(),d("ngForOf",t.tags),a(),d("ngForOf",t.branches)}}function tn(i,e){if(i&1&&(s(0,"span",30),l(1),r()),i&2){let t=e.ngIf;a(),h(t)}}function nn(i,e){if(i&1){let t=A();s(0,"button",7),w("click",function(){let o=P(t).$implicit,c=x();return k(c.select(o))}),s(1,"span",8),b(2,"span",9),v(3,Xt,1,0,"span",10),r(),s(4,"span",11)(5,"span",12)(6,"span",13),l(7),r(),v(8,en,3,2,"span",14),r(),s(9,"span",15)(10,"span",16),l(11),r(),s(12,"span",17),l(13,"\u2022"),r(),s(14,"span",18),l(15),r(),s(16,"span",17),l(17,"\u2022"),r(),s(18,"span",19),j(19,"date"),l(20),j(21,"date"),r()(),s(22,"span",20)(23,"span",21),l(24),r(),s(25,"span",22)(26,"span",16),l(27),r(),s(28,"span"),l(29),r(),s(30,"span"),l(31),j(32,"date"),r()(),v(33,tn,2,1,"span",23),r()()()}if(i&2){let t=e.$implicit,n=e.index,o=x();W("selected",t.hash===o.selectedHash()),de("aria-current",t.hash===o.selectedHash()?"true":null),a(2),J("background",o.laneColor(t)),W("merge",t.isMerge),a(),d("ngIf",n<o.commits().length-1),a(3),d("title",t.subject),a(),h(t.subject),a(),d("ngIf",t.branches.length||t.tags.length),a(3),h(t.shortHash),a(3),d("title",t.author),a(),h(t.author),a(3),d("title",q(19,21,t.date,"MMM d, y, h:mm a")),a(2),z(" ",q(21,24,t.date,"MMM d, y, h:mm a")," "),a(4),h(t.subject),a(3),h(t.shortHash),a(2),h(t.author),a(2),h(q(32,27,t.date,"MMM d, y, h:mm a")),a(2),d("ngIf",o.refsFor(t))}}function on(i,e){i&1&&(s(0,"div",31)(1,"p"),l(2,"No commits match your filters."),r()())}var Pe=class i{state=M(G);commits=this.state.commits;selectedHash=this.state.selectedHash;refsCache=new Map;laneColorCache=new Map;trackByHash(e,t){return t.hash}select(e){this.state.selectHash(e.hash)}refsFor(e){let t=this.refsCache.get(e.hash);if(t!==void 0)return t;let n=[...e.tags,...e.branches].join(", ");return this.refsCache.set(e.hash,n),n}laneColors=["#4f46e5","#06b6d4","#f59e0b","#ef4444","#10b981","#8b5cf6"];laneColor(e){let t=this.laneColorCache.get(e.hash);if(t)return t;let n=0,o=e.branches[0]??e.parents[0]??e.hash;for(let g=0;g<o.length;g++)n=n*31+o.charCodeAt(g)>>>0;let c=this.laneColors[n%this.laneColors.length];return this.laneColorCache.set(e.hash,c),c}onKey(e){if(!this.isTyping(e.target)){if(e.key==="j")e.preventDefault(),this.state.selectByOffset(1);else if(e.key==="k")e.preventDefault(),this.state.selectByOffset(-1);else if(e.key==="g"){e.preventDefault();let t=this.state.commits();t.length&&this.state.selectHash(t[0].hash)}else if(e.key==="G"){e.preventDefault();let t=this.state.commits();t.length&&this.state.selectHash(t[t.length-1].hash)}}}isTyping(e){return e instanceof HTMLElement?["INPUT","TEXTAREA","SELECT"].includes(e.tagName)||e.isContentEditable:!1}static \u0275fac=function(t){return new(t||i)};static \u0275cmp=L({type:i,selectors:[["app-commit-list"]],hostBindings:function(t,n){t&1&&w("keydown",function(c){return n.onKey(c)},pe)},decls:13,vars:6,consts:[[1,"header"],[1,"count"],[1,"hint"],[1,"kbd"],["minBufferPx","640","maxBufferPx","1280",1,"viewport",3,"itemSize"],["class","row",3,"selected","click",4,"cdkVirtualFor","cdkVirtualForOf","cdkVirtualForTrackBy"],["class","empty",4,"ngIf"],[1,"row",3,"click"],[1,"lane"],[1,"dot"],["class","line",4,"ngIf"],[1,"content"],[1,"title-row"],[1,"subject",3,"title"],["class","badges",4,"ngIf"],[1,"meta"],[1,"hash"],[1,"dot-sep"],[1,"author",3,"title"],[1,"date",3,"title"],["aria-hidden","true",1,"hover-card"],[1,"hover-subject"],[1,"hover-meta"],["class","hover-refs",4,"ngIf"],[1,"line"],[1,"badges"],["class","badge tag",3,"title",4,"ngFor","ngForOf"],["class","badge branch",3,"title",4,"ngFor","ngForOf"],[1,"badge","tag",3,"title"],[1,"badge","branch",3,"title"],[1,"hover-refs"],[1,"empty"]],template:function(t,n){t&1&&(s(0,"div",0)(1,"span",1),l(2),r(),s(3,"span",2)(4,"kbd",3),l(5,"j"),r(),l(6,"/"),s(7,"kbd",3),l(8,"k"),r(),l(9," navigate "),r()(),s(10,"cdk-virtual-scroll-viewport",4),v(11,nn,34,30,"button",5)(12,on,3,0,"div",6),r()),t&2&&(a(2),ue("",n.commits().length," of ",n.state.total()),a(8),d("itemSize",68),a(),d("cdkVirtualForOf",n.commits())("cdkVirtualForTrackBy",n.trackByHash),a(),d("ngIf",!n.commits().length&&!n.state.loading()))},dependencies:[H,Q,N,pt,lt,mt,ct,Z],styles:['[_nghost-%COMP%]{display:flex;flex-direction:column;height:100%;min-height:0;background:transparent}.header[_ngcontent-%COMP%]{display:flex;align-items:center;justify-content:space-between;padding:.6rem .85rem;border-bottom:1px solid var(--border-soft);font-size:12px;color:var(--fg-muted);background:color-mix(in oklab,var(--bg-surface) 92%,transparent)}.viewport[_ngcontent-%COMP%]{flex:1;min-height:0}.row[_ngcontent-%COMP%]{position:relative;display:grid;grid-template-columns:24px minmax(0,1fr);gap:.5rem;align-items:center;width:100%;height:68px;padding:.45rem .7rem;background:transparent;border:0;border-bottom:1px solid var(--border-soft);border-left:3px solid transparent;color:inherit;text-align:left;cursor:pointer;transition:background .1s,border-color .1s,transform 80ms}.row[_ngcontent-%COMP%]:hover{background:color-mix(in oklab,var(--bg-hover) 70%,transparent);border-left-color:color-mix(in oklab,var(--accent) 48%,transparent);z-index:3}.row.selected[_ngcontent-%COMP%]{background:linear-gradient(90deg,color-mix(in oklab,var(--accent) 18%,transparent),transparent 70%);border-left-color:var(--accent)}.row.selected[_ngcontent-%COMP%] .subject[_ngcontent-%COMP%]{color:var(--fg-primary)}.lane[_ngcontent-%COMP%]{position:relative;width:24px;height:100%;display:flex;align-items:center;justify-content:center}.dot[_ngcontent-%COMP%]{width:9px;height:9px;border-radius:50%;background:var(--accent);box-shadow:0 0 0 2px var(--bg-surface);z-index:1}.dot.merge[_ngcontent-%COMP%]{background:transparent;border:2px solid var(--accent)}.line[_ngcontent-%COMP%]{position:absolute;top:50%;left:50%;width:2px;height:50%;background:var(--border-strong);transform:translate(-50%)}.content[_ngcontent-%COMP%]{display:flex;flex-direction:column;min-width:0;gap:4px}.title-row[_ngcontent-%COMP%]{display:flex;align-items:center;min-width:0;gap:.5rem}.subject[_ngcontent-%COMP%]{flex:1 1 auto;min-width:0;font-weight:500;font-size:13px;color:var(--fg-primary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.meta[_ngcontent-%COMP%]{display:flex;gap:6px;font-size:11px;color:var(--fg-muted);align-items:center;min-width:0;overflow:hidden;white-space:nowrap}.hash[_ngcontent-%COMP%]{flex:0 0 auto;font-family:var(--font-mono)}.author[_ngcontent-%COMP%], .date[_ngcontent-%COMP%]{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.author[_ngcontent-%COMP%]{flex:0 1 auto}.date[_ngcontent-%COMP%]{flex:1 1 auto}.dot-sep[_ngcontent-%COMP%]{opacity:.5}.badges[_ngcontent-%COMP%]{display:inline-flex;gap:4px;flex:0 1 auto;flex-wrap:nowrap;justify-content:flex-end;min-width:0;max-width:min(48%,180px);overflow:hidden}.badge[_ngcontent-%COMP%]{flex:0 1 auto;font-size:10px;font-weight:600;padding:2px 7px;border-radius:999px;letter-spacing:.02em;border:1px solid transparent;min-width:0;max-width:132px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.badge.tag[_ngcontent-%COMP%]{background:#d9770626;color:var(--warning);border-color:color-mix(in oklab,var(--warning) 24%,transparent)}.badge.branch[_ngcontent-%COMP%]{background:var(--accent-soft);color:var(--accent);border-color:color-mix(in oklab,var(--accent) 24%,transparent)}.hover-card[_ngcontent-%COMP%]{position:absolute;left:42px;right:10px;top:calc(100% - 8px);display:flex;flex-direction:column;gap:.35rem;padding:.65rem .75rem;border:1px solid color-mix(in oklab,var(--accent) 28%,var(--border-soft));border-radius:var(--radius-md);background:color-mix(in oklab,var(--bg-elevated) 96%,transparent);box-shadow:var(--shadow-lg);color:var(--fg-primary);opacity:0;pointer-events:none;transform:translateY(-4px);transition:opacity .1s ease,transform .1s ease,visibility .1s;visibility:hidden;z-index:12}.row[_ngcontent-%COMP%]:hover .hover-card[_ngcontent-%COMP%], .row[_ngcontent-%COMP%]:focus-visible .hover-card[_ngcontent-%COMP%]{opacity:1;transform:translateY(0);visibility:visible}.hover-subject[_ngcontent-%COMP%]{font-size:13px;font-weight:600;line-height:1.35;white-space:normal;overflow-wrap:anywhere}.hover-meta[_ngcontent-%COMP%], .hover-refs[_ngcontent-%COMP%]{display:flex;gap:.45rem;flex-wrap:wrap;font-size:11px;line-height:1.35;color:var(--fg-muted)}.hover-meta[_ngcontent-%COMP%] > span[_ngcontent-%COMP%]:not(:last-child):after{content:"\\2022";margin-left:.45rem;opacity:.55}.hover-refs[_ngcontent-%COMP%]{color:var(--accent);overflow-wrap:anywhere}.empty[_ngcontent-%COMP%]{padding:2rem 1rem;text-align:center;color:var(--fg-muted)}'],changeDetection:0})};var ke=class i{http=M(xe);base="/api";cache=new _e(50);list(e={}){let t=new Xe;for(let[n,o]of Object.entries(e))o&&(t=t.set(n,String(o)));return this.cache.get(`groups:${t.toString()}`,()=>this.http.get(`${this.base}/groups`,{params:t}),be.VOLATILE)}invalidate(){this.cache.clear()}static \u0275fac=function(t){return new(t||i)};static \u0275prov=me({token:i,factory:i.\u0275fac,providedIn:"root"})};function rn(i,e){if(i&1&&(s(0,"span",7),l(1),r()),i&2){let t=e.ngIf;a(),z("",t," groups")}}function an(i,e){i&1&&(s(0,"div",8),l(1,"Loading groups\u2026"),r())}function sn(i,e){if(i&1&&(s(0,"div",9),l(1),r()),i&2){let t=e.ngIf;a(),h(t)}}function ln(i,e){i&1&&(s(0,"div",8),l(1," No groups detected. Try the flat view. "),r())}function cn(i,e){if(i&1&&(s(0,"span",18),l(1),r()),i&2){let t=x().$implicit;a(),z("#",t.prNumber)}}function mn(i,e){if(i&1){let t=A();s(0,"li",21),w("click",function(){let o=P(t).$implicit,c=x(3);return k(c.state.selectHash(o))}),s(1,"code",22),l(2),r(),s(3,"span",23),l(4),r()()}if(i&2){let t=e.$implicit,n=x(3);W("selected",t===n.state.selectedHash()),a(2),h(n.shortHash(t)),a(2),h(n.subjectFor(t))}}function pn(i,e){if(i&1&&(s(0,"ul",19),v(1,mn,5,4,"li",20),r()),i&2){let t=x().$implicit;a(),d("ngForOf",t.commits)}}function dn(i,e){if(i&1){let t=A();s(0,"li",10)(1,"button",11),w("click",function(){let o=P(t).$implicit,c=x();return k(c.toggle(o.id))}),s(2,"span",12),l(3,"\u25B8"),r(),s(4,"span",13),l(5),r(),v(6,cn,2,1,"span",14),s(7,"span",15),l(8),r(),s(9,"span",16),l(10),r()(),v(11,pn,2,1,"ul",17),r()}if(i&2){let t=e.$implicit,n=x();W("expanded",n.isExpanded(t.id)),a(2),W("open",n.isExpanded(t.id)),a(2),Qe("src-"+t.source),a(),h(n.sourceLabel(t.source)),a(),d("ngIf",t.prNumber),a(2),h(t.title),a(2),h(t.commits.length),a(),d("ngIf",n.isExpanded(t.id))}}var Se=class i{state=M(G);groupsApi=M(ke);groups=S(null);loading=S(!1);error=S(null);expanded=S(new Set);subjectMap=K(()=>{let e=new Map;for(let t of this.state.commits())e.set(t.hash,t.subject);return e});constructor(){Y(()=>{let e=this.state.filters();this.load(e.since,e.until,e.author,e.branch)})}isExpanded(e){return this.expanded().has(e)}toggle(e){let t=new Set(this.expanded());t.has(e)?t.delete(e):t.add(e),this.expanded.set(t)}shortHash(e){return e.slice(0,7)}subjectFor(e){return this.subjectMap().get(e)??e.slice(0,7)}sourceLabel(e){switch(e){case"merge":return"PR";case"squash":return"PR (sq)";case"conventional":return"Feat";case"standalone":return"commit"}}load(e,t,n,o){this.loading.set(!0),this.error.set(null),this.groupsApi.list({since:e,until:t,author:n,branch:o}).subscribe({next:c=>{this.groups.set(c),this.loading.set(!1);let g=c.find(u=>u.prNumber);g&&this.expanded.set(new Set([g.id]))},error:c=>{this.error.set(this.errMsg(c)),this.loading.set(!1)}})}errMsg(e){if(e&&typeof e=="object"&&"error"in e){let t=e.error;if(t?.error)return t.error}return e instanceof Error?e.message:"Failed to load groups"}static \u0275fac=function(t){return new(t||i)};static \u0275cmp=L({type:i,selectors:[["app-grouped-list"]],decls:9,vars:5,consts:[[1,"head"],[1,"title"],["class","meta",4,"ngIf"],["class","empty",4,"ngIf"],["class","empty error",4,"ngIf"],[1,"groups"],["class","group",3,"expanded",4,"ngFor","ngForOf"],[1,"meta"],[1,"empty"],[1,"empty","error"],[1,"group"],[1,"group-head",3,"click"],[1,"caret"],[1,"badge"],["class","pr",4,"ngIf"],[1,"g-title"],[1,"count"],["class","commits",4,"ngIf"],[1,"pr"],[1,"commits"],["class","commit",3,"selected","click",4,"ngFor","ngForOf"],[1,"commit",3,"click"],[1,"hash"],[1,"subject"]],template:function(t,n){if(t&1&&(s(0,"div",0)(1,"span",1),l(2,"PR / feature groups"),r(),v(3,rn,2,1,"span",2),r(),v(4,an,2,0,"div",3)(5,sn,2,1,"div",4)(6,ln,2,0,"div",3),s(7,"ul",5),v(8,dn,12,11,"li",6),r()),t&2){let o,c;a(3),d("ngIf",(o=n.groups())==null?null:o.length),a(),d("ngIf",n.loading()),a(),d("ngIf",n.error()),a(),d("ngIf",!n.loading()&&!n.error()&&(((c=n.groups())==null?null:c.length)??0)===0),a(2),d("ngForOf",n.groups())}},dependencies:[H,Q,N],styles:["[_nghost-%COMP%]{display:flex;flex-direction:column;height:100%;overflow:hidden}.head[_ngcontent-%COMP%]{display:flex;justify-content:space-between;padding:.6rem .85rem;border-bottom:1px solid var(--border-soft);background:color-mix(in oklab,var(--bg-surface) 92%,transparent)}.title[_ngcontent-%COMP%]{font-weight:600;font-size:13px}.meta[_ngcontent-%COMP%]{font-size:11px;color:var(--fg-muted)}.empty[_ngcontent-%COMP%]{padding:1rem .85rem;color:var(--fg-muted);font-size:12px}.empty.error[_ngcontent-%COMP%]{color:var(--danger)}.groups[_ngcontent-%COMP%]{list-style:none;margin:0;padding:0;overflow-y:auto;flex:1}.group[_ngcontent-%COMP%]{border-bottom:1px solid var(--border-soft);transition:background .12s}.group.expanded[_ngcontent-%COMP%]{background:color-mix(in oklab,var(--accent) 5%,transparent)}.group-head[_ngcontent-%COMP%]{width:100%;display:flex;align-items:center;gap:.5rem;padding:.58rem .85rem;background:transparent;border:0;color:var(--fg-primary);cursor:pointer;text-align:left}.group-head[_ngcontent-%COMP%]:hover{background:color-mix(in oklab,var(--bg-hover) 72%,transparent)}.caret[_ngcontent-%COMP%]{display:inline-block;width:10px;transition:transform .15s ease;color:var(--fg-muted)}.caret.open[_ngcontent-%COMP%]{transform:rotate(90deg)}.badge[_ngcontent-%COMP%]{font-size:10px;letter-spacing:.04em;padding:1px 6px;border-radius:999px;background:var(--bg-surface-2);color:var(--fg-secondary);text-transform:uppercase}.badge.src-merge[_ngcontent-%COMP%]{background:#6366f12e;color:var(--accent)}.badge.src-squash[_ngcontent-%COMP%]{background:#10b9812e;color:#10b981}.badge.src-conventional[_ngcontent-%COMP%]{background:#f59e0b2e;color:#d97706}.pr[_ngcontent-%COMP%]{font-size:11px;color:var(--fg-muted)}.g-title[_ngcontent-%COMP%]{flex:1;font-size:13px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.count[_ngcontent-%COMP%]{font-size:11px;color:var(--fg-muted);background:var(--bg-surface-2);border:1px solid var(--border-soft);padding:1px 6px;border-radius:999px}.commits[_ngcontent-%COMP%]{list-style:none;margin:0;padding:0 0 .4rem;background:color-mix(in oklab,var(--bg-surface-2) 72%,transparent)}.commit[_ngcontent-%COMP%]{display:flex;gap:.6rem;align-items:center;padding:.34rem .85rem .34rem 2rem;cursor:pointer;font-size:12px}.commit[_ngcontent-%COMP%]:hover{background:var(--bg-hover)}.commit.selected[_ngcontent-%COMP%]{background:color-mix(in oklab,var(--accent) 18%,transparent);box-shadow:inset 3px 0 0 var(--accent)}.commit[_ngcontent-%COMP%] .hash[_ngcontent-%COMP%]{font-family:var(--font-mono, monospace);font-size:11px;color:var(--fg-muted)}.commit[_ngcontent-%COMP%] .subject[_ngcontent-%COMP%]{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}"],changeDetection:0})};function gn(i,e){i&1&&(ge(0),b(1,"app-grouped-list"),fe())}function fn(i,e){i&1&&b(0,"app-commit-list")}var ut=class i{state=M(G);static \u0275fac=function(t){return new(t||i)};static \u0275cmp=L({type:i,selectors:[["app-home-shell"]],decls:11,vars:2,consts:[["flat",""],[1,"layout"],[1,"pane","graph"],[1,"pane","list"],[1,"pane-shell"],[4,"ngIf","ngIfElse"],[1,"pane","detail"],[1,"pane-shell","detail-shell"]],template:function(t,n){if(t&1&&(s(0,"main",1)(1,"aside",2),b(2,"app-commit-graph"),r(),s(3,"section",3)(4,"div",4),v(5,gn,2,0,"ng-container",5)(6,fn,1,0,"ng-template",null,0,ve),r()(),s(8,"section",6)(9,"div",7),b(10,"app-commit-detail"),r()()()),t&2){let o=he(7);a(5),d("ngIf",n.state.viewMode()==="grouped")("ngIfElse",o)}},dependencies:[H,N,Oe,Pe,we,Se],styles:["[_nghost-%COMP%]{display:block;flex:1;min-height:0}.layout[_ngcontent-%COMP%]{height:100%;display:grid;grid-template-columns:minmax(240px,320px) minmax(340px,430px) minmax(0,1fr);gap:.75rem;padding:.75rem;min-height:0;background:transparent}.pane[_ngcontent-%COMP%]{min-width:0;min-height:0;overflow:hidden;border-radius:var(--radius-lg);background:var(--bg-panel);border:1px solid color-mix(in oklab,var(--border-soft) 86%,transparent);box-shadow:var(--shadow-md)}.pane.graph[_ngcontent-%COMP%]{background:color-mix(in oklab,var(--bg-panel) 86%,transparent)}.pane-shell[_ngcontent-%COMP%]{height:100%;min-height:0;overflow:hidden;border-radius:inherit}.detail-shell[_ngcontent-%COMP%]{background:color-mix(in oklab,var(--bg-surface) 82%,transparent)}@media (max-width: 1100px){.layout[_ngcontent-%COMP%]{grid-template-columns:320px 1fr}.pane.graph[_ngcontent-%COMP%]{display:none}}@media (max-width: 720px){.layout[_ngcontent-%COMP%]{grid-template-columns:1fr}.pane.list[_ngcontent-%COMP%]{display:none}}"],changeDetection:0})};export{ut as HomeShellComponent};
@@ -1 +1 @@
1
- import{$ as a,Qb as v,Rb as l,Sb as m,U as f,W as h,ja as y,ka as b,l as g,la as d,oa as i}from"./chunk-OBPR2XVH.js";function U(n,e){let t=e?.injector??a(y),s=new g(1),u=m(()=>{let r;try{r=n()}catch(c){v(()=>s.error(c));return}v(()=>s.next(r))},{injector:t,manualCleanup:!0});return t.get(d).onDestroy(()=>{u.destroy(),s.complete()}),s.asObservable()}function L(n,e){let s=!e?.manualCleanup?e?.injector?.get(d)??a(d):null,u=w(e?.equal),r;e?.requireSync?r=i({kind:0},{equal:u}):r=i({kind:1,value:e?.initialValue},{equal:u});let c,p=n.subscribe({next:o=>r.set({kind:1,value:o}),error:o=>{r.set({kind:2,error:o}),c?.()},complete:()=>{c?.()}});if(e?.requireSync&&r().kind===0)throw new f(601,!1);return c=s?.onDestroy(p.unsubscribe.bind(p)),l(()=>{let o=r();switch(o.kind){case 1:return o.value;case 2:throw o.error;case 0:throw new f(601,!1)}},{equal:e?.equal})}function w(n=Object.is){return(e,t)=>e.kind===1&&t.kind===1&&n(e.value,t.value)}var k="ghui:theme",D=class n{doc=a(b);mediaQuery=null;preference=i(this.read());systemDark=i(!1);resolved=l(()=>{let e=this.preference();return e==="system"?this.systemDark()?"dark":"light":e});constructor(){typeof window<"u"&&window.matchMedia&&(this.mediaQuery=window.matchMedia("(prefers-color-scheme: dark)"),this.systemDark.set(this.mediaQuery.matches),this.mediaQuery.addEventListener("change",e=>this.systemDark.set(e.matches))),m(()=>{let e=this.resolved(),t=this.doc.documentElement;t.classList.toggle("dark",e==="dark"),t.dataset.theme=e,this.doc.body?.classList?.toggle("dark",e==="dark")})}setPreference(e){this.preference.set(e);try{localStorage.setItem(k,e)}catch{}}cycle(){let t=this.resolved()==="light"?"dark":"light";this.setPreference(t)}read(){try{let e=localStorage.getItem(k);if(e==="light"||e==="dark"||e==="system")return e}catch{}return"system"}static \u0275fac=function(t){return new(t||n)};static \u0275prov=h({token:n,factory:n.\u0275fac,providedIn:"root"})};export{U as a,L as b,D as c};
1
+ import{$ as a,U as f,W as h,bc as v,cc as l,dc as m,ja as y,ka as b,l as g,la as d,oa as i}from"./chunk-UM7ESEXJ.js";function U(n,e){let t=e?.injector??a(y),s=new g(1),u=m(()=>{let r;try{r=n()}catch(c){v(()=>s.error(c));return}v(()=>s.next(r))},{injector:t,manualCleanup:!0});return t.get(d).onDestroy(()=>{u.destroy(),s.complete()}),s.asObservable()}function L(n,e){let s=!e?.manualCleanup?e?.injector?.get(d)??a(d):null,u=w(e?.equal),r;e?.requireSync?r=i({kind:0},{equal:u}):r=i({kind:1,value:e?.initialValue},{equal:u});let c,p=n.subscribe({next:o=>r.set({kind:1,value:o}),error:o=>{r.set({kind:2,error:o}),c?.()},complete:()=>{c?.()}});if(e?.requireSync&&r().kind===0)throw new f(601,!1);return c=s?.onDestroy(p.unsubscribe.bind(p)),l(()=>{let o=r();switch(o.kind){case 1:return o.value;case 2:throw o.error;case 0:throw new f(601,!1)}},{equal:e?.equal})}function w(n=Object.is){return(e,t)=>e.kind===1&&t.kind===1&&n(e.value,t.value)}var k="ghui:theme",D=class n{doc=a(b);mediaQuery=null;preference=i(this.read());systemDark=i(!1);resolved=l(()=>{let e=this.preference();return e==="system"?this.systemDark()?"dark":"light":e});constructor(){typeof window<"u"&&window.matchMedia&&(this.mediaQuery=window.matchMedia("(prefers-color-scheme: dark)"),this.systemDark.set(this.mediaQuery.matches),this.mediaQuery.addEventListener("change",e=>this.systemDark.set(e.matches))),m(()=>{let e=this.resolved(),t=this.doc.documentElement;t.classList.toggle("dark",e==="dark"),t.dataset.theme=e,this.doc.body?.classList?.toggle("dark",e==="dark")})}setPreference(e){this.preference.set(e);try{localStorage.setItem(k,e)}catch{}}cycle(){let t=this.resolved()==="light"?"dark":"light";this.setPreference(t)}read(){try{let e=localStorage.getItem(k);if(e==="light"||e==="dark"||e==="system")return e}catch{}return"system"}static \u0275fac=function(t){return new(t||n)};static \u0275prov=h({token:n,factory:n.\u0275fac,providedIn:"root"})};export{U as a,L as b,D as c};