@volter/twin-github 0.1.2 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (215) hide show
  1. package/README.md +88 -36
  2. package/client/github-mirror.css +277 -319
  3. package/client/github-mirror.d.ts +418 -0
  4. package/client/github-mirror.js +485 -0
  5. package/client/github-mirror.tsx +153 -357
  6. package/client/pulls-rest.ts +159 -0
  7. package/client/pulls-workspace.tsx +841 -0
  8. package/dist/client/github-mirror.bundle.js +239 -0
  9. package/dist/client/github-mirror.css +916 -0
  10. package/dist/client/github-mirror.d.ts +418 -0
  11. package/dist/client/github-mirror.js +485 -0
  12. package/dist/client/github-mirror.tsx +1315 -0
  13. package/dist/client/pulls-rest.d.ts +42 -0
  14. package/dist/client/pulls-rest.js +140 -0
  15. package/dist/client/pulls-rest.ts +159 -0
  16. package/dist/client/pulls-workspace.bundle.js +22 -0
  17. package/dist/client/pulls-workspace.d.ts +114 -0
  18. package/dist/client/pulls-workspace.js +418 -0
  19. package/dist/client/pulls-workspace.tsx +841 -0
  20. package/dist/src/cli.d.ts +2 -0
  21. package/dist/src/cli.js +40 -0
  22. package/dist/src/generated/graphql-sdl.gen.json +1 -0
  23. package/dist/src/generated/graphql.gen.json +1 -0
  24. package/dist/src/generated/surface.gen.json +1 -0
  25. package/dist/src/generated/ui.gen.json +1 -0
  26. package/dist/src/github-budget.d.ts +69 -0
  27. package/dist/src/github-budget.js +172 -0
  28. package/dist/src/github-capabilities.d.ts +5 -0
  29. package/dist/src/github-capabilities.js +4468 -0
  30. package/dist/src/github-conformance.d.ts +43 -0
  31. package/dist/src/github-conformance.js +76 -0
  32. package/dist/src/github-connector.d.ts +307 -0
  33. package/dist/src/github-connector.js +1398 -0
  34. package/dist/src/github-events.d.ts +41 -0
  35. package/dist/src/github-events.js +232 -0
  36. package/dist/src/github-git-http.d.ts +49 -0
  37. package/dist/src/github-git-http.js +185 -0
  38. package/dist/src/github-git-plane.d.ts +114 -0
  39. package/dist/src/github-git-plane.js +407 -0
  40. package/dist/src/github-mirror-state.d.ts +2 -0
  41. package/dist/src/github-mirror-state.js +335 -0
  42. package/dist/src/github-mirror-ui.d.ts +20 -0
  43. package/dist/src/github-mirror-ui.js +101 -0
  44. package/dist/src/github-server.d.ts +14 -0
  45. package/dist/src/github-server.js +240 -0
  46. package/dist/src/github-shared.d.ts +12 -0
  47. package/dist/src/github-shared.js +21 -0
  48. package/dist/src/github-twin.d.ts +1411 -0
  49. package/dist/src/github-twin.js +4084 -0
  50. package/dist/src/github-ui-conformance.d.ts +4 -0
  51. package/dist/src/github-ui-conformance.js +105 -0
  52. package/dist/src/github-ui-structure.d.ts +18 -0
  53. package/dist/src/github-ui-structure.js +251 -0
  54. package/dist/src/graphql-wire.d.ts +22 -0
  55. package/dist/src/graphql-wire.js +89 -0
  56. package/dist/src/index.d.ts +17 -0
  57. package/dist/src/index.js +104 -0
  58. package/dist/src/manifest.d.ts +8 -0
  59. package/dist/src/manifest.js +597 -0
  60. package/dist/src/npm-registry.d.ts +7 -0
  61. package/dist/src/npm-registry.js +47 -0
  62. package/dist/src/screens/app-installation.d.ts +3 -0
  63. package/dist/src/screens/app-installation.js +166 -0
  64. package/dist/src/screens/app-manifest.d.ts +3 -0
  65. package/dist/src/screens/app-manifest.js +81 -0
  66. package/dist/src/screens/oauth.d.ts +15 -0
  67. package/dist/src/screens/oauth.js +257 -0
  68. package/dist/src/screens/session.d.ts +8 -0
  69. package/dist/src/screens/session.js +170 -0
  70. package/dist/src/semantics/actions.d.ts +2 -0
  71. package/dist/src/semantics/actions.js +413 -0
  72. package/dist/src/semantics/activity.d.ts +4 -0
  73. package/dist/src/semantics/activity.js +161 -0
  74. package/dist/src/semantics/apps.d.ts +2 -0
  75. package/dist/src/semantics/apps.js +144 -0
  76. package/dist/src/semantics/branches.d.ts +2 -0
  77. package/dist/src/semantics/branches.js +136 -0
  78. package/dist/src/semantics/checks.d.ts +2 -0
  79. package/dist/src/semantics/checks.js +176 -0
  80. package/dist/src/semantics/code-scanning-upload.d.ts +2 -0
  81. package/dist/src/semantics/code-scanning-upload.js +97 -0
  82. package/dist/src/semantics/codespaces.d.ts +2 -0
  83. package/dist/src/semantics/codespaces.js +58 -0
  84. package/dist/src/semantics/commits.d.ts +2 -0
  85. package/dist/src/semantics/commits.js +109 -0
  86. package/dist/src/semantics/contents.d.ts +2 -0
  87. package/dist/src/semantics/contents.js +131 -0
  88. package/dist/src/semantics/deployments.d.ts +2 -0
  89. package/dist/src/semantics/deployments.js +127 -0
  90. package/dist/src/semantics/gists.d.ts +2 -0
  91. package/dist/src/semantics/gists.js +86 -0
  92. package/dist/src/semantics/git.d.ts +2 -0
  93. package/dist/src/semantics/git.js +235 -0
  94. package/dist/src/semantics/graphql.d.ts +7 -0
  95. package/dist/src/semantics/graphql.js +512 -0
  96. package/dist/src/semantics/index.d.ts +5 -0
  97. package/dist/src/semantics/index.js +62 -0
  98. package/dist/src/semantics/issues.d.ts +2 -0
  99. package/dist/src/semantics/issues.js +456 -0
  100. package/dist/src/semantics/keys.d.ts +2 -0
  101. package/dist/src/semantics/keys.js +66 -0
  102. package/dist/src/semantics/labels.d.ts +2 -0
  103. package/dist/src/semantics/labels.js +100 -0
  104. package/dist/src/semantics/meta.d.ts +12 -0
  105. package/dist/src/semantics/meta.js +144 -0
  106. package/dist/src/semantics/notifications.d.ts +4 -0
  107. package/dist/src/semantics/notifications.js +62 -0
  108. package/dist/src/semantics/orgs.d.ts +2 -0
  109. package/dist/src/semantics/orgs.js +420 -0
  110. package/dist/src/semantics/packages.d.ts +2 -0
  111. package/dist/src/semantics/packages.js +55 -0
  112. package/dist/src/semantics/pages.d.ts +2 -0
  113. package/dist/src/semantics/pages.js +176 -0
  114. package/dist/src/semantics/projects.d.ts +2 -0
  115. package/dist/src/semantics/projects.js +239 -0
  116. package/dist/src/semantics/pulls.d.ts +2 -0
  117. package/dist/src/semantics/pulls.js +462 -0
  118. package/dist/src/semantics/push-reactions.d.ts +101 -0
  119. package/dist/src/semantics/push-reactions.js +513 -0
  120. package/dist/src/semantics/releases.d.ts +19 -0
  121. package/dist/src/semantics/releases.js +231 -0
  122. package/dist/src/semantics/repo-invitations.d.ts +2 -0
  123. package/dist/src/semantics/repo-invitations.js +98 -0
  124. package/dist/src/semantics/repos.d.ts +17 -0
  125. package/dist/src/semantics/repos.js +390 -0
  126. package/dist/src/semantics/rulesets.d.ts +2 -0
  127. package/dist/src/semantics/rulesets.js +111 -0
  128. package/dist/src/semantics/search.d.ts +2 -0
  129. package/dist/src/semantics/search.js +84 -0
  130. package/dist/src/semantics/security.d.ts +2 -0
  131. package/dist/src/semantics/security.js +177 -0
  132. package/dist/src/semantics/shared.d.ts +61 -0
  133. package/dist/src/semantics/shared.js +151 -0
  134. package/dist/src/semantics/users.d.ts +2 -0
  135. package/dist/src/semantics/users.js +255 -0
  136. package/dist/src/semantics/webhooks.d.ts +2 -0
  137. package/dist/src/semantics/webhooks.js +107 -0
  138. package/dist/test-fixtures/github-a11y-reference.pr-list.SOURCE.md +56 -0
  139. package/dist/test-fixtures/github-a11y-reference.pr-list.json +1447 -0
  140. package/dist/test-fixtures/github-comment-schema.SOURCE.md +11 -0
  141. package/dist/test-fixtures/github-comment-schema.json +702 -0
  142. package/dist/test-fixtures/github-known-deviations.json +94 -0
  143. package/dist/test-fixtures/github-openapi-operations.SOURCE.md +76 -0
  144. package/dist/test-fixtures/github-openapi-operations.json +362 -0
  145. package/dist/test-fixtures/github-pull-schema.SOURCE.md +37 -0
  146. package/dist/test-fixtures/github-pull-schema.json +3601 -0
  147. package/dist/test-fixtures/github-review-schema.SOURCE.md +12 -0
  148. package/dist/test-fixtures/github-review-schema.json +236 -0
  149. package/package.json +20 -11
  150. package/src/cli.ts +7 -6
  151. package/src/generated/graphql-sdl.gen.json +1 -0
  152. package/src/generated/graphql.gen.json +1 -0
  153. package/src/generated/surface.gen.json +1 -0
  154. package/src/generated/ui.gen.json +1 -0
  155. package/src/github-a11y-snapshot.uitest.ts +5 -5
  156. package/src/github-budget.ts +4 -4
  157. package/src/github-capabilities.ts +1993 -556
  158. package/src/github-conformance.ts +12 -7
  159. package/src/github-connector.ts +108 -96
  160. package/src/github-events.ts +225 -95
  161. package/src/github-git-http.ts +48 -84
  162. package/src/github-git-plane.ts +247 -385
  163. package/src/github-journey.uitest.ts +28 -44
  164. package/src/github-mirror-state.ts +23 -61
  165. package/src/github-mirror-ui.ts +16 -10
  166. package/src/github-server.ts +158 -60
  167. package/src/github-shared.ts +1 -1
  168. package/src/github-twin.ts +1525 -4494
  169. package/src/github-ui-conformance.ts +7 -8
  170. package/src/github-ui-structure.ts +19 -18
  171. package/src/graphql-wire.ts +115 -0
  172. package/src/index.ts +50 -7
  173. package/src/manifest.ts +605 -0
  174. package/src/npm-registry.ts +43 -0
  175. package/src/screens/app-installation.tsx +217 -0
  176. package/src/screens/app-manifest.tsx +97 -0
  177. package/src/screens/oauth.tsx +258 -0
  178. package/src/screens/session.tsx +193 -0
  179. package/src/semantics/actions.ts +395 -0
  180. package/src/semantics/activity.ts +171 -0
  181. package/src/semantics/apps.ts +133 -0
  182. package/src/semantics/branches.ts +133 -0
  183. package/src/semantics/checks.ts +163 -0
  184. package/src/semantics/code-scanning-upload.ts +92 -0
  185. package/src/semantics/codespaces.ts +58 -0
  186. package/src/semantics/commits.ts +109 -0
  187. package/src/semantics/contents.ts +112 -0
  188. package/src/semantics/deployments.ts +116 -0
  189. package/src/semantics/gists.ts +85 -0
  190. package/src/semantics/git.ts +226 -0
  191. package/src/semantics/graphql.ts +505 -0
  192. package/src/semantics/index.ts +68 -0
  193. package/src/semantics/issues.ts +434 -0
  194. package/src/semantics/keys.ts +66 -0
  195. package/src/semantics/labels.ts +91 -0
  196. package/src/semantics/meta.ts +139 -0
  197. package/src/semantics/notifications.ts +58 -0
  198. package/src/semantics/orgs.ts +383 -0
  199. package/src/semantics/packages.ts +59 -0
  200. package/src/semantics/pages.ts +154 -0
  201. package/src/semantics/projects.ts +246 -0
  202. package/src/semantics/pulls.ts +421 -0
  203. package/src/semantics/push-reactions.ts +471 -0
  204. package/src/semantics/releases.ts +213 -0
  205. package/src/semantics/repo-invitations.ts +112 -0
  206. package/src/semantics/repos.ts +373 -0
  207. package/src/semantics/rulesets.ts +107 -0
  208. package/src/semantics/search.ts +83 -0
  209. package/src/semantics/security.ts +153 -0
  210. package/src/semantics/shared.ts +181 -0
  211. package/src/semantics/users.ts +252 -0
  212. package/src/semantics/webhooks.ts +101 -0
  213. package/test-fixtures/github-known-deviations.json +7 -8
  214. package/test-fixtures/github-openapi-operations.json +46 -227
  215. package/src/github-graphql.ts +0 -398
@@ -0,0 +1,114 @@
1
+ import React from "react";
2
+ import type { ChangedFile, Comment, Commit, PullRequest, Review } from "./github-mirror.js";
3
+ import type { Check, RepoFacts, StateEvent } from "./pulls-rest.js";
4
+ export declare function Octicon({ name, size, className }: {
5
+ name: string;
6
+ size?: number;
7
+ className?: string;
8
+ }): React.JSX.Element;
9
+ /** A person's default picture, as GitHub draws one for an account without an upload: a 5x5 mirrored identicon. */
10
+ export declare function Avatar({ login, size, src }: {
11
+ login?: string;
12
+ size?: number;
13
+ src?: string;
14
+ }): React.JSX.Element;
15
+ /** GitHub's relative time: "now", "3 minutes ago", "yesterday", then "on Sep 24". */
16
+ export declare function timeAgo(iso?: string, now?: number): string;
17
+ /** A label pill in the label's own colour, its text dark or light by the colour's lightness. */
18
+ export declare function Label({ name, color }: {
19
+ name: string;
20
+ color?: string;
21
+ }): React.JSX.Element;
22
+ type PullState = "open" | "merged" | "closed" | "draft";
23
+ export declare function pullState(pr: PullRequest): PullState;
24
+ export declare function StateBadge({ pr }: {
25
+ pr: PullRequest;
26
+ }): React.JSX.Element;
27
+ /** The CI rollup beside a title: a green check, a red cross or an amber dot. */
28
+ export declare function CiStatus({ status }: {
29
+ status?: string;
30
+ }): React.JSX.Element | null;
31
+ /** What the latest review by each reviewer says, as GitHub decides a pull request's review state. */
32
+ export declare function reviewDecision(pr: PullRequest, reviews: Review[]): "approved" | "changes_requested" | "review_required" | undefined;
33
+ export type RepoTab = "code" | "issues" | "pulls" | "actions" | "projects" | "security" | "insights" | "settings" | "discussions";
34
+ export declare function RepoHeader({ repo, viewer, active, facts, openPulls, projects, onTab, onHome, onInbox, onSearch }: {
35
+ repo: string;
36
+ viewer?: string;
37
+ active?: RepoTab;
38
+ facts?: RepoFacts;
39
+ openPulls: number;
40
+ projects: number;
41
+ onTab: (tab: RepoTab) => void;
42
+ onHome: () => void;
43
+ onInbox: () => void;
44
+ onSearch: () => void;
45
+ }): React.JSX.Element;
46
+ /** The page footer every GitHub page ends with. */
47
+ export declare function SiteFooter(): React.JSX.Element;
48
+ /** The list's rows: each pull request's state icon, title, CI, labels, meta line, assignees and comment count. */
49
+ export declare function PrList({ prs, reviews, labelColors, showRepo, onSelect }: {
50
+ prs: PullRequest[];
51
+ reviews: Review[];
52
+ labelColors?: Record<string, string>;
53
+ showRepo?: boolean;
54
+ selectedKey?: string;
55
+ onSelect?: (key: string) => void;
56
+ }): React.JSX.Element;
57
+ export declare function PullsListPage({ repo, prs, reviews, facts, listState, onListState, onSelect }: {
58
+ repo: string;
59
+ prs: PullRequest[];
60
+ reviews: Review[];
61
+ facts?: RepoFacts;
62
+ listState: "open" | "closed";
63
+ onListState: (s: "open" | "closed") => void;
64
+ onSelect: (key: string) => void;
65
+ }): React.JSX.Element;
66
+ export type PullTab = "conversation" | "commits" | "checks" | "files";
67
+ export type PullActions = {
68
+ comment: (body: string) => Promise<void>;
69
+ merge: (title: string, message: string) => Promise<void>;
70
+ close: (body?: string) => Promise<void>;
71
+ reopen: () => Promise<void>;
72
+ };
73
+ /** The Conversation tab's timeline, the merge box and the comment form. */
74
+ export declare function Conversation({ pr, reviews, commits, comments, reviewComments, checks, stateEvents, viewer, actions }: {
75
+ pr?: PullRequest;
76
+ reviews: Review[];
77
+ commits: Commit[];
78
+ comments: Comment[];
79
+ reviewComments: Comment[];
80
+ files?: ChangedFile[];
81
+ checks?: Check[];
82
+ stateEvents?: StateEvent[];
83
+ viewer?: string;
84
+ actions?: PullActions;
85
+ }): React.JSX.Element | null;
86
+ /** The sidebar: Reviewers, Assignees, Labels, Projects, Milestone, Development, Participants. */
87
+ export declare function Aside({ pr, reviews, comments, labelColors }: {
88
+ pr?: PullRequest;
89
+ reviews: Review[];
90
+ commits?: Commit[];
91
+ files?: ChangedFile[];
92
+ comments?: Comment[];
93
+ labelColors?: Record<string, string>;
94
+ }): React.JSX.Element;
95
+ /** The Files changed tab: each file's header (diffstat, path) and its unified diff with both line-number gutters. */
96
+ export declare function FilesDiff({ files }: {
97
+ files: ChangedFile[];
98
+ }): React.JSX.Element;
99
+ export declare function PullPage({ pr, reviews, commits, comments, reviewComments, files, checks, stateEvents, viewer, labelColors, tab, onTab, actions }: {
100
+ pr?: PullRequest;
101
+ reviews: Review[];
102
+ commits: Commit[];
103
+ comments: Comment[];
104
+ reviewComments: Comment[];
105
+ files: ChangedFile[];
106
+ checks: Check[];
107
+ stateEvents: StateEvent[];
108
+ viewer?: string;
109
+ labelColors: Record<string, string>;
110
+ tab: PullTab;
111
+ onTab: (t: PullTab) => void;
112
+ actions: PullActions;
113
+ }): React.JSX.Element;
114
+ export {};
@@ -0,0 +1,418 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ // The pull-request WORKSPACE: GitHub's repository header, its pull request list (/{owner}/{repo}/pulls) and a pull
3
+ // request's page (/{owner}/{repo}/pull/{n}, with its Commits, Checks and Files changed tabs). Authored, never
4
+ // captured (docs/contributing/architecture.md, "Screens"): the glyphs are Primer's published Octicons (MIT), the rest
5
+ // is written here. Everything shown was read through GitHub's REST API (pulls-rest.ts) and every control writes
6
+ // through it: a comment (POST /issues/{n}/comments), a merge (PUT /pulls/{n}/merge), close and reopen
7
+ // (PATCH /pulls/{n} state).
8
+ import React, { useState } from "react";
9
+ // ── Octicons (16px viewBox unless noted) ────────────────────────────────────────────────────────────────────────
10
+ const ICONS = {
11
+ "mark-github": "M8 0c4.42 0 8 3.58 8 8a8.013 8.013 0 0 1-5.45 7.59c-.4.08-.55-.17-.55-.38 0-.27.01-1.13.01-2.2 0-.75-.25-1.23-.54-1.48 1.78-.2 3.65-.88 3.65-3.95 0-.88-.31-1.59-.82-2.15.08-.2.36-1.02-.08-2.12 0 0-.67-.22-2.2.82-.64-.18-1.32-.27-2-.27-.68 0-1.36.09-2 .27-1.53-1.03-2.2-.82-2.2-.82-.44 1.1-.16 1.92-.08 2.12-.51.56-.82 1.28-.82 2.15 0 3.06 1.86 3.75 3.64 3.95-.23.2-.44.55-.51 1.07-.46.21-1.61.55-2.33-.66-.15-.24-.6-.83-1.23-.82-.67.01-.27.38.01.53.34.19.73.9.82 1.13.16.45.68 1.31 2.69.94 0 .67.01 1.3.01 1.49 0 .21-.15.45-.55.38A7.995 7.995 0 0 1 0 8c0-4.42 3.58-8 8-8Z",
12
+ "git-pull-request": "M1.5 3.25a2.25 2.25 0 1 1 3 2.122v5.256a2.251 2.251 0 1 1-1.5 0V5.372A2.25 2.25 0 0 1 1.5 3.25Zm5.677-.177L9.573.677A.25.25 0 0 1 10 .854V2.5h1A2.5 2.5 0 0 1 13.5 5v5.628a2.251 2.251 0 1 1-1.5 0V5a1 1 0 0 0-1-1h-1v1.646a.25.25 0 0 1-.427.177L7.177 3.427a.25.25 0 0 1 0-.354ZM3.75 2.5a.75.75 0 1 0 0 1.5.75.75 0 0 0 0-1.5Zm0 9.5a.75.75 0 1 0 0 1.5.75.75 0 0 0 0-1.5Zm8.25.75a.75.75 0 1 0 1.5 0 .75.75 0 0 0-1.5 0Z",
13
+ "git-merge": "M5.45 5.154A4.25 4.25 0 0 0 9.25 7.5h1.378a2.251 2.251 0 1 1 0 1.5H9.25A5.734 5.734 0 0 1 5 7.123v3.505a2.25 2.25 0 1 1-1.5 0V5.372a2.25 2.25 0 1 1 1.95-.218ZM4.25 13.5a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5Zm8.5-4.5a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5ZM5 3.25a.75.75 0 1 0 0 .005V3.25Z",
14
+ "git-pull-request-closed": "M3.25 1A2.25 2.25 0 0 1 4 5.372v5.256a2.251 2.251 0 1 1-1.5 0V5.372A2.251 2.251 0 0 1 3.25 1Zm9.5 5.5a.75.75 0 0 1 .75.75v3.378a2.251 2.251 0 1 1-1.5 0V7.25a.75.75 0 0 1 .75-.75Zm-2.03-5.273a.75.75 0 0 1 1.06 0l.97.97.97-.97a.748.748 0 0 1 1.265.332.75.75 0 0 1-.205.729l-.97.97.97.97a.751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018l-.97-.97-.97.97a.749.749 0 0 1-1.275-.326.749.749 0 0 1 .215-.734l.97-.97-.97-.97a.75.75 0 0 1 0-1.06ZM2.5 3.25a.75.75 0 1 0 1.5 0 .75.75 0 0 0-1.5 0ZM3.25 12a.75.75 0 1 0 0 1.5.75.75 0 0 0 0-1.5Zm9.5 0a.75.75 0 1 0 0 1.5.75.75 0 0 0 0-1.5Z",
15
+ "git-pull-request-draft": "M3.25 1A2.25 2.25 0 0 1 4 5.372v5.256a2.251 2.251 0 1 1-1.5 0V5.372A2.251 2.251 0 0 1 3.25 1Zm9.5 14a2.25 2.25 0 1 1 0-4.5 2.25 2.25 0 0 1 0 4.5ZM2.5 3.25a.75.75 0 1 0 1.5 0 .75.75 0 0 0-1.5 0ZM3.25 12a.75.75 0 1 0 0 1.5.75.75 0 0 0 0-1.5Zm9.5 0a.75.75 0 1 0 0 1.5.75.75 0 0 0 0-1.5ZM14 7.5a1.25 1.25 0 1 1-2.5 0 1.25 1.25 0 0 1 2.5 0Zm0-4.25a1.25 1.25 0 1 1-2.5 0 1.25 1.25 0 0 1 2.5 0Z",
16
+ check: "M13.78 4.22a.75.75 0 0 1 0 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L2.22 9.28a.751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018L6 10.94l6.72-6.72a.75.75 0 0 1 1.06 0Z",
17
+ x: "M3.72 3.72a.75.75 0 0 1 1.06 0L8 6.94l3.22-3.22a.749.749 0 0 1 1.275.326.749.749 0 0 1-.215.734L9.06 8l3.22 3.22a.749.749 0 0 1-.326 1.275.749.749 0 0 1-.734-.215L8 9.06l-3.22 3.22a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042L6.94 8 3.72 4.78a.75.75 0 0 1 0-1.06Z",
18
+ "dot-fill": "M8 4a4 4 0 1 1 0 8 4 4 0 0 1 0-8Z",
19
+ comment: "M1 2.75C1 1.784 1.784 1 2.75 1h10.5c.966 0 1.75.784 1.75 1.75v7.5A1.75 1.75 0 0 1 13.25 12H9.06l-2.573 2.573A1.458 1.458 0 0 1 4 13.543V12H2.75A1.75 1.75 0 0 1 1 10.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h2a.75.75 0 0 1 .75.75v2.19l2.72-2.72a.749.749 0 0 1 .53-.22h4.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z",
20
+ "comment-discussion": "M1.75 1h8.5c.966 0 1.75.784 1.75 1.75v5.5A1.75 1.75 0 0 1 10.25 10H7.061l-2.574 2.573A1.458 1.458 0 0 1 2 11.543V10h-.25A1.75 1.75 0 0 1 0 8.25v-5.5C0 1.784.784 1 1.75 1ZM1.5 2.75v5.5c0 .138.112.25.25.25h1a.75.75 0 0 1 .75.75v2.19l2.72-2.72a.749.749 0 0 1 .53-.22h3.5a.25.25 0 0 0 .25-.25v-5.5a.25.25 0 0 0-.25-.25h-8.5a.25.25 0 0 0-.25.25Zm13 2a.25.25 0 0 0-.25-.25h-.5a.75.75 0 0 1 0-1.5h.5c.966 0 1.75.784 1.75 1.75v5.5A1.75 1.75 0 0 1 14.25 12H14v1.543a1.458 1.458 0 0 1-2.487 1.03L9.22 12.28a.749.749 0 0 1 .326-1.275.749.749 0 0 1 .734.215l2.22 2.22v-2.19a.75.75 0 0 1 .75-.75h1a.25.25 0 0 0 .25-.25Z",
21
+ code: "m11.28 3.22 4.25 4.25a.75.75 0 0 1 0 1.06l-4.25 4.25a.749.749 0 0 1-1.275-.326.749.749 0 0 1 .215-.734L13.94 8l-3.72-3.72a.749.749 0 0 1 .326-1.275.749.749 0 0 1 .734.215Zm-6.56 0a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042L2.06 8l3.72 3.72a.749.749 0 0 1-.326 1.275.749.749 0 0 1-.734-.215L.47 8.53a.75.75 0 0 1 0-1.06Z",
22
+ "issue-opened": "M8 9.5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3ZM8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0ZM1.5 8a6.5 6.5 0 1 0 13 0 6.5 6.5 0 0 0-13 0Z",
23
+ play: "M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0ZM1.5 8a6.5 6.5 0 1 0 13 0 6.5 6.5 0 0 0-13 0Zm4.879-2.773 4.264 2.559a.25.25 0 0 1 0 .428l-4.264 2.559A.25.25 0 0 1 6 10.559V5.442a.25.25 0 0 1 .379-.215Z",
24
+ table: "M0 1.75C0 .784.784 0 1.75 0h12.5C15.216 0 16 .784 16 1.75v12.5A1.75 1.75 0 0 1 14.25 16H1.75A1.75 1.75 0 0 1 0 14.25ZM6.5 6.5v8h7.75a.25.25 0 0 0 .25-.25V6.5Zm8-1.5V1.75a.25.25 0 0 0-.25-.25H6.5V5Zm-13 1.5v7.75c0 .138.112.25.25.25H5v-8ZM5 5V1.5H1.75a.25.25 0 0 0-.25.25V5Z",
25
+ shield: "M7.467.133a1.748 1.748 0 0 1 1.066 0l5.25 1.68A1.75 1.75 0 0 1 15 3.48V7c0 1.566-.32 3.182-1.303 4.682-.983 1.498-2.585 2.813-5.032 3.855a1.697 1.697 0 0 1-1.33 0c-2.447-1.042-4.049-2.357-5.032-3.855C1.32 10.182 1 8.566 1 7V3.48a1.755 1.755 0 0 1 1.217-1.667Zm.61 1.429a.25.25 0 0 0-.153 0l-5.25 1.68a.25.25 0 0 0-.174.238V7c0 1.358.275 2.666 1.057 3.86.784 1.194 2.121 2.34 4.366 3.297a.196.196 0 0 0 .154 0c2.245-.956 3.582-2.104 4.366-3.298C13.225 9.666 13.5 8.36 13.5 7V3.48a.251.251 0 0 0-.174-.237l-5.25-1.68Z",
26
+ graph: "M1.5 1.75V13.5h13.75a.75.75 0 0 1 0 1.5H.75a.75.75 0 0 1-.75-.75V1.75a.75.75 0 0 1 1.5 0Zm14.28 2.53-5.25 5.25a.75.75 0 0 1-1.06 0L7 7.06 4.28 9.78a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042l3.25-3.25a.75.75 0 0 1 1.06 0L10 7.94l4.72-4.72a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042Z",
27
+ gear: "M8 0a8.2 8.2 0 0 1 .701.031C9.444.095 9.99.645 10.16 1.29l.288 1.107c.018.066.079.158.212.224.231.114.454.243.668.386.123.082.233.09.299.071l1.103-.303c.644-.176 1.392.021 1.82.63.27.385.506.792.704 1.218.315.675.111 1.422-.364 1.891l-.814.806c-.049.048-.098.147-.088.294.016.257.016.515 0 .772-.01.147.038.246.088.294l.814.806c.475.469.679 1.216.364 1.891a7.977 7.977 0 0 1-.704 1.217c-.428.61-1.176.807-1.82.63l-1.102-.302c-.067-.019-.177-.011-.3.071a5.909 5.909 0 0 1-.668.386c-.133.066-.194.158-.211.224l-.29 1.106c-.168.646-.715 1.196-1.458 1.26a8.006 8.006 0 0 1-1.402 0c-.743-.064-1.289-.614-1.458-1.26l-.289-1.106c-.018-.066-.079-.158-.212-.224a5.738 5.738 0 0 1-.668-.386c-.123-.082-.233-.09-.299-.071l-1.103.303c-.644.176-1.392-.021-1.82-.63a8.12 8.12 0 0 1-.704-1.218c-.315-.675-.111-1.422.363-1.891l.815-.806c.05-.048.098-.147.088-.294a6.214 6.214 0 0 1 0-.772c.01-.147-.038-.246-.088-.294l-.815-.806C.635 6.045.431 5.298.746 4.623a7.92 7.92 0 0 1 .704-1.217c.428-.61 1.176-.807 1.82-.63l1.102.302c.067.019.177.011.3-.071.214-.143.437-.272.668-.386.133-.066.194-.158.211-.224l.29-1.106C6.009.645 6.556.095 7.299.03 7.53.01 7.764 0 8 0Zm-.571 1.525c-.036.003-.108.036-.137.146l-.289 1.105c-.147.561-.549.967-.998 1.189-.173.086-.34.183-.5.29-.417.278-.97.423-1.529.27l-1.103-.303c-.109-.03-.175.016-.195.045-.22.312-.412.644-.573.99-.014.031-.021.11.059.19l.815.806c.411.406.562.957.53 1.456a4.709 4.709 0 0 0 0 .582c.032.499-.119 1.05-.53 1.456l-.815.806c-.081.08-.073.159-.059.19.162.346.353.677.573.989.02.03.085.076.195.046l1.102-.303c.56-.153 1.113-.008 1.53.27.161.107.328.204.501.29.447.222.85.629.997 1.189l.289 1.105c.029.109.101.143.137.146a6.6 6.6 0 0 0 1.142 0c.036-.003.108-.036.137-.146l.289-1.105c.147-.561.549-.967.998-1.189.173-.086.34-.183.5-.29.417-.278.97-.423 1.529-.27l1.103.303c.109.029.175-.016.195-.045.22-.313.411-.644.573-.99.014-.031.021-.11-.059-.19l-.815-.806c-.411-.406-.562-.957-.53-1.456a4.709 4.709 0 0 0 0-.582c-.032-.499.119-1.05.53-1.456l.815-.806c.081-.08.073-.159.059-.19a6.464 6.464 0 0 0-.573-.989c-.02-.03-.085-.076-.195-.046l-1.102.303c-.56.153-1.113.008-1.53-.27a4.44 4.44 0 0 0-.501-.29c-.447-.222-.85-.629-.997-1.189l-.289-1.105c-.029-.11-.101-.143-.137-.146a6.6 6.6 0 0 0-1.142 0ZM11 8a3 3 0 1 1-6 0 3 3 0 0 1 6 0ZM9.5 8a1.5 1.5 0 1 0-3.001.001A1.5 1.5 0 0 0 9.5 8Z",
28
+ "git-commit": "M11.93 8.5a4.002 4.002 0 0 1-7.86 0H.75a.75.75 0 0 1 0-1.5h3.32a4.002 4.002 0 0 1 7.86 0h3.32a.75.75 0 0 1 0 1.5Zm-1.43-.75a2.5 2.5 0 1 0-5 0 2.5 2.5 0 0 0 5 0Z",
29
+ "file-diff": "M1 1.75C1 .784 1.784 0 2.75 0h7.586c.464 0 .909.184 1.237.513l2.914 2.914c.329.328.513.773.513 1.237v9.586A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25Zm1.75-.25a.25.25 0 0 0-.25.25v12.5c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V4.664a.25.25 0 0 0-.073-.177l-2.914-2.914a.25.25 0 0 0-.177-.073ZM8 3.25a.75.75 0 0 1 .75.75v1.5h1.5a.75.75 0 0 1 0 1.5h-1.5v1.5a.75.75 0 0 1-1.5 0V7h-1.5a.75.75 0 0 1 0-1.5h1.5V4A.75.75 0 0 1 8 3.25Zm-3 8a.75.75 0 0 1 .75-.75h4.5a.75.75 0 0 1 0 1.5h-4.5a.75.75 0 0 1-.75-.75Z",
30
+ checklist: "M2.5 1.75v11.5c0 .138.112.25.25.25h3.17a.75.75 0 0 1 0 1.5H2.75A1.75 1.75 0 0 1 1 13.25V1.75C1 .784 1.784 0 2.75 0h8.5C12.216 0 13 .784 13 1.75v7.736a.75.75 0 0 1-1.5 0V1.75a.25.25 0 0 0-.25-.25h-8.5a.25.25 0 0 0-.25.25Zm13.274 9.537v-.001l-4.557 4.45a.75.75 0 0 1-1.055-.008l-1.943-1.95a.75.75 0 0 1 1.062-1.058l1.419 1.425 4.026-3.932a.75.75 0 1 1 1.048 1.074ZM4.75 4h4.5a.75.75 0 0 1 0 1.5h-4.5a.75.75 0 0 1 0-1.5ZM4 7.75A.75.75 0 0 1 4.75 7h2a.75.75 0 0 1 0 1.5h-2A.75.75 0 0 1 4 7.75Z",
31
+ eye: "M8 2c1.981 0 3.671.992 4.933 2.078 1.27 1.091 2.187 2.345 2.637 3.023a1.62 1.62 0 0 1 0 1.798c-.45.678-1.367 1.932-2.637 3.023C11.67 13.008 9.981 14 8 14c-1.981 0-3.671-.992-4.933-2.078C1.797 10.83.88 9.576.43 8.898a1.62 1.62 0 0 1 0-1.798c.45-.677 1.367-1.931 2.637-3.022C4.33 2.992 6.019 2 8 2ZM1.679 7.932a.12.12 0 0 0 0 .136c.411.622 1.241 1.75 2.366 2.717C5.176 11.758 6.527 12.5 8 12.5c1.473 0 2.825-.742 3.955-1.715 1.124-.967 1.954-2.096 2.366-2.717a.12.12 0 0 0 0-.136c-.412-.621-1.242-1.75-2.366-2.717C10.824 4.242 9.473 3.5 8 3.5c-1.473 0-2.825.742-3.955 1.715-1.124.967-1.954 2.096-2.366 2.717ZM8 10a2 2 0 1 1-.001-3.999A2 2 0 0 1 8 10Z",
32
+ search: "M10.68 11.74a6 6 0 0 1-7.922-8.982 6 6 0 0 1 8.982 7.922l3.04 3.04a.749.749 0 0 1-.326 1.275.749.749 0 0 1-.734-.215ZM11.5 7a4.499 4.499 0 1 0-8.997 0A4.499 4.499 0 0 0 11.5 7Z",
33
+ "three-bars": "M1 2.75A.75.75 0 0 1 1.75 2h12.5a.75.75 0 0 1 0 1.5H1.75A.75.75 0 0 1 1 2.75Zm0 5A.75.75 0 0 1 1.75 7h12.5a.75.75 0 0 1 0 1.5H1.75A.75.75 0 0 1 1 7.75ZM1.75 12h12.5a.75.75 0 0 1 0 1.5H1.75a.75.75 0 0 1 0-1.5Z",
34
+ inbox: "M2.8 2.06A1.75 1.75 0 0 1 4.41 1h7.18c.7 0 1.333.417 1.61 1.06l2.74 6.395c.04.093.06.194.06.295v4.5A1.75 1.75 0 0 1 14.25 15H1.75A1.75 1.75 0 0 1 0 13.25v-4.5c0-.101.02-.202.06-.295Zm1.61.44a.25.25 0 0 0-.23.152L1.887 8H4.75a.75.75 0 0 1 .6.3L6.625 10h2.75l1.275-1.7a.75.75 0 0 1 .6-.3h2.863L11.82 2.652a.25.25 0 0 0-.23-.152Zm10.09 7h-2.875l-1.275 1.7a.75.75 0 0 1-.6.3h-3.5a.75.75 0 0 1-.6-.3L4.375 9.5H1.5v3.75c0 .138.112.25.25.25h12.5a.25.25 0 0 0 .25-.25Z",
35
+ plus: "M7.75 2a.75.75 0 0 1 .75.75V7h4.25a.75.75 0 0 1 0 1.5H8.5v4.25a.75.75 0 0 1-1.5 0V8.5H2.75a.75.75 0 0 1 0-1.5H7V2.75A.75.75 0 0 1 7.75 2Z",
36
+ "triangle-down": "m4.427 7.427 3.396 3.396a.25.25 0 0 0 .354 0l3.396-3.396A.25.25 0 0 0 11.396 7H4.604a.25.25 0 0 0-.177.427Z",
37
+ "chevron-down": "M12.78 5.22a.749.749 0 0 1 0 1.06l-4.25 4.25a.749.749 0 0 1-1.06 0L3.22 6.28a.749.749 0 1 1 1.06-1.06L8 8.939l3.72-3.719a.749.749 0 0 1 1.06 0Z",
38
+ tag: "M1 7.775V2.75C1 1.784 1.784 1 2.75 1h5.025c.464 0 .91.184 1.238.513l6.25 6.25a1.75 1.75 0 0 1 0 2.474l-5.026 5.026a1.75 1.75 0 0 1-2.474 0l-6.25-6.25A1.752 1.752 0 0 1 1 7.775Zm1.5 0c0 .066.026.13.073.177l6.25 6.25a.25.25 0 0 0 .354 0l5.025-5.025a.25.25 0 0 0 0-.354l-6.25-6.25a.25.25 0 0 0-.177-.073H2.75a.25.25 0 0 0-.25.25ZM6 5a1 1 0 1 1 0 2 1 1 0 0 1 0-2Z",
39
+ milestone: "M7.75 0a.75.75 0 0 1 .75.75V3h3.634c.414 0 .814.147 1.13.414l2.07 1.75a1.75 1.75 0 0 1 0 2.672l-2.07 1.75a1.75 1.75 0 0 1-1.13.414H8.5v5.25a.75.75 0 0 1-1.5 0V10H2.75A1.75 1.75 0 0 1 1 8.25v-3.5C1 3.784 1.784 3 2.75 3H7V.75A.75.75 0 0 1 7.75 0Zm4.384 8.5a.25.25 0 0 0 .161-.06l2.07-1.75a.248.248 0 0 0 0-.38l-2.07-1.75a.25.25 0 0 0-.161-.06H2.75a.25.25 0 0 0-.25.25v3.5c0 .138.112.25.25.25h9.384Z",
40
+ copy: "M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 0 1 0 1.5h-1.5a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-1.5a.75.75 0 0 1 1.5 0v1.5A1.75 1.75 0 0 1 9.25 16h-7.5A1.75 1.75 0 0 1 0 14.25Zm5-5C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0 1 14.25 11h-7.5A1.75 1.75 0 0 1 5 9.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z",
41
+ "kebab-horizontal": "M8 9a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3ZM1.5 9a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3Zm13 0a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3Z",
42
+ "skip": "M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0ZM1.5 8a6.5 6.5 0 1 0 13 0 6.5 6.5 0 0 0-13 0Z",
43
+ };
44
+ export function Octicon({ name, size = 16, className }) {
45
+ return (_jsx("svg", { className: `octicon octicon-${name}${className ? ` ${className}` : ""}`, viewBox: "0 0 16 16", width: size, height: size, "aria-hidden": "true", fill: "currentColor", children: _jsx("path", { d: ICONS[name] ?? "" }) }));
46
+ }
47
+ // ── Small shared pieces ─────────────────────────────────────────────────────────────────────────────────────────
48
+ function hashOf(text) {
49
+ let h = 2166136261;
50
+ for (let i = 0; i < text.length; i++)
51
+ h = Math.imul(h ^ text.charCodeAt(i), 16777619);
52
+ return h >>> 0;
53
+ }
54
+ /** A person's default picture, as GitHub draws one for an account without an upload: a 5x5 mirrored identicon. */
55
+ export function Avatar({ login, size = 20, src }) {
56
+ if (src)
57
+ return _jsx("img", { className: "gh-avatar", src: src, width: size, height: size, alt: `@${login ?? ""}` });
58
+ const h = hashOf(login || "ghost");
59
+ const hue = h % 360;
60
+ const cells = [];
61
+ for (let y = 0; y < 5; y++)
62
+ for (let x = 0; x < 3; x++) {
63
+ if ((h >> (y * 3 + x)) & 1) {
64
+ cells.push(_jsx("rect", { x: 1 + x, y: 1 + y, width: 1, height: 1 }, `${x}-${y}`));
65
+ if (x < 2)
66
+ cells.push(_jsx("rect", { x: 5 - x, y: 1 + y, width: 1, height: 1 }, `${4 - x}-${y}m`));
67
+ }
68
+ }
69
+ return (_jsxs("svg", { className: "gh-avatar", width: size, height: size, viewBox: "0 0 7 7", role: "img", "aria-label": `@${login ?? "ghost"}`, shapeRendering: "crispEdges", children: [_jsx("rect", { width: "7", height: "7", fill: "#f0f0f0" }), _jsx("g", { fill: `hsl(${hue} 55% 58%)`, children: cells })] }));
70
+ }
71
+ /** GitHub's relative time: "now", "3 minutes ago", "yesterday", then "on Sep 24". */
72
+ export function timeAgo(iso, now = Date.now()) {
73
+ if (!iso)
74
+ return "";
75
+ const t = Date.parse(iso);
76
+ if (!Number.isFinite(t))
77
+ return "";
78
+ const s = Math.max(0, Math.round((now - t) / 1000));
79
+ if (s < 45)
80
+ return "now";
81
+ const m = Math.round(s / 60);
82
+ if (m < 60)
83
+ return m === 1 ? "1 minute ago" : `${m} minutes ago`;
84
+ const h = Math.round(m / 60);
85
+ if (h < 24)
86
+ return h === 1 ? "1 hour ago" : `${h} hours ago`;
87
+ const d = Math.round(h / 24);
88
+ if (d === 1)
89
+ return "yesterday";
90
+ if (d < 30)
91
+ return `${d} days ago`;
92
+ return `on ${new Date(t).toLocaleDateString("en-US", { month: "short", day: "numeric", ...(new Date(t).getFullYear() !== new Date(now).getFullYear() ? { year: "numeric" } : {}) })}`;
93
+ }
94
+ function RelativeTime({ iso }) {
95
+ return _jsx("time", { dateTime: iso, title: iso ? new Date(iso).toLocaleString("en-US", { dateStyle: "medium", timeStyle: "short" }) : undefined, children: timeAgo(iso) });
96
+ }
97
+ /** A label pill in the label's own colour, its text dark or light by the colour's lightness. */
98
+ export function Label({ name, color }) {
99
+ const hex = (color || "ededed").replace(/^#/, "");
100
+ const [r, g, b] = [0, 2, 4].map((i) => parseInt(hex.slice(i, i + 2), 16) || 0);
101
+ const light = (r * 299 + g * 587 + b * 114) / 1000 > 150;
102
+ return _jsx("span", { className: "gh-label", style: { background: `#${hex}`, color: light ? "#1f2328" : "#ffffff" }, children: name });
103
+ }
104
+ /** A body as GitHub renders its Markdown, for what a comment ordinarily holds: paragraphs, inline code, bold, links. */
105
+ function Markdown({ text }) {
106
+ const inline = (line, key) => line.split(/(`[^`]+`|\*\*[^*]+\*\*|https?:\/\/\S+)/g).map((part, i) => {
107
+ if (/^`[^`]+`$/.test(part))
108
+ return _jsx("code", { children: part.slice(1, -1) }, `${key}-${i}`);
109
+ if (/^\*\*[^*]+\*\*$/.test(part))
110
+ return _jsx("strong", { children: part.slice(2, -2) }, `${key}-${i}`);
111
+ if (/^https?:\/\//.test(part))
112
+ return _jsx("a", { href: part, target: "_blank", rel: "noreferrer", children: part }, `${key}-${i}`);
113
+ return part;
114
+ });
115
+ const paragraphs = (text ?? "").trim().split(/\n{2,}/).filter(Boolean);
116
+ if (!paragraphs.length)
117
+ return _jsx("p", { className: "gh-muted", children: _jsx("em", { children: "No description provided." }) });
118
+ return _jsx(_Fragment, { children: paragraphs.map((p, i) => _jsx("p", { children: p.split("\n").flatMap((line, j) => (j ? [_jsx("br", {}, `br-${i}-${j}`)] : []).concat(inline(line, `${i}-${j}`))) }, i)) });
119
+ }
120
+ export function pullState(pr) {
121
+ if (pr.merged)
122
+ return "merged";
123
+ if (pr.state === "closed")
124
+ return "closed";
125
+ return pr.draft ? "draft" : "open";
126
+ }
127
+ const STATE_ICON = { open: "git-pull-request", merged: "git-merge", closed: "git-pull-request-closed", draft: "git-pull-request-draft" };
128
+ const STATE_WORD = { open: "Open", merged: "Merged", closed: "Closed", draft: "Draft" };
129
+ export function StateBadge({ pr }) {
130
+ const s = pullState(pr);
131
+ return _jsxs("span", { className: `state-badge gh-state gh-state-${s}`, children: [_jsx(Octicon, { name: STATE_ICON[s] }), STATE_WORD[s]] });
132
+ }
133
+ const CI_ICON = { success: "check", failure: "x", error: "x", pending: "dot-fill" };
134
+ /** The CI rollup beside a title: a green check, a red cross or an amber dot. */
135
+ export function CiStatus({ status }) {
136
+ if (!status)
137
+ return null;
138
+ const word = status === "success" ? "All checks have passed" : status === "pending" ? "Some checks haven't completed yet" : "Some checks were not successful";
139
+ return _jsx("span", { className: `ci-status ${status}`, title: `checks: ${status}`, "aria-label": word, children: _jsx(Octicon, { name: CI_ICON[status] ?? "dot-fill" }) });
140
+ }
141
+ /** What the latest review by each reviewer says, as GitHub decides a pull request's review state. */
142
+ export function reviewDecision(pr, reviews) {
143
+ const latest = new Map();
144
+ for (const r of reviews) {
145
+ if (r.repo !== pr.repo || Number(r.number) !== Number(pr.number) || !r.reviewer)
146
+ continue;
147
+ const st = String(r.state ?? "").toLowerCase();
148
+ if (st === "approved" || st === "changes_requested" || st === "dismissed")
149
+ latest.set(r.reviewer, st);
150
+ }
151
+ const states = [...latest.values()];
152
+ if (states.includes("changes_requested"))
153
+ return "changes_requested";
154
+ if (states.includes("approved"))
155
+ return "approved";
156
+ return (pr.requestedReviewers ?? []).length ? "review_required" : undefined;
157
+ }
158
+ export function RepoHeader({ repo, viewer, active, facts, openPulls, projects, onTab, onHome, onInbox, onSearch }) {
159
+ const all = repo === "all" || !repo;
160
+ const [owner, name] = all ? ["", ""] : repo.split("/");
161
+ const tabs = [
162
+ { id: "code", label: "Code", icon: "code" },
163
+ { id: "issues", label: "Issues", icon: "issue-opened", count: facts?.openIssues ?? 0 },
164
+ { id: "pulls", label: "Pull requests", icon: "git-pull-request", count: openPulls },
165
+ ...(facts?.hasDiscussions ? [{ id: "discussions", label: "Discussions", icon: "comment-discussion" }] : []),
166
+ { id: "actions", label: "Actions", icon: "play" },
167
+ { id: "projects", label: "Projects", icon: "table", ...(projects ? { count: projects } : {}) },
168
+ { id: "security", label: "Security", icon: "shield" },
169
+ { id: "insights", label: "Insights", icon: "graph" },
170
+ { id: "settings", label: "Settings", icon: "gear" },
171
+ ];
172
+ return (_jsxs("header", { className: "gh-header", children: [_jsxs("div", { className: "gh-globalbar", children: [_jsx("button", { className: "gh-iconbtn", "aria-label": "Open global navigation menu", children: _jsx(Octicon, { name: "three-bars" }) }), _jsx("a", { className: "gh-logo", href: "/", "aria-label": "Homepage", onClick: (e) => { e.preventDefault(); onHome(); }, children: _jsx(Octicon, { name: "mark-github", size: 32 }) }), _jsx("nav", { className: "gh-context", "aria-label": "Page context", children: all ? _jsx("strong", { children: "Pull requests" }) : (_jsxs(_Fragment, { children: [_jsx("a", { href: `/${owner}`, onClick: (e) => e.preventDefault(), children: owner }), _jsx("span", { className: "gh-context-sep", children: "/" }), _jsx("a", { href: `/${repo}`, onClick: (e) => { e.preventDefault(); onTab("code"); }, children: _jsx("strong", { children: name }) })] })) }), _jsx("div", { className: "gh-spacer" }), _jsxs("button", { className: "gh-search", onClick: onSearch, children: [_jsx(Octicon, { name: "search" }), _jsxs("span", { children: ["Type ", _jsx("kbd", { children: "/" }), " to search"] })] }), _jsx("span", { className: "gh-divider" }), _jsxs("button", { className: "gh-iconbtn gh-iconbtn-wide", "aria-label": "Create something new", children: [_jsx(Octicon, { name: "plus" }), _jsx(Octicon, { name: "triangle-down" })] }), _jsx("button", { className: "gh-iconbtn", "aria-label": "Your issues", onClick: () => onTab("issues"), children: _jsx(Octicon, { name: "issue-opened" }) }), _jsx("button", { className: "gh-iconbtn", "aria-label": "Your pull requests", onClick: onHome, children: _jsx(Octicon, { name: "git-pull-request" }) }), _jsx("button", { className: "gh-iconbtn", "aria-label": "You have no unread notifications", onClick: onInbox, children: _jsx(Octicon, { name: "inbox" }) }), _jsx("span", { className: "gh-viewer", children: _jsx(Avatar, { login: viewer, size: 32 }) })] }), all ? null : (_jsx("nav", { className: "gh-localbar", "aria-label": "Repository", children: tabs.map((t) => (_jsxs("a", { href: `/${repo}${t.id === "code" ? "" : `/${t.id}`}`, className: `gh-tab${active === t.id ? " selected" : ""}`, "aria-current": active === t.id ? "page" : undefined, onClick: (e) => { e.preventDefault(); onTab(t.id); }, children: [_jsx(Octicon, { name: t.icon }), _jsx("span", { children: t.label }), t.count ? _jsx("span", { className: "Counter", children: t.count }) : null] }, t.id))) }))] }));
173
+ }
174
+ /** The page footer every GitHub page ends with. */
175
+ export function SiteFooter() {
176
+ return (_jsxs("footer", { className: "gh-footer", children: [_jsxs("span", { className: "gh-footer-brand", children: [_jsx(Octicon, { name: "mark-github", size: 24 }), " \u00A9 ", new Date().getFullYear(), " GitHub, Inc."] }), _jsx("ul", { children: ["Terms", "Privacy", "Security", "Status", "Community", "Docs", "Contact", "Manage cookies", "Do not share my personal information"].map((t) => _jsx("li", { children: t }, t)) })] }));
177
+ }
178
+ // ── The list page ──────────────────────────────────────────────────────────────────────────────────────────────
179
+ function rowMeta(pr) {
180
+ const s = pullState(pr);
181
+ if (s === "merged")
182
+ return _jsxs(_Fragment, { children: ["#", pr.number, " by ", _jsx("a", { className: "gh-muted-link", children: pr.author }), " was merged ", _jsx(RelativeTime, { iso: pr.mergedAt })] });
183
+ if (s === "closed")
184
+ return _jsxs(_Fragment, { children: ["#", pr.number, " by ", _jsx("a", { className: "gh-muted-link", children: pr.author }), " was closed ", _jsx(RelativeTime, { iso: pr.closedAt ?? pr.updatedAt })] });
185
+ return _jsxs(_Fragment, { children: ["#", pr.number, " opened ", _jsx(RelativeTime, { iso: pr.createdAt }), " by ", _jsx("a", { className: "gh-muted-link", children: pr.author }), s === "draft" ? " • Draft" : ""] });
186
+ }
187
+ const DECISION_WORD = { approved: "Approved", changes_requested: "Changes requested", review_required: "Review required" };
188
+ /** The list's rows: each pull request's state icon, title, CI, labels, meta line, assignees and comment count. */
189
+ export function PrList({ prs, reviews, labelColors = {}, showRepo, onSelect }) {
190
+ return (_jsx("div", { className: "gh-rows", children: prs.map((pr) => {
191
+ const s = pullState(pr);
192
+ const decision = s === "open" || s === "draft" ? reviewDecision(pr, reviews) : undefined;
193
+ const comments = pr.commentsCount ?? 0;
194
+ return (_jsxs("div", { className: "pr-row", "data-number": pr.number, children: [_jsx("span", { className: `pr-state-icon ${s}`, "aria-label": `${STATE_WORD[s]} pull request`, children: _jsx(Octicon, { name: STATE_ICON[s] }) }), _jsxs("div", { className: "pr-row-main", children: [_jsxs("div", { className: "pr-row-title-line", children: [showRepo ? _jsx("span", { className: "pr-row-repo", children: pr.repo }) : null, _jsx("a", { className: "pr-row-title", href: `/${pr.repo}/pull/${pr.number}`, onClick: (e) => { e.preventDefault(); onSelect?.(`${pr.repo}#${pr.number}`); }, children: pr.title }), _jsx(CiStatus, { status: pr.ciStatus }), (pr.labels ?? []).map((l) => _jsx(Label, { name: l.name, color: labelColors[`${pr.repo}:${l.name}`] ?? l.color }, l.name))] }), _jsxs("div", { className: "pr-row-meta", children: [rowMeta(pr), decision ? _jsxs(_Fragment, { children: [" \u2022 ", _jsx("a", { className: `gh-muted-link decision-${decision}`, children: DECISION_WORD[decision] })] }) : null] })] }), _jsxs("div", { className: "pr-row-side", children: [_jsx("span", { className: "pr-row-assignees", children: (pr.assignees ?? []).map((a) => _jsx(Avatar, { login: a, size: 20 }, a)) }), _jsx("span", { className: "pr-row-comments", children: comments ? _jsxs("a", { className: "gh-muted-link", "aria-label": `${comments} comments`, children: [_jsx(Octicon, { name: "comment" }), " ", comments] }) : null })] })] }, `${pr.repo}#${pr.number}`));
195
+ }) }));
196
+ }
197
+ export function PullsListPage({ repo, prs, reviews, facts, listState, onListState, onSelect }) {
198
+ const open = prs.filter((p) => pullState(p) === "open" || pullState(p) === "draft");
199
+ const closed = prs.filter((p) => pullState(p) === "merged" || pullState(p) === "closed");
200
+ const shown = listState === "open" ? open : closed;
201
+ const labelColors = Object.fromEntries((facts?.labels ?? []).map((l) => [`${repo}:${l.name}`, l.color ?? "ededed"]));
202
+ for (const p of prs)
203
+ for (const l of p.labels ?? [])
204
+ if (l.color && !labelColors[`${p.repo}:${l.name}`])
205
+ labelColors[`${p.repo}:${l.name}`] = l.color;
206
+ const all = repo === "all" || !repo;
207
+ return (_jsxs("div", { className: "gh-container gh-list-page", children: [_jsxs("div", { className: "gh-subnav", children: [_jsxs("div", { className: "gh-filter", children: [_jsxs("button", { className: "gh-btn gh-filter-btn", children: ["Filters ", _jsx(Octicon, { name: "triangle-down" })] }), _jsxs("label", { className: "gh-filter-input", children: [_jsx(Octicon, { name: "search" }), _jsx("input", { "aria-label": "Search all pull requests", value: `is:pr is:${listState} `, readOnly: true })] })] }), all ? null : (_jsxs("div", { className: "gh-subnav-links", children: [_jsxs("a", { className: "gh-subnav-item", children: [_jsx(Octicon, { name: "tag" }), " Labels ", _jsx("span", { className: "Counter", children: facts?.labels.length ?? 0 })] }), _jsxs("a", { className: "gh-subnav-item", children: [_jsx(Octicon, { name: "milestone" }), " Milestones ", _jsx("span", { className: "Counter", children: facts?.milestones ?? 0 })] })] })), all ? null : _jsx("button", { className: "gh-btn gh-btn-primary", children: "New pull request" })] }), _jsxs("div", { className: "gh-box", children: [_jsxs("div", { className: "gh-box-header", children: [_jsxs("div", { className: "gh-states", role: "group", "aria-label": "Pull request state", children: [_jsxs("button", { className: `gh-states-link${listState === "open" ? " selected" : ""}`, "aria-pressed": listState === "open", onClick: () => onListState("open"), children: [_jsx(Octicon, { name: "git-pull-request" }), " ", open.length, " Open"] }), _jsxs("button", { className: `gh-states-link${listState === "closed" ? " selected" : ""}`, "aria-pressed": listState === "closed", onClick: () => onListState("closed"), children: [_jsx(Octicon, { name: "check" }), " ", closed.length, " Closed"] })] }), _jsx("div", { className: "gh-box-filters", children: ["Author", "Label", "Projects", "Milestones", "Reviews", "Assignee", "Sort"].map((f) => _jsxs("span", { className: "gh-select-menu", children: [f, " ", _jsx(Octicon, { name: "triangle-down" })] }, f)) })] }), shown.length ? _jsx(PrList, { prs: shown, reviews: reviews, labelColors: labelColors, showRepo: all, onSelect: onSelect }) : (_jsxs("div", { className: "gh-blankslate", children: [_jsx(Octicon, { name: "git-pull-request", size: 24 }), _jsx("h3", { children: listState === "open" ? "There aren’t any open pull requests." : "There aren’t any closed pull requests." }), _jsxs("p", { children: ["You could search ", _jsx("a", { children: "all of GitHub" }), " or try an ", _jsx("a", { children: "advanced search" }), "."] })] }))] }), _jsxs("p", { className: "gh-protip", children: [_jsx("strong", { children: "ProTip!" }), " Filter pull requests by the default branch with ", _jsx("a", { children: "base:main" }), "."] })] }));
208
+ }
209
+ function shortSha(sha) { return sha ? sha.slice(0, 7) : ""; }
210
+ function DiffStat({ additions = 0, deletions = 0 }) {
211
+ const total = additions + deletions;
212
+ const greens = total ? Math.round((additions / total) * 5) : 0;
213
+ const reds = total ? Math.min(5 - greens, Math.round((deletions / total) * 5) || (deletions ? 1 : 0)) : 0;
214
+ return (_jsxs("span", { className: "gh-diffstat", children: [_jsxs("span", { className: "gh-add", children: ["+", additions] }), " ", _jsxs("span", { className: "gh-del", children: ["\u2212", deletions] }), _jsx("span", { className: "gh-diffstat-blocks", children: Array.from({ length: 5 }, (_, i) => _jsx("span", { className: `gh-block ${i < greens ? "add" : i < greens + reds ? "del" : "neutral"}` }, i)) })] }));
215
+ }
216
+ function PullHeader({ pr, commits, checks, files, tab, onTab }) {
217
+ const s = pullState(pr);
218
+ const n = pr.commitsCount ?? commits;
219
+ const commitWord = `${n} commit${n === 1 ? "" : "s"}`;
220
+ const [owner] = pr.repo.split("/");
221
+ const ref = (branch) => _jsx("span", { className: "gh-commit-ref", title: `${owner}:${branch}`, children: branch });
222
+ const tabs = [
223
+ { id: "conversation", label: "Conversation", icon: "comment-discussion", count: (pr.commentsCount ?? 0) + (pr.reviewCount ?? 0) + (pr.reviewCommentsCount ?? 0) },
224
+ { id: "commits", label: "Commits", icon: "git-commit", count: n },
225
+ { id: "checks", label: "Checks", icon: "checklist", count: checks },
226
+ { id: "files", label: "Files changed", icon: "file-diff", count: pr.changedFiles ?? files },
227
+ ];
228
+ return (_jsxs("div", { className: "gh-pull-header", children: [_jsxs("div", { className: "gh-pull-title-row", children: [_jsxs("h1", { className: "gh-pull-title", children: [_jsx("bdi", { children: pr.title }), " ", _jsxs("span", { className: "gh-pull-number", children: ["#", pr.number] })] }), _jsxs("div", { className: "gh-pull-title-actions", children: [_jsx("button", { className: "gh-btn gh-btn-sm", children: "Edit" }), _jsxs("button", { className: "gh-btn gh-btn-sm gh-btn-primary", children: [_jsx(Octicon, { name: "code" }), " Code ", _jsx(Octicon, { name: "triangle-down" })] })] })] }), _jsxs("div", { className: "gh-pull-meta state-line", children: [_jsx(StateBadge, { pr: pr }), _jsxs("span", { className: "gh-pull-meta-text", children: [s === "merged" ? (_jsxs(_Fragment, { children: [_jsx("a", { className: "gh-author", children: pr.mergedBy ?? pr.author }), " merged ", commitWord, " into ", ref(pr.baseBranch), " from ", ref(pr.headBranch), " ", _jsx(RelativeTime, { iso: pr.mergedAt })] })) : (_jsxs(_Fragment, { children: [_jsx("a", { className: "gh-author", children: pr.author }), " wants to merge ", commitWord, " into ", ref(pr.baseBranch), " from ", ref(pr.headBranch)] })), _jsx("button", { className: "gh-copy", "aria-label": "Copy head branch name to clipboard", onClick: () => void navigator.clipboard?.writeText(pr.headBranch ?? ""), children: _jsx(Octicon, { name: "copy" }) })] })] }), _jsxs("nav", { className: "gh-pull-tabs", "aria-label": "Pull request tabs", children: [tabs.map((t) => (_jsxs("a", { href: "#", className: `gh-pull-tab${tab === t.id ? " selected" : ""}`, "aria-current": tab === t.id ? "page" : undefined, onClick: (e) => { e.preventDefault(); onTab(t.id); }, children: [_jsx(Octicon, { name: t.icon }), " ", t.label, " ", _jsx("span", { className: "Counter", children: t.count })] }, t.id))), _jsx("span", { className: "gh-pull-tabs-fill" }), _jsx(DiffStat, { additions: pr.additions, deletions: pr.deletions })] })] }));
229
+ }
230
+ function association(login, pr, given) {
231
+ const out = [];
232
+ if (given && given !== "NONE")
233
+ out.push(given === "OWNER" ? "Owner" : given === "MEMBER" ? "Member" : given === "COLLABORATOR" ? "Collaborator" : given === "CONTRIBUTOR" ? "Contributor" : "");
234
+ else if (login && login === pr.repo.split("/")[0])
235
+ out.push("Owner");
236
+ if (login && login === pr.author)
237
+ out.push("Author");
238
+ return out.filter(Boolean);
239
+ }
240
+ function CommentBox({ login, when, body, pr, viewer, verb = "commented", given }) {
241
+ return (_jsxs("div", { className: "timeline-item gh-comment-item", children: [_jsx("span", { className: "gh-timeline-avatar", children: _jsx(Avatar, { login: login, size: 40 }) }), _jsxs("article", { className: `comment gh-comment${login && login === viewer ? " gh-comment-self" : ""}`, children: [_jsxs("header", { className: "gh-comment-header", children: [_jsxs("span", { children: [_jsx("a", { className: "gh-author", children: login }), " ", _jsxs("span", { className: "gh-muted", children: [verb, " ", _jsx(RelativeTime, { iso: when })] })] }), _jsxs("span", { className: "gh-comment-badges", children: [association(login, pr, given).map((b) => _jsx("span", { className: "gh-role", children: b }, b)), _jsx("button", { className: "gh-iconbtn gh-iconbtn-sm", "aria-label": "Show options", children: _jsx(Octicon, { name: "kebab-horizontal" }) })] })] }), _jsx("div", { className: "gh-comment-body gh-markdown", children: _jsx(Markdown, { text: body }) })] })] }));
242
+ }
243
+ function TimelineEvent({ icon, tone, children }) {
244
+ return (_jsxs("div", { className: "gh-event", children: [_jsx("span", { className: `gh-event-badge${tone ? ` ${tone}` : ""}`, children: _jsx(Octicon, { name: icon }) }), _jsx("div", { className: "gh-event-body", children: children })] }));
245
+ }
246
+ /** The Conversation tab's timeline, the merge box and the comment form. */
247
+ export function Conversation({ pr, reviews, commits, comments, reviewComments, checks = [], stateEvents, viewer, actions }) {
248
+ if (!pr)
249
+ return null;
250
+ const s = pullState(pr);
251
+ const entries = [];
252
+ const at = (iso) => { const t = Date.parse(iso ?? ""); return Number.isFinite(t) ? t : 0; };
253
+ if (commits.length) {
254
+ // commits pushed together read as one event: "<login> added N commits"
255
+ const by = commits[0].authorLogin ?? commits[0].author;
256
+ entries.push({
257
+ at: at(commits[0].date), key: "commits", node: (_jsxs("div", { className: "gh-commits-event", children: [_jsxs(TimelineEvent, { icon: "git-commit", children: [_jsx(Avatar, { login: by, size: 20 }), " ", _jsx("a", { className: "gh-author", children: by }), " ", _jsxs("span", { className: "gh-muted", children: ["added ", commits.length, " commit", commits.length === 1 ? "" : "s", " ", _jsx(RelativeTime, { iso: commits[0].date })] })] }), commits.map((c) => (_jsxs("div", { className: "gh-commit-line", children: [_jsx(Avatar, { login: c.authorLogin ?? c.author, size: 16 }), _jsx("span", { className: "gh-commit-message", children: (c.message ?? "").split("\n")[0] }), _jsx("span", { className: "gh-commit-status" }), _jsx("code", { className: "gh-sha", children: shortSha(c.sha) })] }, c.sha)))] })),
258
+ });
259
+ }
260
+ comments.forEach((c, i) => entries.push({ at: at(c.createdAt), key: `c-${c.id ?? i}`, node: _jsx(CommentBox, { login: c.author, when: c.createdAt, body: c.body, pr: pr, viewer: viewer, given: c.authorAssociation }) }));
261
+ reviews.forEach((r, i) => {
262
+ const st = String(r.state ?? "").toLowerCase();
263
+ const word = st === "approved" ? "approved these changes" : st === "changes_requested" ? "requested changes" : st === "dismissed" ? "reviewed (dismissed)" : "reviewed";
264
+ const tone = st === "approved" ? "approved" : st === "changes_requested" ? "changes" : "";
265
+ entries.push({
266
+ at: at(r.submittedAt), key: `r-${r.id ?? i}`, node: (_jsxs("div", { className: "review-card gh-review", children: [_jsxs(TimelineEvent, { icon: st === "approved" ? "check" : st === "changes_requested" ? "file-diff" : "eye", tone: tone, children: [_jsx(Avatar, { login: r.reviewer, size: 20 }), " ", _jsx("a", { className: "gh-author", children: r.reviewer }), " ", _jsxs("span", { className: "gh-muted", children: [word, " ", _jsx(RelativeTime, { iso: r.submittedAt })] })] }), r.body ? _jsx("div", { className: "gh-review-body", children: _jsx(CommentBox, { login: r.reviewer, when: r.submittedAt, body: r.body, pr: pr, viewer: viewer, verb: "left a comment" }) }) : null] })),
267
+ });
268
+ });
269
+ reviewComments.forEach((c, i) => entries.push({ at: at(c.createdAt), key: `rc-${c.id ?? i}`, node: _jsx(CommentBox, { login: c.author, when: c.createdAt, body: c.body, pr: pr, viewer: viewer, verb: `commented on ${c.path ?? "a file"}` }) }));
270
+ if (s === "merged")
271
+ entries.push({
272
+ at: at(pr.mergedAt), key: "merged", node: (_jsxs(TimelineEvent, { icon: "git-merge", tone: "merged", children: [_jsx(Avatar, { login: pr.mergedBy ?? pr.author, size: 20 }), " ", _jsx("a", { className: "gh-author", children: pr.mergedBy ?? pr.author }), " ", _jsx("span", { className: "gh-muted", children: "merged commit" }), " ", _jsx("code", { className: "gh-sha", children: shortSha(pr.mergeCommit) }), " ", _jsx("span", { className: "gh-muted", children: "into" }), " ", _jsx("span", { className: "gh-commit-ref", children: pr.baseBranch }), " ", _jsx("span", { className: "gh-muted", children: _jsx(RelativeTime, { iso: pr.mergedAt }) })] })),
273
+ });
274
+ // closed and reopened, from the timeline GitHub keeps (a merge closes too; the merged event says it)
275
+ const changes = stateEvents ?? (s === "closed" ? [{ event: "closed", at: pr.closedAt ?? pr.updatedAt }] : []);
276
+ changes.filter((e) => !(s === "merged" && e.event === "closed")).forEach((e, i) => {
277
+ const who = e.actor ?? pr.author;
278
+ entries.push({
279
+ at: at(e.at), key: `state-${i}`, node: (_jsxs(TimelineEvent, { icon: e.event === "closed" ? "git-pull-request-closed" : "git-pull-request", tone: e.event === "closed" ? "closed" : "reopened", children: [_jsx(Avatar, { login: who, size: 20 }), " ", _jsx("a", { className: "gh-author", children: who }), " ", _jsxs("span", { className: "gh-muted", children: [e.event === "closed" ? "closed" : "reopened", " this ", _jsx(RelativeTime, { iso: e.at })] })] })),
280
+ });
281
+ });
282
+ entries.sort((a, b) => a.at - b.at);
283
+ return (_jsxs("section", { className: "conversation gh-conversation", children: [_jsxs("div", { className: "gh-timeline", children: [_jsx(CommentBox, { login: pr.author, when: pr.createdAt, body: pr.body, pr: pr, viewer: viewer }), entries.map((e) => _jsx(React.Fragment, { children: e.node }, e.key))] }), _jsx(MergeBox, { pr: pr, reviews: reviews, checks: checks, actions: actions }), _jsx(CommentForm, { pr: pr, viewer: viewer, actions: actions })] }));
284
+ }
285
+ function MergeSection({ ok, tone, icon, title, detail, children }) {
286
+ return (_jsxs("div", { className: "gh-merge-section", children: [_jsx("span", { className: `gh-merge-section-icon ${tone ?? (ok ? "ok" : "warn")}`, children: _jsx(Octicon, { name: icon, size: 16 }) }), _jsxs("div", { className: "gh-merge-section-text", children: [_jsx("strong", { children: title }), detail ? _jsx("div", { className: "gh-muted", children: detail }) : null, children] })] }));
287
+ }
288
+ function MergeBox({ pr, reviews, checks, actions }) {
289
+ const [confirming, setConfirming] = useState(false);
290
+ const [title, setTitle] = useState("");
291
+ const [message, setMessage] = useState("");
292
+ const [busy, setBusy] = useState(false);
293
+ const [error, setError] = useState("");
294
+ const s = pullState(pr);
295
+ if (s === "merged") {
296
+ return (_jsxs("div", { className: "gh-mergebox", children: [_jsx("span", { className: "gh-mergebox-icon merged", children: _jsx(Octicon, { name: "git-merge", size: 24 }) }), _jsx("div", { className: "gh-mergebox-panel", children: _jsx(MergeSection, { tone: "merged", icon: "git-merge", title: "Pull request successfully merged and closed", detail: `You’re all set — the ${pr.headBranch} branch can be safely deleted.` }) })] }));
297
+ }
298
+ if (s === "closed") {
299
+ return (_jsxs("div", { className: "gh-mergebox", children: [_jsx("span", { className: "gh-mergebox-icon closed", children: _jsx(Octicon, { name: "git-pull-request-closed", size: 24 }) }), _jsx("div", { className: "gh-mergebox-panel", children: _jsx(MergeSection, { tone: "closed", icon: "git-pull-request-closed", title: "Closed with unmerged commits", detail: "This pull request is closed, but the branch has unmerged commits. You can reopen it to continue the review." }) })] }));
300
+ }
301
+ const decision = reviewDecision(pr, reviews);
302
+ const approvals = new Set(reviews.filter((r) => String(r.state).toLowerCase() === "approved").map((r) => r.reviewer)).size;
303
+ const failing = checks.filter((c) => ["failure", "error", "timed_out", "cancelled", "action_required"].includes(c.state));
304
+ const pending = checks.filter((c) => c.state === "pending" || c.state === "queued" || c.state === "in_progress");
305
+ const passed = checks.length - failing.length - pending.length;
306
+ const conflicts = pr.mergeable === false;
307
+ const plural = (n, w) => `${n} ${w}${n === 1 ? "" : "s"}`;
308
+ async function run(work) {
309
+ setBusy(true);
310
+ setError("");
311
+ try {
312
+ await work();
313
+ setConfirming(false);
314
+ }
315
+ catch (e) {
316
+ setError(e instanceof Error ? e.message : String(e));
317
+ }
318
+ finally {
319
+ setBusy(false);
320
+ }
321
+ }
322
+ return (_jsxs("div", { className: "gh-mergebox", children: [_jsx("span", { className: `gh-mergebox-icon ${conflicts || failing.length ? "neutral" : "open"}`, children: _jsx(Octicon, { name: "git-merge", size: 24 }) }), _jsxs("div", { className: "gh-mergebox-panel", children: [decision === undefined ? null : decision === "approved" ? (_jsx(MergeSection, { ok: true, icon: "check", title: "Changes approved", detail: `${plural(approvals, "approving review")} by reviewers with write access.` })) : decision === "changes_requested" ? (_jsx(MergeSection, { tone: "bad", icon: "file-diff", title: "Changes requested", detail: "A reviewer has requested changes to this pull request." })) : (_jsx(MergeSection, { tone: "warn", icon: "dot-fill", title: "Review requested", detail: `Review has been requested on this pull request. ${(pr.requestedReviewers ?? []).join(", ")} ${(pr.requestedReviewers ?? []).length === 1 ? "is" : "are"} requested.` })), checks.length ? (_jsx(MergeSection, { ok: !failing.length && !pending.length, tone: failing.length ? "bad" : pending.length ? "warn" : "ok", icon: failing.length ? "x" : pending.length ? "dot-fill" : "check", title: failing.length ? "Some checks were not successful" : pending.length ? "Some checks haven’t completed yet" : "All checks have passed", detail: [failing.length ? `${failing.length} failing` : "", pending.length ? `${pending.length} pending` : "", passed ? plural(passed, "successful check") : ""].filter(Boolean).join(", "), children: _jsx("ul", { className: "gh-checks-list", children: checks.map((c) => (_jsxs("li", { children: [_jsx("span", { className: `ci-status ${c.state === "success" ? "success" : c.state === "pending" ? "pending" : "failure"}`, children: _jsx(Octicon, { name: CI_ICON[c.state] ?? (c.state === "success" ? "check" : "x") }) }), _jsx("strong", { children: c.name }), c.description ? _jsxs("span", { className: "gh-muted", children: [" \u2014 ", c.description] }) : null, c.targetUrl ? _jsx("a", { className: "gh-check-details", href: c.targetUrl, target: "_blank", rel: "noreferrer", children: "Details" }) : null] }, c.name))) }) })) : null, conflicts
323
+ ? _jsx(MergeSection, { tone: "bad", icon: "x", title: "This branch has conflicts that must be resolved", detail: "Resolve conflicts then push again." })
324
+ : _jsx(MergeSection, { ok: true, icon: "check", title: "No conflicts with base branch", detail: "Merging can be performed automatically." }), _jsxs("div", { className: "gh-merge-actions", children: [confirming ? (_jsxs("div", { className: "gh-merge-confirm", children: [_jsx("input", { className: "gh-input", "aria-label": "Commit title", value: title, onChange: (e) => setTitle(e.target.value) }), _jsx("textarea", { className: "gh-input", "aria-label": "Commit message", value: message, onChange: (e) => setMessage(e.target.value), rows: 3 }), _jsxs("div", { className: "gh-merge-confirm-buttons", children: [_jsx("button", { className: "gh-btn gh-btn-primary", disabled: busy, onClick: () => void run(() => actions.merge(title, message)), children: busy ? "Merging…" : "Confirm merge" }), _jsx("button", { className: "gh-btn", disabled: busy, onClick: () => setConfirming(false), children: "Cancel" })] })] })) : (_jsxs("div", { className: "gh-merge-button-row", children: [_jsxs("span", { className: "gh-btn-group", children: [_jsx("button", { className: "gh-btn gh-btn-primary", disabled: conflicts || !actions, onClick: () => {
325
+ const [owner] = pr.repo.split("/");
326
+ setTitle(`Merge pull request #${pr.number} from ${owner}/${pr.headBranch}`);
327
+ setMessage(pr.title ?? "");
328
+ setConfirming(true);
329
+ }, children: "Merge pull request" }), _jsx("button", { className: "gh-btn gh-btn-primary gh-btn-caret", "aria-label": "Select merge method", disabled: conflicts || !actions, children: _jsx(Octicon, { name: "triangle-down" }) })] }), _jsx("span", { className: "gh-muted gh-merge-hint", children: "You can also merge this with the command line." })] })), error ? _jsx("div", { className: "gh-flash-error", role: "alert", children: error }) : null] })] })] }));
330
+ }
331
+ function CommentForm({ pr, viewer, actions }) {
332
+ const [body, setBody] = useState("");
333
+ const [busy, setBusy] = useState(false);
334
+ const [error, setError] = useState("");
335
+ const s = pullState(pr);
336
+ async function run(work, clear = true) {
337
+ setBusy(true);
338
+ setError("");
339
+ try {
340
+ await work();
341
+ if (clear)
342
+ setBody("");
343
+ }
344
+ catch (e) {
345
+ setError(e instanceof Error ? e.message : String(e));
346
+ }
347
+ finally {
348
+ setBusy(false);
349
+ }
350
+ }
351
+ return (_jsxs("div", { className: "timeline-item gh-comment-form-item", children: [_jsx("span", { className: "gh-timeline-avatar", children: _jsx(Avatar, { login: viewer, size: 40 }) }), _jsxs("div", { className: "gh-comment-form", children: [_jsx("h2", { className: "gh-comment-form-title", children: "Add a comment" }), _jsxs("div", { className: "gh-comment-form-box", children: [_jsxs("div", { className: "gh-comment-form-tabs", children: [_jsx("span", { className: "selected", children: "Write" }), _jsx("span", { children: "Preview" })] }), _jsx("textarea", { className: "gh-comment-textarea", "aria-label": "Markdown value", placeholder: "Use Markdown to format your comment", value: body, onChange: (e) => setBody(e.target.value) }), _jsxs("div", { className: "gh-comment-form-hint", children: [_jsx(Octicon, { name: "file-diff" }), " Markdown is supported"] })] }), error ? _jsx("div", { className: "gh-flash-error", role: "alert", children: error }) : null, _jsxs("div", { className: "gh-comment-form-actions", children: [s === "open" || s === "draft" ? (_jsxs("button", { className: "gh-btn", disabled: busy || !actions, onClick: () => void run(() => actions.close(body.trim() || undefined)), children: [_jsx(Octicon, { name: "git-pull-request-closed", className: "gh-closed-fg" }), " ", body.trim() ? "Close with comment" : "Close pull request"] })) : s === "closed" ? (_jsx("button", { className: "gh-btn", disabled: busy || !actions, onClick: () => void run(() => actions.reopen(), false), children: "Reopen pull request" })) : null, _jsx("button", { className: "gh-btn gh-btn-primary", disabled: busy || !body.trim() || !actions, onClick: () => void run(() => actions.comment(body.trim())), children: "Comment" })] })] })] }));
352
+ }
353
+ /** The sidebar: Reviewers, Assignees, Labels, Projects, Milestone, Development, Participants. */
354
+ export function Aside({ pr, reviews, comments = [], labelColors = {} }) {
355
+ if (!pr)
356
+ return _jsx("aside", { className: "sidebar" });
357
+ const latest = new Map();
358
+ for (const r of reviews)
359
+ if (r.reviewer)
360
+ latest.set(r.reviewer, String(r.state ?? "").toLowerCase());
361
+ const requested = (pr.requestedReviewers ?? []).filter((l) => !latest.has(l));
362
+ const participants = [...new Set([pr.author, ...reviews.map((r) => r.reviewer), ...comments.map((c) => c.author)].filter(Boolean))];
363
+ return (_jsxs("aside", { className: "sidebar gh-sidebar", children: [_jsxs("section", { className: "gh-sidebar-item", children: [_jsxs("h3", { children: ["Reviewers", _jsx(Octicon, { name: "gear" })] }), latest.size || requested.length ? (_jsxs("ul", { className: "gh-sidebar-list", children: [[...latest].map(([login, st]) => (_jsxs("li", { children: [_jsx(Avatar, { login: login, size: 20 }), " ", _jsx("a", { className: "gh-author", children: login }), _jsx("span", { className: `gh-review-state ${st}`, title: st, children: _jsx(Octicon, { name: st === "approved" ? "check" : st === "changes_requested" ? "file-diff" : "comment" }) })] }, login))), requested.map((login) => (_jsxs("li", { children: [_jsx(Avatar, { login: login, size: 20 }), " ", _jsx("a", { className: "gh-author", children: login }), _jsx("span", { className: "gh-review-state pending", title: "Awaiting requested review", children: _jsx(Octicon, { name: "dot-fill" }) })] }, login)))] })) : _jsx("p", { className: "gh-muted", children: "No reviews" })] }), _jsxs("section", { className: "gh-sidebar-item", children: [_jsxs("h3", { children: ["Assignees", _jsx(Octicon, { name: "gear" })] }), (pr.assignees ?? []).length ? _jsx("ul", { className: "gh-sidebar-list", children: (pr.assignees ?? []).map((a) => _jsxs("li", { children: [_jsx(Avatar, { login: a, size: 20 }), " ", _jsx("a", { className: "gh-author", children: a })] }, a)) })
364
+ : _jsxs("p", { className: "gh-muted", children: ["No one\u2014", _jsx("a", { children: "assign yourself" })] })] }), _jsxs("section", { className: "gh-sidebar-item", children: [_jsxs("h3", { children: ["Labels", _jsx(Octicon, { name: "gear" })] }), (pr.labels ?? []).length ? _jsx("div", { className: "gh-sidebar-labels", children: (pr.labels ?? []).map((l) => _jsx(Label, { name: l.name, color: labelColors[`${pr.repo}:${l.name}`] ?? l.color }, l.name)) }) : _jsx("p", { className: "gh-muted", children: "None yet" })] }), _jsxs("section", { className: "gh-sidebar-item", children: [_jsxs("h3", { children: ["Projects", _jsx(Octicon, { name: "gear" })] }), _jsx("p", { className: "gh-muted", children: "None yet" })] }), _jsxs("section", { className: "gh-sidebar-item", children: [_jsxs("h3", { children: ["Milestone", _jsx(Octicon, { name: "gear" })] }), _jsx("p", { className: "gh-muted", children: pr.milestone ?? "No milestone" })] }), _jsxs("section", { className: "gh-sidebar-item", children: [_jsxs("h3", { children: ["Development", _jsx(Octicon, { name: "gear" })] }), _jsx("p", { className: "gh-muted", children: "Successfully merging this pull request may close these issues." }), _jsx("p", { className: "gh-muted", children: "None yet" })] }), _jsxs("section", { className: "gh-sidebar-item", children: [_jsxs("h3", { className: "gh-sidebar-plain", children: [participants.length, " participant", participants.length === 1 ? "" : "s"] }), _jsx("div", { className: "gh-participants", children: participants.map((p) => _jsx(Avatar, { login: p, size: 26 }, p)) })] })] }));
365
+ }
366
+ // ── Commits, Checks and Files changed tabs ─────────────────────────────────────────────────────────────────────
367
+ function CommitsTab({ commits }) {
368
+ const days = new Map();
369
+ for (const c of commits) {
370
+ const day = c.date ? new Date(c.date).toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" }) : "";
371
+ days.set(day, [...(days.get(day) ?? []), c]);
372
+ }
373
+ if (!commits.length)
374
+ return _jsx("div", { className: "gh-blankslate", children: _jsx("h3", { children: "No commits" }) });
375
+ return (_jsx("div", { className: "gh-commits-tab", children: [...days].map(([day, list]) => (_jsxs("div", { className: "gh-commit-group", children: [_jsxs("h3", { className: "gh-commit-group-title", children: [_jsx(Octicon, { name: "git-commit" }), " Commits on ", day] }), _jsx("div", { className: "gh-box", children: list.map((c) => (_jsxs("div", { className: "gh-commit-row", children: [_jsxs("div", { children: [_jsx("div", { className: "gh-commit-row-title", children: (c.message ?? "").split("\n")[0] }), _jsxs("div", { className: "gh-commit-row-meta", children: [_jsx(Avatar, { login: c.authorLogin ?? c.author, size: 16 }), " ", _jsx("a", { className: "gh-author", children: c.authorLogin ?? c.author }), " ", _jsxs("span", { className: "gh-muted", children: ["committed ", _jsx(RelativeTime, { iso: c.date })] })] })] }), _jsxs("div", { className: "gh-commit-row-side", children: [_jsx("code", { className: "gh-sha gh-sha-btn", children: shortSha(c.sha) }), _jsx("button", { className: "gh-iconbtn gh-iconbtn-sm", "aria-label": "Copy full SHA", children: _jsx(Octicon, { name: "copy" }) }), _jsx("button", { className: "gh-iconbtn gh-iconbtn-sm", "aria-label": "Browse the repository at this point", children: _jsx(Octicon, { name: "code" }) })] })] }, c.sha))) })] }, day))) }));
376
+ }
377
+ function ChecksTab({ checks }) {
378
+ if (!checks.length)
379
+ return _jsxs("div", { className: "gh-blankslate", children: [_jsx(Octicon, { name: "checklist", size: 24 }), _jsx("h3", { children: "No checks" }), _jsx("p", { children: "This commit has no checks yet." })] });
380
+ return (_jsxs("div", { className: "gh-box gh-checks-tab", children: [_jsx("div", { className: "gh-box-header", children: _jsxs("strong", { children: [checks.length, " check", checks.length === 1 ? "" : "s"] }) }), checks.map((c) => (_jsxs("div", { className: "gh-check-row", children: [_jsx("span", { className: `ci-status ${c.state === "success" ? "success" : c.state === "pending" ? "pending" : "failure"}`, children: _jsx(Octicon, { name: CI_ICON[c.state] ?? "x" }) }), _jsx("strong", { children: c.name }), _jsx("span", { className: "gh-muted", children: c.description }), _jsx("span", { className: "gh-spacer" }), c.targetUrl ? _jsx("a", { href: c.targetUrl, target: "_blank", rel: "noreferrer", children: "Details" }) : null] }, c.name)))] }));
381
+ }
382
+ function diffLines(patch) {
383
+ const out = [];
384
+ let oldNo = 0, newNo = 0;
385
+ for (const line of patch.split("\n")) {
386
+ const m = /^@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@/.exec(line);
387
+ if (m) {
388
+ oldNo = Number(m[1]);
389
+ newNo = Number(m[2]);
390
+ out.push({ kind: "hunk", text: line });
391
+ continue;
392
+ }
393
+ if (line.startsWith("\\")) {
394
+ out.push({ kind: "note", text: line });
395
+ continue;
396
+ }
397
+ if (line.startsWith("+"))
398
+ out.push({ kind: "add", text: line.slice(1), newNo: newNo++ });
399
+ else if (line.startsWith("-"))
400
+ out.push({ kind: "del", text: line.slice(1), oldNo: oldNo++ });
401
+ else
402
+ out.push({ kind: "ctx", text: line.slice(1), oldNo: oldNo++, newNo: newNo++ });
403
+ }
404
+ return out;
405
+ }
406
+ /** The Files changed tab: each file's header (diffstat, path) and its unified diff with both line-number gutters. */
407
+ export function FilesDiff({ files }) {
408
+ if (!files.length)
409
+ return _jsx("div", { className: "gh-blankslate", children: _jsx("h3", { children: "No changes to show" }) });
410
+ return (_jsxs("section", { className: "files-diff gh-files", "data-testid": "files-diff", children: [_jsxs("div", { className: "gh-files-toolbar", children: [_jsxs("span", { className: "gh-select-menu", children: ["Changes from all commits ", _jsx(Octicon, { name: "triangle-down" })] }), _jsxs("span", { className: "gh-select-menu", children: ["File filter ", _jsx(Octicon, { name: "triangle-down" })] }), _jsxs("span", { className: "gh-select-menu", children: ["Conversations ", _jsx(Octicon, { name: "triangle-down" })] }), _jsx("span", { className: "gh-spacer" }), _jsxs("span", { className: "gh-muted", children: ["0 / ", files.length, " files viewed"] }), _jsxs("button", { className: "gh-btn gh-btn-sm gh-btn-primary", children: ["Review changes ", _jsx(Octicon, { name: "triangle-down" })] })] }), files.map((file) => (_jsxs("article", { className: "diff-file gh-file", children: [_jsxs("header", { className: "gh-file-header", children: [_jsx(Octicon, { name: "chevron-down" }), _jsx("span", { className: "gh-file-count", children: (file.additions ?? 0) + (file.deletions ?? 0) }), _jsx(DiffStat, { additions: file.additions, deletions: file.deletions }), _jsx("span", { className: "gh-file-name", children: file.filename }), _jsx("button", { className: "gh-iconbtn gh-iconbtn-sm", "aria-label": "Copy file name to clipboard", children: _jsx(Octicon, { name: "copy" }) }), _jsx("span", { className: "gh-spacer" }), _jsxs("label", { className: "gh-viewed", children: [_jsx("input", { type: "checkbox", readOnly: true }), " Viewed"] })] }), file.patch ? (_jsx("table", { className: "gh-diff-table", "data-testid": "diff-hunk", children: _jsx("tbody", { children: diffLines(file.patch).map((l, i) => l.kind === "hunk" || l.kind === "note" ? (_jsxs("tr", { className: `gh-diff-${l.kind}`, children: [_jsx("td", { className: "gh-diff-num", colSpan: 2 }), _jsx("td", { className: "gh-diff-code", children: l.text })] }, i)) : (_jsxs("tr", { className: `gh-diff-${l.kind}`, children: [_jsx("td", { className: "gh-diff-num", children: l.oldNo ?? "" }), _jsx("td", { className: "gh-diff-num", children: l.newNo ?? "" }), _jsxs("td", { className: "gh-diff-code", children: [_jsx("span", { className: "gh-diff-marker", children: l.kind === "add" ? "+" : l.kind === "del" ? "-" : " " }), l.text || "\u00a0"] })] }, i))) }) })) : _jsx("div", { className: "gh-file-nodiff gh-muted", children: file.status === "removed" ? "This file was deleted." : "Binary file not shown." })] }, file.filename)))] }));
411
+ }
412
+ export function PullPage({ pr, reviews, commits, comments, reviewComments, files, checks, stateEvents, viewer, labelColors, tab, onTab, actions }) {
413
+ if (!pr)
414
+ return _jsx("div", { className: "gh-container", children: _jsx("div", { className: "gh-blankslate", children: _jsx("h3", { children: "Loading pull request\u2026" }) }) });
415
+ return (_jsxs("div", { className: "gh-container gh-pull-page", children: [_jsx(PullHeader, { pr: pr, commits: commits.length, checks: checks.length, files: files.length, tab: tab, onTab: onTab }), tab === "conversation" ? (_jsxs("div", { className: "gh-pull-layout", children: [_jsx(Conversation, { pr: pr, reviews: reviews, commits: commits, comments: comments, reviewComments: reviewComments, checks: checks, stateEvents: stateEvents, viewer: viewer, actions: actions }), _jsx(Aside, { pr: pr, reviews: reviews, comments: comments, labelColors: labelColors })] })) : tab === "commits" ? _jsx(CommitsTab, { commits: commits })
416
+ : tab === "checks" ? _jsx(ChecksTab, { checks: checks })
417
+ : _jsx(FilesDiff, { files: files })] }));
418
+ }