feat(frontend): add the repository Commits tab [IFC-3150]

Merged
PA
1mo ago
+4432-107

PR 6 of 11 for IFC-3101 · ticket IFC-3150 · spec set dev/specs/ifc-3101-repo-commit-visibility/, block 3.5 (T049–T058).

Base is the epic branch pog-repo-commit-visibility-ifc-3101: it holds the generated types from PR 2 (#10566). Frontend only, no backend file touched.

image

What this adds

A Commits tab on the repository detail page (CoreGenericRepository and its kinds), following the branch selected in the header like every other tab.

  • Newest-first rows with short hash, summary, author and date, paged through DataTable inside InfiniteScroll. No total anywhere (FR-024).
  • Per-row state comes only from the response's state: Remote head, Imported, Pending import, Not on current history as text badges; a row that is both head and imported carries both (FR-005, FR-028).
  • Header: refresh button inline with Tracking <git_ref> and the Checked / Updated freshness line (FR-007), the pending count under BEHIND, and an amber notice for REWRITTEN and ORPHANED.
  • Row menu, as the ticket specifies: Copy commit hash (confirmed with a toast, or an error toast when copying fails) and View on GitHub, shown only when the repository location is a GitHub remote. The link is rebuilt from the validated owner and repo, so credentials in location never reach it.
  • Not-yet-available state (UNAVAILABLE): retried every 10 s for up to 30 attempts (about five minutes) while the reason is NOT_CLONED or TIMEOUT, then the tab asks the user to refresh. NOT_IMPLEMENTED is not retried and says the log is not available in this version. All of these stay distinct from a general error (FR-014). NOT_TRACKED and NO_REMOTE get their own empty states.
  • Tab pill styled like the Tasks tab: the pending-import count when BEHIND, 0 when IN_SYNC, nothing otherwise. Never a total; the number carries a visually hidden "pending import" suffix.

Architecture

Follows dev/knowledge/frontend/entities-structure.md:

  • api/: get-repository-commits-from-api.ts (requests pending_count on the first page only, @include(if: $isFirstPage)) and repository-commits.mappers.ts, the only place the wire shape changes (DateTime narrowed from gql.tada's unknown, edges flattened).
  • domain/: the model uses the generated enums and types directly. The use-case throws a typed RepositoryGitUnavailableError (reason, message, and the log for the header) for UNAVAILABLE; every other condition is data. Rules are pure and tested (should-retry-git-unavailable, get-pending-import-count, get-commit-web-url, get-repository-location).
  • ui/queries/: one infinite query serves both the list and the tab pill (select on the first page), so there is one cache entry and one worker read. TanStack's own error handling keeps loaded pages when a refresh fails and retries a failed next page; retry with a fixed retryDelay covers warm-up without the loading-state flicker an interval refetch would cause. refetchOnWindowFocus is off and the stale time is a minute, because every loaded page is a worker round trip.
  • ui/: RepositoryCommitsTab is the link tab and RepositoryCommitsManager the content, like RepositoryObjectsTab / RepositoryObjectsManager; display decisions live in repository-commits.view.ts with unit tests, components only render.

Shared changes:

  • DataTable gains an optional getRowId, so rows that are not Infrahub nodes need no fabricated id / __typename; selection stays node-only at the type level. Existing callers are unchanged.
  • copyTextToClipboard in shared/utils/clipboard.ts reports whether the copy worked (used by useCopyToClipboard and the row menu); CopyToClipboardButton gains a visually hidden role="status" announcement (CopiedAnnouncement); NoDataFound gains an optional title.

Where the implementation deviates from the task text (recorded on the tasks)

  • T054: Pagination cannot hide its total, so the tab uses DataTable + InfiniteScroll (the ticket text already said so).
  • T053: retries with a fixed delay on the unavailable error, capped at 30, instead of placeholderData or an interval.
  • T051/T057: NOT_IMPLEMENTED is not retried because it is permanent for a deployment; NOT_CLONED, TIMEOUT and a missing reason are.
  • T058: fragment is changelog/+ifc-3101-repository-commits-tab.added.md (towncrier orphan prefix); it describes what the tab shows once Infrahub can read commit history, and that until then the tab says the log is not available.

T094 (design canvas)

Built on the answers already encoded in T054: no commit-count badge, check time on the freshness line, a distinct notice for a rewritten ref. The fourth item, where "check remote now" sits (FR-015), stays with T072 in PR 9. T094 remains unchecked for that reason.

Docs

User-facing documentation is deliberately not in this PR: T088 (Phase 6, PR 11) owns it, and the backend still answers UNAVAILABLE / NOT_IMPLEMENTED for every repository until PR 5 lands, so the live tab shows the not-available state today.

Known cost

The tab pill reads the first page of the commit log (20 commits) on every repository page, one worker request, and retries it during a warm-up like the tab does.

Review findings declined, with reasons

  • Refresh spins and is disabled while a warm-up retries: a retry is a fetch in progress, and the cap limits it to about five minutes.
  • Memoizing onLoadMore: the React Compiler memoizes it; manual useCallback is against the repo rule.
  • Amber notice uses the same colour literals as branch-working-notice.tsx: no warning token exists yet.
  • gql.tada types DateTime as unknown (no scalars config on the client): narrowed in the mapper; configuring scalars: { DateTime: string } app-wide is a follow-up because it shifts the betterer baseline.

Out of scope

Search, filter and sort (the contract takes only limit and offset; order is fixed newest-first), Check remote now (T072, PR 9), web links for non-GitHub hosts, and the e2e test T059/T060 (PR 7).

Verification

  • pnpm test (full browser suite): 229 files, 1806 tests passed on 7ea148ec01.
  • biome ci . (workspace root), pnpm knip, betterer ci: clean, baseline unchanged (176).
  • cubic local review: clean on 7ea148ec01 before it was pushed.
  • Tried end to end against a local stack with a stand-in git-state reader (not committed), since the real worker read lands in PR 5.

🤖 Generated with Claude Code