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.
- package/CHANGELOG.md +36 -0
- package/CODE_OF_CONDUCT.md +53 -0
- package/CONTRIBUTING.md +132 -0
- package/LICENSE +21 -0
- package/README.md +75 -15
- package/SECURITY.md +51 -0
- package/build/frontend/chunk-6DL2ZTKG.js +1 -0
- package/build/frontend/chunk-ACWXPHRR.js +1 -0
- package/build/frontend/{chunk-CFCRPNUQ.js → chunk-F6HZ5OVS.js} +1 -1
- package/build/frontend/{chunk-UHWTLUOW.js → chunk-GHSRMAIR.js} +1 -1
- package/build/frontend/chunk-J5ZVP6TN.js +9 -0
- package/build/frontend/chunk-PRY6VAFG.js +1 -0
- package/build/frontend/chunk-PXNKNORB.js +1 -0
- package/build/frontend/chunk-S56XRIZ7.js +9 -0
- package/build/frontend/chunk-STUSEB5P.js +1 -0
- package/build/frontend/chunk-UM7ESEXJ.js +5 -0
- package/build/frontend/chunk-VGAHCEAA.js +1 -0
- package/build/frontend/chunk-XLGWMKXV.js +1 -0
- package/build/frontend/chunk-Y6GOUMAT.js +1 -0
- package/build/frontend/index.html +2 -2
- package/build/frontend/main-W5NQX3FE.js +1 -0
- package/build/frontend/styles-RQGRQK4Z.css +1 -0
- package/dist/backend/aggregations.d.ts +1 -0
- package/dist/backend/aggregations.d.ts.map +1 -0
- package/dist/backend/annotations.d.ts +10 -1
- package/dist/backend/annotations.d.ts.map +1 -0
- package/dist/backend/annotations.js +84 -9
- package/dist/backend/annotations.js.map +1 -1
- package/dist/backend/breakage.d.ts +70 -0
- package/dist/backend/breakage.d.ts.map +1 -0
- package/dist/backend/breakage.js +235 -0
- package/dist/backend/breakage.js.map +1 -0
- package/dist/backend/cache/sqliteIndex.d.ts +2 -1
- package/dist/backend/cache/sqliteIndex.d.ts.map +1 -0
- package/dist/backend/cache/sqliteIndex.js +36 -23
- package/dist/backend/cache/sqliteIndex.js.map +1 -1
- package/dist/backend/dev-server.d.ts +1 -0
- package/dist/backend/dev-server.d.ts.map +1 -0
- package/dist/backend/gitService.d.ts +15 -6
- package/dist/backend/gitService.d.ts.map +1 -0
- package/dist/backend/gitService.js +148 -45
- package/dist/backend/gitService.js.map +1 -1
- package/dist/backend/grouping/prGrouping.d.ts +1 -0
- package/dist/backend/grouping/prGrouping.d.ts.map +1 -0
- package/dist/backend/grouping/prGrouping.js +41 -19
- package/dist/backend/grouping/prGrouping.js.map +1 -1
- package/dist/backend/impact.d.ts +4 -1
- package/dist/backend/impact.d.ts.map +1 -0
- package/dist/backend/impact.js +30 -16
- package/dist/backend/impact.js.map +1 -1
- package/dist/backend/insights.d.ts +1 -0
- package/dist/backend/insights.d.ts.map +1 -0
- package/dist/backend/insights.js +8 -3
- package/dist/backend/insights.js.map +1 -1
- package/dist/backend/llm/anthropicProvider.d.ts +2 -0
- package/dist/backend/llm/anthropicProvider.d.ts.map +1 -0
- package/dist/backend/llm/anthropicProvider.js +10 -4
- package/dist/backend/llm/anthropicProvider.js.map +1 -1
- package/dist/backend/llm/heuristicProvider.d.ts +2 -0
- package/dist/backend/llm/heuristicProvider.d.ts.map +1 -0
- package/dist/backend/llm/heuristicProvider.js +54 -4
- package/dist/backend/llm/heuristicProvider.js.map +1 -1
- package/dist/backend/llm/index.d.ts +7 -0
- package/dist/backend/llm/index.d.ts.map +1 -0
- package/dist/backend/llm/index.js +15 -5
- package/dist/backend/llm/index.js.map +1 -1
- package/dist/backend/llm/openaiProvider.d.ts +3 -0
- package/dist/backend/llm/openaiProvider.d.ts.map +1 -0
- package/dist/backend/llm/openaiProvider.js +29 -4
- package/dist/backend/llm/openaiProvider.js.map +1 -1
- package/dist/backend/llm/types.d.ts +3 -1
- package/dist/backend/llm/types.d.ts.map +1 -0
- package/dist/backend/presets.d.ts +1 -0
- package/dist/backend/presets.d.ts.map +1 -0
- package/dist/backend/search/datePhrase.d.ts +1 -0
- package/dist/backend/search/datePhrase.d.ts.map +1 -0
- package/dist/backend/search/nlSearch.d.ts +1 -0
- package/dist/backend/search/nlSearch.d.ts.map +1 -0
- package/dist/backend/server.d.ts +1 -0
- package/dist/backend/server.d.ts.map +1 -0
- package/dist/backend/server.js +100 -26
- package/dist/backend/server.js.map +1 -1
- package/dist/backend/snapshot.d.ts +4 -1
- package/dist/backend/snapshot.d.ts.map +1 -0
- package/dist/backend/snapshot.js +14 -9
- package/dist/backend/snapshot.js.map +1 -1
- package/dist/cli.d.ts +1 -0
- package/dist/cli.d.ts.map +1 -0
- package/docs/API.md +70 -0
- package/docs/architecture.md +85 -0
- package/docs/configuration.md +54 -0
- package/docs/troubleshooting.md +85 -0
- package/package.json +44 -8
- package/build/frontend/chunk-4JCUQ6Y5.js +0 -1
- package/build/frontend/chunk-6VRZZB3T.js +0 -1
- package/build/frontend/chunk-EZUFC4CQ.js +0 -1
- package/build/frontend/chunk-GRG6HTLW.js +0 -9
- package/build/frontend/chunk-I4VBIHH2.js +0 -1
- package/build/frontend/chunk-OBPR2XVH.js +0 -5
- package/build/frontend/chunk-QNEGGJHP.js +0 -2
- package/build/frontend/chunk-UIWFTWLF.js +0 -1
- package/build/frontend/chunk-UNROVCUZ.js +0 -7
- package/build/frontend/chunk-UWM4ZZWH.js +0 -1
- package/build/frontend/chunk-VHEMN3MU.js +0 -1
- package/build/frontend/main-SQFBVJA6.js +0 -1
- 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.
|
package/CONTRIBUTING.md
ADDED
|
@@ -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
|
-
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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 (
|
|
224
|
+
# Anthropic (uses Claude Sonnet 4 by default)
|
|
179
225
|
export ANTHROPIC_API_KEY=sk-ant-...
|
|
180
226
|
|
|
181
|
-
# Or OpenAI (uses
|
|
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
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
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,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}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,
|
|
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};
|