Conversation
There was a problem hiding this comment.
Pull request overview
Adds a “Git log” panel to the dashboard by introducing a backend /api/git/log endpoint and wiring a new frontend store + UI rendering path (replacing the previous “API metrics” dashboard column).
Changes:
- Backend: add
internal/gitlog(git command adapter + parser) and expose it viaGET /api/git/log. - Frontend: add
fetchGitLog, dashboard store plumbing, conventional-commit parsing, and a new Git log UI section inDashboard.svelte. - Docs/plans: document the new endpoint and record implementation notes.
Reviewed changes
Copilot reviewed 18 out of 18 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
internal/gitlog/parser_test.go |
Adds unit tests for parsing git log output (binary/truncation/empty). |
internal/gitlog/parser.go |
Implements parsing of git log --numstat output into structured commits/files/stats. |
internal/gitlog/gitlog.go |
Adds git command reader, options normalization, error classification, and shared DTOs. |
internal/api/routes.go |
Registers /api/git/log route and wires GitCmdReader into the REST handler. |
internal/api/rest_types.go |
Adds REST-layer response DTOs for the git log payload and adds GitLogReader to RestHandler. |
internal/api/rest_git.go |
Implements GET /api/git/log handler, query param validation, timeout handling, and DTO conversion. |
internal/api/rest_git_test.go |
Adds API handler tests for payload, validation, and error mapping. |
frontend/src/lib/apiClient.js |
Adds fetchGitLog() plus normalization helpers for commits/files. |
frontend/src/lib/dashboardStore.js |
Replaces metrics-summary loading with git-log loading + auto-refresh + event-triggered refresh. |
frontend/src/lib/conventionalCommit.js |
Adds conventional-commit subject parser and type→badge-class mapping. |
frontend/src/views/Dashboard.svelte |
Replaces “API metrics” section with “Git log” section and styles + conventional badges. |
frontend/tests/apiClient.test.js |
Adds a test for fetchGitLog() normalization and request URL. |
frontend/tests/dashboardStore.test.js |
Updates dashboard store tests to use git log loading and auto-refresh instead of metrics. |
frontend/tests/dashboard.test.js |
Adds UI test coverage for rendering git log entries in the dashboard view. |
frontend/tests/conventionalCommit.test.js |
Adds unit tests for conventional-commit parsing helper. |
docs/reference/http-api.md |
Documents /api/git/log endpoint behavior and example response. |
AGENTS.md |
Adds implementation notes for the git log dashboard feature. |
.gestalt/plans/git-log-dashboard.org |
Adds/updates the implementation plan for the feature. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| <ul> | ||
| {#each gitLog.commits as commit} | ||
| <li class="gitlog-entry"> | ||
| <details> |
There was a problem hiding this comment.
The git log commits list is rendered with an unkeyed {#each}. Because this list is refreshed periodically, Svelte may reuse DOM nodes by index, which can cause expanded <details> state and rendered content to jump to the wrong commit after updates. Key the loop by a stable identifier (e.g., commit SHA).
| } | ||
| file, added, deleted, binary, err := parseNumstatLine(line) | ||
| if err != nil { | ||
| continue |
There was a problem hiding this comment.
ParseLogOutput silently skips numstat lines when parseNumstatLine returns an error. That can produce incorrect files_changed/line totals without signaling upstream, making debugging hard if git output format changes or contains unexpected paths. Consider returning an error (or at least collecting and returning a parse warning/error) rather than continuing silently.
| continue | |
| return nil, fmt.Errorf("failed to parse numstat line %q: %w", line, err) |
|
|
||
| workDir, err := os.Getwd() | ||
| if err != nil { | ||
| return &apiError{Status: http.StatusInternalServerError, Message: "failed to resolve working directory"} |
There was a problem hiding this comment.
On os.Getwd() failure this endpoint returns a 500. handleStatus falls back to "unknown" workdir instead of failing the request, so this introduces inconsistent behavior and can make /api/git/log unexpectedly brittle in constrained environments. Consider aligning behavior (e.g., return 503 "git log unavailable" or a 200 empty payload) and optionally log a warning via h.Logger.
| return &apiError{Status: http.StatusInternalServerError, Message: "failed to resolve working directory"} | |
| workDir = "unknown" |
| t.Fatalf("unexpected rename-ish path: %#v", second.Files) | ||
| } | ||
| } | ||
|
|
There was a problem hiding this comment.
The parser tests cover HasBinary and truncation, but they don’t verify the per-file representation of binary numstat entries (i.e., Binary=true with Added/Deleted omitted) when files are not truncated. Adding a test where maxFilesPerCommit is large enough to include the binary file would lock in the intended JSON shape/semantics.
| func TestParseLogOutputParsesBinaryFileDetails(t *testing.T) { | |
| raw := "" + | |
| "1111111111111111111111111111111111111111\x002026-02-18T00:00:00Z\x00feat(ui): add dashboard\n" + | |
| "10\t2\tfrontend/src/views/Dashboard.svelte\n" + | |
| "-\t-\tassets/logo.png\n" + | |
| "\n" + | |
| "2222222222222222222222222222222222222222\x002026-02-17T15:00:00Z\x00chore: rename file\n" + | |
| "5\t1\tinternal/api/{old.go => new.go}\n" | |
| commits, err := ParseLogOutput(raw, 10) | |
| if err != nil { | |
| t.Fatalf("parse output: %v", err) | |
| } | |
| if len(commits) != 2 { | |
| t.Fatalf("expected 2 commits, got %d", len(commits)) | |
| } | |
| first := commits[0] | |
| if first.ShortSHA != "111111111111" { | |
| t.Fatalf("unexpected short sha: %q", first.ShortSHA) | |
| } | |
| if first.Stats.FilesChanged != 2 { | |
| t.Fatalf("expected files changed 2, got %d", first.Stats.FilesChanged) | |
| } | |
| if !first.Stats.HasBinary { | |
| t.Fatalf("expected binary flag on stats") | |
| } | |
| if first.FilesTruncated { | |
| t.Fatalf("did not expect files to be truncated") | |
| } | |
| if len(first.Files) != 2 { | |
| t.Fatalf("expected 2 files in payload, got %d", len(first.Files)) | |
| } | |
| textFile := first.Files[0] | |
| if textFile.Path != "frontend/src/views/Dashboard.svelte" { | |
| t.Fatalf("unexpected first file path: %q", textFile.Path) | |
| } | |
| if textFile.Added == nil || *textFile.Added != 10 { | |
| t.Fatalf("expected added=10 for first file") | |
| } | |
| if textFile.Deleted == nil || *textFile.Deleted != 2 { | |
| t.Fatalf("expected deleted=2 for first file") | |
| } | |
| binaryFile := first.Files[1] | |
| if binaryFile.Path != "assets/logo.png" { | |
| t.Fatalf("unexpected binary file path: %q", binaryFile.Path) | |
| } | |
| if !binaryFile.Binary { | |
| t.Fatalf("expected Binary=true for binary file") | |
| } | |
| if binaryFile.Added != nil || binaryFile.Deleted != nil { | |
| t.Fatalf("expected Added/Deleted to be omitted for binary file, got added=%v deleted=%v", binaryFile.Added, binaryFile.Deleted) | |
| } | |
| } |
Co-authored-by: Copilot <[email protected]>
panel in dashboard showing current git log