Monorepo for Tangled tangled.org
2

Configure Feed

Select the types of activity you want to include in your feed.

appview/pulls: lazily calculate mergeCheck, resubmitCheck and branchDeleteStatus

Signed-off-by: oppiliappan <me@oppi.li>

author
oppiliappan
date (Jun 16, 2026, 2:47 PM +0100) commit 2639ba8f parent 5c97f1cc change-id sxqnpqpn
+129 -94
+4
appview/pages/pages.go
··· 1472 1472 ResubmitCheck ResubmitResult 1473 1473 BranchDeleteStatus *models.BranchDeleteStatus 1474 1474 Stack models.Stack 1475 + 1476 + // renders buttons in a pre-check state and attaches the hx-trigger="load" 1477 + // that fetches the real, checked fragment 1478 + Loading bool 1475 1479 } 1476 1480 1477 1481 func (p *Pages) PullActionsFragment(w io.Writer, params PullActionsParams) error {
+94 -13
appview/pages/templates/repo/pulls/fragments/pullActions.html
··· 2 2 {{ $lastIdx := sub (len .Pull.Submissions) 1 }} 3 3 {{ $roundNumber := .RoundNumber }} 4 4 {{ $stack := .Stack }} 5 + {{ $loading := .Loading }} 5 6 6 7 {{ $totalPulls := sub 0 1 }} 7 8 {{ $below := sub 0 1 }} ··· 22 23 {{ $isLastRound := eq $roundNumber $lastIdx }} 23 24 {{ $isSameRepoBranch := .Pull.IsBranchBased }} 24 25 {{ $isUpToDate := .ResubmitCheck.No }} 25 - <div id="actions-{{$roundNumber}}" hx-target="this" class="flex flex-wrap gap-2 relative p-2"> 26 - <button 26 + <div id="actions-{{$roundNumber}}" hx-target="this" class="{{ if .LoggedInUser }}flex flex-wrap gap-2 relative p-2{{ else }}hidden{{ end }}" 27 + {{ if $loading }} 28 + hx-get="/{{ .RepoInfo.FullName }}/pulls/{{ .Pull.PullId }}/round/{{ $roundNumber }}/actions" 29 + hx-trigger="load" 30 + hx-swap="outerHTML" 31 + {{ end }} 32 + > 33 + {{ if .LoggedInUser }} 34 + <button 27 35 hx-get="/{{ .RepoInfo.FullName }}/pulls/{{ .Pull.PullId }}/round/{{ $roundNumber }}/comment" 28 36 class="btn-flat p-2 flex items-center gap-2 no-underline hover:no-underline group"> 29 37 {{ i "message-square-plus" "w-4 h-4 inline group-[.htmx-request]:hidden" }} 30 38 {{ i "loader-circle" "w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }} 31 39 Comment 32 40 </button> 33 - {{ if .BranchDeleteStatus }} 34 - <button 41 + {{ end }} 42 + {{ if and (not $loading) .BranchDeleteStatus }} 43 + <button 35 44 hx-delete="/{{ resolve .BranchDeleteStatus.Repo.Did }}/{{ .BranchDeleteStatus.Repo.Slug }}/branches" 36 45 hx-vals='{"branch": "{{ .BranchDeleteStatus.Branch }}" }' 37 46 hx-swap="none" ··· 42 51 </button> 43 52 {{ end }} 44 53 {{ if and $isPushAllowed $isOpen $isLastRound }} 45 - <button 54 + <button 46 55 hx-post="/{{ .RepoInfo.FullName }}/pulls/{{ .Pull.PullId }}/merge" 47 56 hx-swap="none" 48 57 hx-confirm="Are you sure you want to merge pull #{{ .Pull.PullId }} into the `{{ .Pull.TargetBranch }}` branch?" 49 58 class="btn-flat p-2 flex items-center gap-2 group" 50 - {{ if $isConflicted }}disabled{{ end }} 59 + {{ if or $loading $isConflicted }}disabled{{ end }} 51 60 > 52 - {{ i "git-merge" "w-4 h-4 inline group-[.htmx-request]:hidden" }} 53 - {{ i "loader-circle" "w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }} 61 + {{ if $loading }} 62 + {{ i "loader-circle" "w-4 h-4 animate-spin" }} 63 + {{ else }} 64 + {{ i "git-merge" "w-4 h-4 inline group-[.htmx-request]:hidden" }} 65 + {{ i "loader-circle" "w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }} 66 + {{ end }} 54 67 Merge{{if $stackCount}} {{$stackCount}}{{end}} 55 68 </button> 56 69 {{ end }} ··· 67 80 hx-disabled-elt="#resubmitBtn" 68 81 class="btn-flat p-2 flex items-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed group" 69 82 70 - {{ if $isUpToDate }} 83 + {{ if $loading }} 84 + title="Checking for updates…" 85 + disabled 86 + {{ else if $isUpToDate }} 71 87 title="Update this branch to resubmit this pull request" 72 88 disabled 73 89 {{ else }} 74 90 title="Resubmit this pull request" 75 91 {{ end }} 76 92 > 77 - {{ i "rotate-ccw" "w-4 h-4 inline group-[.htmx-request]:hidden" }} 78 - {{ i "loader-circle" "w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }} 93 + {{ if $loading }} 94 + {{ i "loader-circle" "w-4 h-4 animate-spin" }} 95 + {{ else }} 96 + {{ i "rotate-ccw" "w-4 h-4 inline group-[.htmx-request]:hidden" }} 97 + {{ i "loader-circle" "w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }} 98 + {{ end }} 79 99 Resubmit 80 100 </button> 81 101 {{ end }} 82 102 83 103 {{ if and (or $isPullAuthor $isPushAllowed) $isOpen $isLastRound }} 84 - <button 104 + <button 85 105 hx-post="/{{ .RepoInfo.FullName }}/pulls/{{ .Pull.PullId }}/close" 86 106 hx-swap="none" 87 107 class="btn-flat p-2 flex items-center gap-2 group"> ··· 92 112 {{ end }} 93 113 94 114 {{ if and (or $isPullAuthor $isPushAllowed) $isClosed $isLastRound }} 95 - <button 115 + <button 96 116 hx-post="/{{ .RepoInfo.FullName }}/pulls/{{ .Pull.PullId }}/reopen" 97 117 hx-swap="none" 98 118 class="btn-flat p-2 flex items-center gap-2 group"> ··· 102 122 </button> 103 123 {{ end }} 104 124 </div> 125 + 126 + {{ if and (not $loading) $isLastRound }} 127 + <div id="mergecheck-banner" hx-swap-oob="innerHTML">{{ template "mergeCheck" . }}</div> 128 + <div id="resubmit-banner" hx-swap-oob="innerHTML">{{ template "resubmitStatus" . }}</div> 129 + {{ end }} 130 + {{ end }} 131 + 132 + {{ define "mergeCheck" }} 133 + {{ $isOpen := .Pull.State.IsOpen }} 134 + {{ if and $isOpen .MergeCheck .MergeCheck.Error }} 135 + <div class="flex items-center gap-2 text-red-600 dark:text-red-500"> 136 + {{ i "triangle-alert" "w-4 h-4" }} 137 + <span class="font-medium">{{ .MergeCheck.Error }}</span> 138 + </div> 139 + {{ else if and $isOpen .MergeCheck .MergeCheck.IsConflicted }} 140 + <details class="group/conflict"> 141 + <summary class="flex items-center justify-between cursor-pointer list-none"> 142 + <div class="flex items-center gap-2 text-red-600 dark:text-red-500"> 143 + {{ i "triangle-alert" "w-4 h-4" }} 144 + <span class="font-medium">Merge conflicts detected</span> 145 + <div class="text-sm text-gray-500 dark:text-gray-400"> 146 + <span class="group-open/conflict:hidden inline">Expand</span> 147 + <span class="hidden group-open/conflict:inline">Collapse</span> 148 + </div> 149 + </div> 150 + </summary> 151 + {{ if gt (len .MergeCheck.Conflicts) 0 }} 152 + <ul class="space-y-1 mt-2 overflow-x-auto"> 153 + {{ range .MergeCheck.Conflicts }} 154 + {{ if .Filename }} 155 + <li class="flex items-center whitespace-nowrap"> 156 + {{ i "file-warning" "inline-flex w-4 h-4 mr-1.5 text-red-600 dark:text-red-500 flex-shrink-0" }} 157 + <span class="font-mono">{{ .Filename }}</span> 158 + </li> 159 + {{ else if .Reason }} 160 + <li class="flex items-center whitespace-nowrap"> 161 + {{ i "file-warning" "w-4 h-4 mr-1.5 text-red-600 dark:text-red-500 " }} 162 + <span>{{.Reason}}</span> 163 + </li> 164 + {{ end }} 165 + {{ end }} 166 + </ul> 167 + {{ end }} 168 + </details> 169 + {{ else if and $isOpen .MergeCheck }} 170 + <div class="flex items-center gap-2 text-green-600 dark:text-green-500"> 171 + {{ i "check" "w-4 h-4" }} 172 + <span class="font-medium">No conflicts, ready to merge</span> 173 + </div> 174 + {{ end }} 175 + {{ end }} 176 + 177 + {{ define "resubmitStatus" }} 178 + {{ if .ResubmitCheck.Yes }} 179 + <div class="bg-amber-50 dark:bg-amber-900 border border-amber-500 rounded shadow-sm px-6 py-2 relative"> 180 + <div class="flex items-center gap-2 text-amber-500 dark:text-amber-300"> 181 + {{ i "triangle-alert" "w-4 h-4" }} 182 + <span class="font-medium">This branch has been updated, consider resubmitting</span> 183 + </div> 184 + </div> 185 + {{ end }} 105 186 {{ end }}
+20 -68
appview/pages/templates/repo/pulls/pull.html
··· 311 311 {{ template "submissionCommits" $ }} 312 312 {{ template "submissionPipeline" $ }} 313 313 {{ if eq $lastIdx $round }} 314 - {{ block "mergeCheck" $root }} {{ end }} 314 + <div id="mergecheck-banner"> 315 + {{ if $root.Pull.State.IsOpen }} 316 + <div class="flex items-center gap-2 text-gray-500 dark:text-gray-400"> 317 + {{ i "loader-circle" "w-4 h-4 animate-spin" }} 318 + <span>Checking mergeability…</span> 319 + </div> 320 + {{ end }} 321 + </div> 315 322 {{ end }} 316 323 </div> 317 324 </div> ··· 444 451 {{ end }} 445 452 {{ end }} 446 453 447 - {{ define "mergeCheck" }} 448 - {{ $isOpen := .Pull.State.IsOpen }} 449 - {{ if and $isOpen .MergeCheck .MergeCheck.Error }} 450 - <div class="flex items-center gap-2 text-red-600 dark:text-red-500"> 451 - {{ i "triangle-alert" "w-4 h-4" }} 452 - <span class="font-medium">{{ .MergeCheck.Error }}</span> 453 - </div> 454 - {{ else if and $isOpen .MergeCheck .MergeCheck.IsConflicted }} 455 - <details class="group/conflict"> 456 - <summary class="flex items-center justify-between cursor-pointer list-none"> 457 - <div class="flex items-center gap-2 text-red-600 dark:text-red-500"> 458 - {{ i "triangle-alert" "w-4 h-4" }} 459 - <span class="font-medium">Merge conflicts detected</span> 460 - <div class="text-sm text-gray-500 dark:text-gray-400"> 461 - <span class="group-open/conflict:hidden inline">Expand</span> 462 - <span class="hidden group-open/conflict:inline">Collapse</span> 463 - </div> 464 - </div> 465 - </summary> 466 - {{ if gt (len .MergeCheck.Conflicts) 0 }} 467 - <ul class="space-y-1 mt-2 overflow-x-auto"> 468 - {{ range .MergeCheck.Conflicts }} 469 - {{ if .Filename }} 470 - <li class="flex items-center whitespace-nowrap"> 471 - {{ i "file-warning" "inline-flex w-4 h-4 mr-1.5 text-red-600 dark:text-red-500 flex-shrink-0" }} 472 - <span class="font-mono">{{ .Filename }}</span> 473 - </li> 474 - {{ else if .Reason }} 475 - <li class="flex items-center whitespace-nowrap"> 476 - {{ i "file-warning" "w-4 h-4 mr-1.5 text-red-600 dark:text-red-500 " }} 477 - <span>{{.Reason}}</span> 478 - </li> 479 - {{ end }} 480 - {{ end }} 481 - </ul> 482 - {{ end }} 483 - </details> 484 - {{ else if and $isOpen .MergeCheck }} 485 - <div class="flex items-center gap-2 text-green-600 dark:text-green-500"> 486 - {{ i "check" "w-4 h-4" }} 487 - <span class="font-medium">No conflicts, ready to merge</span> 488 - </div> 489 - {{ end }} 490 - {{ end }} 491 - 492 454 {{ define "mergeStatus" }} 493 455 {{ if .Pull.State.IsClosed }} 494 456 <div class="bg-gray-50 dark:bg-gray-700 border border-black dark:border-gray-500 rounded shadow-sm px-6 py-2 relative"> ··· 511 473 <div class="flex items-center gap-2 text-red-500 dark:text-red-300"> 512 474 {{ i "git-pull-request-closed" "w-4 h-4" }} 513 475 <span class="font-medium">This pull has been deleted (possibly by jj abandon or jj squash)</span> 514 - </div> 515 - </div> 516 - {{ end }} 517 - {{ end }} 518 - 519 - {{ define "resubmitStatus" }} 520 - {{ if .ResubmitCheck.Yes }} 521 - <div class="bg-amber-50 dark:bg-amber-900 border border-amber-500 rounded shadow-sm px-6 py-2 relative"> 522 - <div class="flex items-center gap-2 text-amber-500 dark:text-amber-300"> 523 - {{ i "triangle-alert" "w-4 h-4" }} 524 - <span class="font-medium">This branch has been updated, consider resubmitting</span> 525 476 </div> 526 477 </div> 527 478 {{ end }} ··· 612 563 <div class="relative -ml-10"> 613 564 {{ if eq $lastIdx $item.RoundNumber }} 614 565 {{ block "mergeStatus" $root }} {{ end }} 615 - {{ block "resubmitStatus" $root }} {{ end }} 566 + <div id="resubmit-banner"></div> 616 567 {{ end }} 617 568 </div> 618 569 <div hx-include="this" class="relative -ml-10 bg-gray-50 dark:bg-gray-900"> 619 570 {{ if $root.LoggedInUser }} 620 571 <input name="subject-uri" type="hidden" value="{{ $root.Pull.AtUri }}"> 621 572 <input name="pull-round-idx" type="hidden" value="{{ $item.RoundNumber }}"> 622 - {{ template "repo/pulls/fragments/pullActions" 623 - (dict 624 - "LoggedInUser" $root.LoggedInUser 625 - "Pull" $root.Pull 626 - "RepoInfo" $root.RepoInfo 627 - "RoundNumber" $item.RoundNumber 628 - "MergeCheck" $root.MergeCheck 629 - "ResubmitCheck" $root.ResubmitCheck 630 - "BranchDeleteStatus" $root.BranchDeleteStatus 631 - "Stack" $root.Stack) }} 632 573 {{ end }} 574 + {{ template "repo/pulls/fragments/pullActions" 575 + (dict 576 + "LoggedInUser" $root.LoggedInUser 577 + "Pull" $root.Pull 578 + "RepoInfo" $root.RepoInfo 579 + "RoundNumber" $item.RoundNumber 580 + "MergeCheck" $root.MergeCheck 581 + "ResubmitCheck" $root.ResubmitCheck 582 + "BranchDeleteStatus" $root.BranchDeleteStatus 583 + "Stack" $root.Stack 584 + "Loading" (eq $lastIdx $item.RoundNumber)) }} 633 585 </div> 634 586 </details> 635 587 {{ end }}
+11 -13
appview/pulls/single.go
··· 58 58 return 59 59 } 60 60 61 - mergeCheckResponse := s.mergeCheck(r, f, pull, stack) 61 + // only the last round's buttons and banners use merge/resubmit checks 62 + isLastRound := roundNumber == pull.LastRoundNumber() 62 63 branchDeleteStatus := s.branchDeleteStatus(r, f, pull) 64 + mergeCheckResponse := types.MergeCheckResponse{} 63 65 resubmitResult := pages.Unknown 64 - if user.Did == pull.OwnerDid { 65 - resubmitResult = s.resubmitCheck(r, f, pull, stack) 66 + if isLastRound { 67 + mergeCheckResponse = s.mergeCheck(r, f, pull, stack) 68 + if user != nil && user.Did == pull.OwnerDid { 69 + resubmitResult = s.resubmitCheck(r, f, pull, stack) 70 + } 66 71 } 67 72 68 73 s.pages.PullActionsFragment(w, pages.PullActionsParams{ ··· 144 149 145 150 // can be nil if this pull is not stacked 146 151 stack, _ := r.Context().Value("stack").(models.Stack) 147 - 148 - mergeCheckResponse := s.mergeCheck(r, f, pull, stack) 149 - branchDeleteStatus := s.branchDeleteStatus(r, f, pull) 150 - resubmitResult := pages.Unknown 151 - if user != nil && user.Did == pull.OwnerDid { 152 - resubmitResult = s.resubmitCheck(r, f, pull, stack) 153 - } 154 152 155 153 m := make(map[string]models.Pipeline) 156 154 ··· 256 254 Pull: pull, 257 255 Stack: stack, 258 256 Backlinks: backlinks, 259 - BranchDeleteStatus: branchDeleteStatus, 260 - MergeCheck: mergeCheckResponse, 261 - ResubmitCheck: resubmitResult, 257 + BranchDeleteStatus: nil, 258 + MergeCheck: types.MergeCheckResponse{}, 259 + ResubmitCheck: pages.Unknown, 262 260 Pipelines: m, 263 261 Diff: diff, 264 262 DiffOpts: diffOpts,