pierre-review 0.1.69 → 0.1.71

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.
@@ -36,7 +36,7 @@ export async function activityRoutes(app) {
36
36
  });
37
37
  });
38
38
  // Mark the Activity Feed as seen (bumps the account's server-side "seen" marker to
39
- // now). Called when the user views the feed; resets the "new FYI since last here"
39
+ // now). Called when the user views the feed; resets the "new My Turn since last here"
40
40
  // count that drives the Welcome-back banner. Account-scoped; no body.
41
41
  app.post('/api/activity/feed/mark-seen', async (req) => {
42
42
  const at = await markFeedSeen(accountIdOf(req));
@@ -2,7 +2,7 @@ import { config } from '../../config.js';
2
2
  import { accountToLocalUser } from '../../auth/account.js';
3
3
  import { accountIdOf } from '../plugins/auth.js';
4
4
  import { EMPTY_CAPABILITIES, entitledProCapabilities, } from '../../pro/contract.js';
5
- import { getFyiProvider } from '../../feed/fyi-provider.js';
5
+ import { countNewMyTurnFeedItems } from '../../feed/my-turn.js';
6
6
  import { getAuthNotices } from '../../sync/auth-notices.js';
7
7
  import { dismissMyTurn, getCompletedDismissals, getFeedLastSeenAt, getMyTurn, markFeedSeen, undismissMyTurn, } from '../../db/queries.js';
8
8
  const dismissSchema = {
@@ -24,23 +24,18 @@ export async function meRoutes(app) {
24
24
  const accountId = accountIdOf(req);
25
25
  const user = accountToLocalUser(req.account);
26
26
  const myTurn = await getMyTurn(accountId);
27
- // Feed "seen" marker + how many FYI items are new since. Only counted once a
28
- // baseline exists (feedLastSeenAt set by the first feed view) so a fresh account
29
- // never sees a scary first-load number. FYI is a Pro capability the count comes
30
- // from the plugin's enricher (0 on the free tier, where there's no provider).
31
- // Per-account entitlement: local = full capabilities (unchanged); cloud = full only when
32
- // the account's plan isn't 'free' (Stripe billing seam). Computed once and reused for both
33
- // the FYI gate and the `pro` passthrough below.
27
+ // Feed "seen" marker + how many "My Turn" items are new since. Only counted once a
28
+ // baseline exists (feedLastSeenAt set by the first feed view) so a fresh account never
29
+ // sees a scary first-load number. "My Turn" is CORE / free now, so the count is computed
30
+ // directly (no capability gate) every tier gets the Welcome-back banner.
31
+ // Per-account entitlement (below): local = full capabilities; cloud = full only when the
32
+ // account's plan isn't 'free' (Stripe billing seam) used for the `pro` passthrough.
34
33
  const entitled = req.account
35
34
  ? entitledProCapabilities(req.account)
36
35
  : EMPTY_CAPABILITIES;
37
36
  const feedLastSeen = await getFeedLastSeenAt(accountId);
38
- const fyi = getFyiProvider();
39
- // FYI is the Pro `feedMyTurn` capability. Gate the count on ENTITLEMENT, not just provider
40
- // presence: once Pro loads in cloud the provider is registered process-globally for every
41
- // account, so a free cloud account would otherwise leak an FYI count it hasn't paid for.
42
- const newFeedItems = feedLastSeen && fyi && entitled.feedMyTurn
43
- ? await fyi.countNewSince(accountId, feedLastSeen)
37
+ const newFeedItems = feedLastSeen
38
+ ? await countNewMyTurnFeedItems(accountId, feedLastSeen)
44
39
  : 0;
45
40
  return {
46
41
  user,
@@ -14,7 +14,7 @@ const REASON_PRIORITY = [
14
14
  ];
15
15
  import { db, schema, isPg } from './client.js';
16
16
  import { runTransaction } from './client.js';
17
- import { getFyiProvider } from '../feed/fyi-provider.js';
17
+ import { enrichMyTurn } from '../feed/my-turn.js';
18
18
  import { resolvePrTickets } from '../pr/detail-enricher.js';
19
19
  import { config } from '../config.js';
20
20
  import { getProCapabilities } from '../pro/contract.js';
@@ -2394,7 +2394,7 @@ export async function getTeamInsights(accountId, window) {
2394
2394
  }
2395
2395
  // The consolidated Feed. Scoped to the account's WATCHED repos (inboxWatch); a passed
2396
2396
  // `repoIds` further narrows WITHIN watched (null → all watched). One flat, newest-first
2397
- // stream of real activity, each row flagged isMyTurn by participation (Pro enricher).
2397
+ // stream of real activity, each row flagged isMyTurn by participation (CORE; feed/my-turn.ts).
2398
2398
  export async function getConsolidatedFeed(accountId, opts = {}) {
2399
2399
  const { repoIds = null, userIds = null, limit = null, offset = 0, excludeBots = false, allowBotIds = null, } = opts;
2400
2400
  // Restrict to the account's WATCHED repos; a passed repoIds narrows within them. An
@@ -2423,11 +2423,9 @@ export async function getConsolidatedFeed(accountId, opts = {}) {
2423
2423
  // Claude Review runs surfaced as their own feed item kind (local-only; empty in cloud).
2424
2424
  getClaudeReviewFeedItems(accountId, effectiveRepoIds, feedSince),
2425
2425
  ]);
2426
- // The FYI / "My Turn" participation flag (isMyTurn / myTurnReasons / reasonTag) is a PAID
2427
- // capability computed by the @pierre/pro plugin's registered enricher (see fyi-provider.ts).
2428
- // Core builds every item as a PLAIN row (isMyTurn:false); the enricher, if present, flags
2429
- // them below — BEFORE the cap, so uncapped My-Turn rows survive. Without the plugin the feed
2430
- // stays a plain chronological stream.
2426
+ // The "My Turn" participation flag (isMyTurn / myTurnReasons / reasonTag) is CORE / free
2427
+ // (see feed/my-turn.ts). Core builds every item as a PLAIN row (isMyTurn:false), then
2428
+ // enrichMyTurn flags participation below BEFORE the cap, so uncapped My-Turn rows survive.
2431
2429
  const usersById = new Map();
2432
2430
  for (const u of feed.users)
2433
2431
  usersById.set(u.id, u);
@@ -2484,21 +2482,20 @@ export async function getConsolidatedFeed(accountId, opts = {}) {
2484
2482
  // Pushed as plain rows; the Pro enricher flags participation below.
2485
2483
  for (const it of commitItems)
2486
2484
  push(it);
2487
- // Claude Review items — a distinct kind (never bot/member-scoped). Kept out of the FYI
2485
+ // Claude Review items — a distinct kind (never bot/member-scoped). Kept out of the My-Turn
2488
2486
  // flow but always retained (see the caps below) so the "Claude Reviews" pill finds them.
2489
2487
  for (const it of claudeItems)
2490
2488
  push(it);
2491
2489
  // Consolidate a submitted review + the SAME actor's top-level PR comment(s) on the SAME PR
2492
2490
  // posted within a short window (issue comments carry no head SHA, so time is the proxy):
2493
2491
  // fold the comment(s) into the review's `mergedComments` and drop their standalone rows, so
2494
- // "review, then a summary comment" reads as one card everywhere (including FYI) instead of
2495
- // two. Runs BEFORE the FYI enrich/cap/paginate so `total`, participation and page bounds
2496
- // reflect the collapsed set (a comment folded here is never separately flagged or capped).
2492
+ // "review, then a summary comment" reads as one card everywhere (including My Turn) instead
2493
+ // of two. Runs BEFORE the my-turn enrich/cap/paginate so `total`, participation and page
2494
+ // bounds reflect the collapsed set (a comment folded here is never separately flagged/capped).
2497
2495
  coalesceReviewComments(items, byId);
2498
- // FYI / "My Turn" enrichment (Pro): flag each item `isMyTurn` by the viewer's participation
2499
- // in its PR. Runs BEFORE the cap so uncapped My-Turn rows survive. No-op (feed stays plain)
2500
- // when the plugin isn't loaded — the free tier.
2501
- await getFyiProvider()?.enrich(accountId, items);
2496
+ // "My Turn" enrichment (CORE / free): flag each item `isMyTurn` by the viewer's participation
2497
+ // in its PR. Runs BEFORE the cap so uncapped My-Turn rows survive.
2498
+ await enrichMyTurn(accountId, items);
2502
2499
  // Pure chronological — newest first. Keep every My Turn item AND every Claude-review item
2503
2500
  // (both are always relevant); cap the plain activity rows so a busy multi-repo account
2504
2501
  // doesn't render thousands of them.
@@ -35,7 +35,7 @@ export const accounts = pgTable('accounts', {
35
35
  // with no open tab stops being re-synced. Null until first activity.
36
36
  lastActiveAt: timestamp('last_active_at', { withTimezone: true, mode: 'date' }),
37
37
  // Last time this account viewed the Activity Feed — server-side "seen" marker (see the
38
- // sqlite twin). Drives "new FYI since you were last here"; null until the first view.
38
+ // sqlite twin). Drives "new My Turn since you were last here"; null until the first view.
39
39
  feedLastSeenAt: timestamp('feed_last_seen_at', { withTimezone: true, mode: 'date' }),
40
40
  // Billing plan ('free' | 'pro') — set only by the Stripe webhook. See the
41
41
  // sqlite twin. Kept in sync by hand (schema-parity.test.ts).
@@ -41,7 +41,7 @@ export const accounts = sqliteTable('accounts', {
41
41
  // with no open tab stops being re-synced. Null until first activity.
42
42
  lastActiveAt: integer('last_active_at', { mode: 'timestamp' }),
43
43
  // Last time this account VIEWED the Activity Feed — a server-side "seen" marker (the
44
- // successor to the removed per-item "Done"). Makes "new FYI since you were last here"
44
+ // successor to the removed per-item "Done"). Makes "new My Turn since you were last here"
45
45
  // server-truth, consistent across devices/sessions (vs the old client-only localStorage
46
46
  // heuristic). Bumped by POST /api/activity/feed/mark-seen; null until the first view,
47
47
  // so nothing counts as "new" until a baseline exists. One column, O(1) — no growth.
@@ -0,0 +1,147 @@
1
+ import { and, eq, gt, inArray, ne } from 'drizzle-orm';
2
+ import { db, schema } from '../db/client.js';
3
+ import { getAccountUserId } from '../auth/account.js';
4
+ // Participation over a candidate set of ACCOUNT-OWNED PR ids (they come from the account-scoped
5
+ // feed): the subset the viewer authored, merged, is a requested reviewer on, or previously
6
+ // reviewed / commented on. Isolation rides on prId ∈ the account-owned candidate set, so the
7
+ // child tables need no accountId predicate. The sets are broken out to colour the reason pills.
8
+ async function resolveParticipation(accountId, localUserId, prIds) {
9
+ const authored = new Set();
10
+ const requested = new Set();
11
+ const reviewed = new Set();
12
+ const commented = new Set();
13
+ const merged = new Set();
14
+ const all = new Set();
15
+ if (localUserId == null || prIds.length === 0)
16
+ return { all, authored, requested, reviewed, commented, merged };
17
+ const { pullRequests, reviewRequests, reviews, prComments, reviewComments } = schema;
18
+ for (const row of await db
19
+ .select({ id: pullRequests.id })
20
+ .from(pullRequests)
21
+ .where(and(eq(pullRequests.accountId, accountId), eq(pullRequests.authorId, localUserId), inArray(pullRequests.id, prIds)))
22
+ .execute()) {
23
+ authored.add(row.id);
24
+ all.add(row.id);
25
+ }
26
+ // A merge is a strong "I own the outcome of this" signal, so later activity is my turn.
27
+ for (const row of await db
28
+ .select({ id: pullRequests.id })
29
+ .from(pullRequests)
30
+ .where(and(eq(pullRequests.accountId, accountId), eq(pullRequests.mergedById, localUserId), inArray(pullRequests.id, prIds)))
31
+ .execute()) {
32
+ merged.add(row.id);
33
+ all.add(row.id);
34
+ }
35
+ for (const row of await db
36
+ .select({ prId: reviewRequests.prId })
37
+ .from(reviewRequests)
38
+ .where(and(eq(reviewRequests.userId, localUserId), inArray(reviewRequests.prId, prIds)))
39
+ .execute()) {
40
+ requested.add(row.prId);
41
+ all.add(row.prId);
42
+ }
43
+ for (const row of await db
44
+ .select({ prId: reviews.prId })
45
+ .from(reviews)
46
+ .where(and(eq(reviews.authorId, localUserId), inArray(reviews.prId, prIds)))
47
+ .execute()) {
48
+ reviewed.add(row.prId);
49
+ all.add(row.prId);
50
+ }
51
+ for (const row of await db
52
+ .select({ prId: prComments.prId })
53
+ .from(prComments)
54
+ .where(and(eq(prComments.authorId, localUserId), inArray(prComments.prId, prIds)))
55
+ .execute()) {
56
+ commented.add(row.prId);
57
+ all.add(row.prId);
58
+ }
59
+ for (const row of await db
60
+ .select({ prId: reviewComments.prId })
61
+ .from(reviewComments)
62
+ .where(and(eq(reviewComments.authorId, localUserId), inArray(reviewComments.prId, prIds)))
63
+ .execute()) {
64
+ commented.add(row.prId);
65
+ all.add(row.prId);
66
+ }
67
+ return { all, authored, requested, reviewed, commented, merged };
68
+ }
69
+ // Ordered reason pills for a my-turn card (most-relevant first; the UI shows the primary).
70
+ // Empty when the viewer doesn't participate.
71
+ function myTurnReasonsFor(participation, prId) {
72
+ if (prId == null)
73
+ return [];
74
+ const reasons = [];
75
+ if (participation.requested.has(prId))
76
+ reasons.push('requested');
77
+ if (participation.authored.has(prId))
78
+ reasons.push('authored');
79
+ if (participation.merged.has(prId))
80
+ reasons.push('merged');
81
+ if (participation.reviewed.has(prId))
82
+ reasons.push('reviewed');
83
+ if (participation.commented.has(prId))
84
+ reasons.push('commented');
85
+ return reasons;
86
+ }
87
+ // The coarse badge colour: a requested review is "awaiting your review"; activity on your own
88
+ // PR is "your PR, new comments"; otherwise none.
89
+ function reasonTagFor(participation, prId) {
90
+ if (prId == null)
91
+ return null;
92
+ if (participation.requested.has(prId))
93
+ return 'awaiting_your_review';
94
+ if (participation.authored.has(prId))
95
+ return 'your_pr_new_comments';
96
+ return null;
97
+ }
98
+ // Flag each feed item `isMyTurn` by the viewer's participation in its PR. Mutates the items in
99
+ // place. Runs BEFORE the feed's cap so uncapped my-turn rows survive. Claude Review items stay
100
+ // OUT of the my-turn flow (their own lane). No-op when there's no viewer identity (offline /
101
+ // not-yet-synced) → the feed stays a plain chronological stream.
102
+ export async function enrichMyTurn(accountId, items) {
103
+ const flaggable = items.filter((i) => i.kind !== 'claude_review');
104
+ const candidatePrIds = [
105
+ ...new Set(flaggable.map((i) => i.prId).filter((x) => x != null)),
106
+ ];
107
+ if (candidatePrIds.length === 0)
108
+ return;
109
+ const localUserId = await getAccountUserId(accountId);
110
+ if (localUserId == null)
111
+ return;
112
+ const participation = await resolveParticipation(accountId, localUserId, candidatePrIds);
113
+ for (const it of flaggable) {
114
+ const mine = it.prId != null && it.actorId !== localUserId && participation.all.has(it.prId);
115
+ if (!mine)
116
+ continue;
117
+ it.isMyTurn = true;
118
+ it.myTurnReasons = myTurnReasonsFor(participation, it.prId);
119
+ it.reasonTag = reasonTagFor(participation, it.prId);
120
+ }
121
+ }
122
+ // How many "My Turn" feed items are NEW since `since` — activity events (all feed types except
123
+ // plain commit pushes) after `since`, on a PR the viewer participates in, by someone other than
124
+ // the viewer. Drives the Welcome-back banner count (/api/me). Cheap because `since` is normally
125
+ // recent. 0 when there's no viewer identity or no matching rows.
126
+ export async function countNewMyTurnFeedItems(accountId, since) {
127
+ const localUserId = await getAccountUserId(accountId);
128
+ if (localUserId == null)
129
+ return 0;
130
+ const { events } = schema;
131
+ const rows = await db
132
+ .select({ prId: events.prId, actorId: events.actorId })
133
+ .from(events)
134
+ .where(and(eq(events.accountId, accountId), gt(events.occurredAt, since), ne(events.type, 'commit_pushed')))
135
+ .execute();
136
+ const prIds = [...new Set(rows.map((r) => r.prId).filter((x) => x != null))];
137
+ if (prIds.length === 0)
138
+ return 0;
139
+ const participation = await resolveParticipation(accountId, localUserId, prIds);
140
+ let n = 0;
141
+ for (const r of rows) {
142
+ if (r.prId != null && r.actorId !== localUserId && participation.all.has(r.prId))
143
+ n++;
144
+ }
145
+ return n;
146
+ }
147
+ //# sourceMappingURL=my-turn.js.map
package/dist/pro/bind.js CHANGED
@@ -8,7 +8,6 @@ import * as hostQueries from '../db/queries.js';
8
8
  import { recordAiUsage, getAiUsageSummary } from '../db/usage.js';
9
9
  import { aiCreditStatus } from '../db/credits.js';
10
10
  import { reviewEvents, registerLearningsProvider } from '../review/events.js';
11
- import { registerFyiProvider } from '../feed/fyi-provider.js';
12
11
  import { registerScheduledJob } from '../sync/scheduled-jobs.js';
13
12
  import { registerPrDetailEnricher } from '../pr/detail-enricher.js';
14
13
  import { cheapComplete } from '../review/llm.js';
@@ -54,7 +53,7 @@ export async function bindProPlugin(app) {
54
53
  if (!mod)
55
54
  return;
56
55
  const plugin = (mod.default ?? mod);
57
- if (plugin?.apiVersion !== 10 || typeof plugin.register !== 'function') {
56
+ if (plugin?.apiVersion !== 11 || typeof plugin.register !== 'function') {
58
57
  app.log.warn({ apiVersion: plugin?.apiVersion }, 'pro contract mismatch — skipped');
59
58
  return;
60
59
  }
@@ -107,7 +106,6 @@ export async function bindProPlugin(app) {
107
106
  },
108
107
  reviewEvents,
109
108
  registerLearningsProvider,
110
- registerFyiProvider,
111
109
  registerScheduledJob,
112
110
  registerPrDetailEnricher,
113
111
  // AI Fix infra (per-account, cloud-ready). The host owns the security-sensitive
@@ -9,7 +9,6 @@ export const EMPTY_CAPABILITIES = {
9
9
  aiFix: false,
10
10
  teamInsights: false,
11
11
  claudeReview: false,
12
- feedMyTurn: false,
13
12
  slackDigest: false,
14
13
  issueLinks: false,
15
14
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pierre-review",
3
- "version": "0.1.69",
3
+ "version": "0.1.71",
4
4
  "description": "Dashboard for tracking your team's GitHub PR activity across repos — local (SQLite + gh) or self-hosted multi-tenant cloud (Postgres + GitHub App).",
5
5
  "type": "module",
6
6
  "author": "Alex Wakeman",
@@ -1,3 +1,3 @@
1
- import{r as o,j as e}from"./tanstack-R-s9abdD.js";import{D as te,w as W,A as re,c as ne,d as ae,q as ie,G as ce,I as J,M as K,i as le,l as oe,k as de,C as ue,b as xe,R as Q,a as he,p as X,F as Z,r as I,E as ge,J as me,n as be,m as pe,y as fe,j as Y}from"./index-DNiwLu1C.js";import"./markdown-BUNN1sk6.js";import"./highlight-C8vPehS1.js";import"./vis-C3BCkP5U.js";const S="whitespace-nowrap rounded border border-blue-400 px-2.5 py-1 text-xs text-blue-600 hover:bg-blue-50 disabled:opacity-50 dark:border-blue-600 dark:text-blue-400 dark:hover:bg-blue-900/30",j="whitespace-nowrap rounded border border-gray-300 px-2.5 py-1 text-xs hover:border-gray-400 disabled:opacity-50 dark:border-gray-700 dark:hover:border-gray-500",ye={fetching_diff:"Reading the PR",cloning:"Checking out the code",fixing:"Applying the fix",capturing:"Capturing changes",persisting:"Saving"};function je(s){var t;if(!s||s.status==="idle")return null;if(s.status==="queued")return 6;const r=s.progress;if(!r)return 10;switch(r.phase){case"fetching_diff":return 12;case"cloning":return 28;case"fixing":return Math.min(90,45+(((t=r.recentActivity)==null?void 0:t.length)??0)*3);case"capturing":return 92;case"persisting":return 96;default:return 20}}const ke={cloning:"Checking out the code",applying_fix:"Applying the fix",fetching_trunk:"Fetching the trunk",rebasing:"Rebasing onto the trunk",merging:"Merging the trunk in",resolving_conflicts:"Resolving conflicts with Claude",verifying:"Verifying the result",pushing:"Pushing"};function Ne(s){var t;if(!s||s.status==="idle")return null;if(s.status==="queued")return 6;const r=s.progress;if(!r)return 10;switch(r.phase){case"cloning":return 15;case"applying_fix":return 25;case"fetching_trunk":return 35;case"rebasing":case"merging":return 45;case"resolving_conflicts":return Math.min(90,55+(((t=r.recentActivity)==null?void 0:t.length)??0)*3);case"verifying":return 92;case"pushing":return 96;default:return 20}}function L({children:s}){return e.jsx("div",{className:"px-4 pb-1 pt-3 text-xs font-semibold uppercase tracking-wide text-gray-400",children:s})}const ve={high:"bg-green-100 text-green-700 dark:bg-green-950/40 dark:text-green-300",medium:"bg-amber-100 text-amber-700 dark:bg-amber-950/40 dark:text-amber-300",low:"bg-red-100 text-red-700 dark:bg-red-950/40 dark:text-red-300"};function z({label:s,value:r}){return r?e.jsxs("span",{className:`rounded px-1.5 py-0.5 text-[10px] font-medium uppercase ${ve[r]}`,title:s==="Fixability"?"How confident the analysis is that Pierre's agent could fix this":"How confident the analysis is about the root cause",children:[s,": ",r]}):null}function De({pr:s}){const{aiAnalysis:r,aiFix:t}=te(),n=W(x=>x.aiFixTabFocus),i=W(x=>x.consumeAiFixTabFocus),[c,m]=o.useState(null);return o.useEffect(()=>{n&&n.prId===s.id&&(n.reviewText&&m(n.reviewText),i())},[n,s.id,i]),!r&&!t?e.jsx("div",{className:"p-4 text-sm text-gray-500",children:"AI Analysis and Fix is not enabled."}):e.jsxs("div",{className:"pb-6",children:[r&&e.jsx("div",{className:"border-b border-gray-200 dark:border-gray-800",children:e.jsx(re,{pr:s})}),e.jsx(Ce,{pr:s}),r&&e.jsx(Pe,{pr:s,canFix:t}),t&&e.jsx(Re,{pr:s,seedReviewText:c,onSeedConsumed:()=>m(null)})]})}function Ce({pr:s}){const r=s.checkRuns;return r.length===0?null:e.jsxs("div",{className:"border-b border-gray-200 dark:border-gray-800",children:[e.jsx(L,{children:"CI status"}),e.jsxs("div",{className:"px-4 pb-3",children:[e.jsx(ne,{prId:s.id,checks:r}),e.jsx(ae,{prId:s.id,checks:r,viewerCanPush:s.viewerCanPush})]})]})}function Pe({pr:s,canFix:r}){const{data:t}=ie(s.id,!0),n=ce(s.id),i=J(s.id),c=o.useMemo(()=>s.checkRuns.filter(l=>l.state==="failure"||l.state==="error").map(l=>({name:l.name,jobId:l.jobId,state:l.state})),[s.checkRuns]);if(!(c.length>0||((t==null?void 0:t.hasFailures)??!1))&&!(t!=null&&t.analysis))return null;const x=(t==null?void 0:t.fixability)==="low";return e.jsxs("div",{className:"border-b border-gray-200 dark:border-gray-800",children:[e.jsx(L,{children:"CI failure analysis"}),e.jsxs("div",{className:"px-4 pb-3",children:[(t==null?void 0:t.analysis)&&(t.rootCauseConfidence||t.fixability)&&e.jsxs("div",{className:"mb-1.5 flex flex-wrap items-center gap-1.5",children:[e.jsx("span",{className:"text-[10px] font-semibold uppercase tracking-wide text-gray-400",title:"How confident the analysis is — not a severity rating",children:"Confidence"}),e.jsx(z,{label:"Root cause",value:t.rootCauseConfidence}),e.jsx(z,{label:"Fixability",value:t.fixability})]}),t!=null&&t.analysis?e.jsx("div",{className:"prose prose-sm max-w-none dark:prose-invert",children:e.jsx(K,{children:t.analysis})}):e.jsx("p",{className:"text-sm text-gray-500",children:"Diagnose the failing CI: likely root cause + a suggested fix, from the full logs of every failing check."}),e.jsxs("div",{className:"mt-2 flex flex-wrap items-center gap-2",children:[e.jsx("button",{type:"button",className:j,disabled:n.isPending||c.length===0,onClick:()=>n.mutate(c),children:n.isPending?"Analyzing…":t!=null&&t.analysis?"Re-analyze":"Analyze CI failure"}),r&&(t==null?void 0:t.analysis)&&e.jsx("button",{type:"button",className:S,disabled:i.isPending,onClick:()=>i.mutate({model:"claude-sonnet-5",seed:"ci_analysis"}),title:"Launch an agent to fix the CI failure",children:"Fix it →"}),r&&x&&(t==null?void 0:t.analysis)&&e.jsx("span",{className:"text-[11px] text-gray-500",children:"low confidence this is auto-fixable"}),n.isError&&e.jsx("span",{className:"text-[11px] text-red-500",children:D(n.error)})]})]})]})}function Re({pr:s,seedReviewText:r,onSeedConsumed:t}){var k,F,N,A;const{data:n,isLoading:i}=le(s.id,!0),[c,m]=o.useState("claude-sonnet-5"),x=J(s.id),l=oe(s.id),p=((k=n==null?void 0:n.fix)==null?void 0:k.status)??null,h=p==="running"||p==="queued"||x.isPending,{status:d}=de(s.id,h),R=(d==null?void 0:d.status)??p??"idle",f=R==="running"||R==="queued",w=r?"review":"plain",M=()=>{x.mutate({model:c,seed:w,reviewText:r??void 0},{onSuccess:()=>t()})},u=(n==null?void 0:n.fix)??null;return e.jsxs("div",{children:[e.jsx(L,{children:"AI Fix"}),e.jsxs("div",{className:"px-4",children:[(n==null?void 0:n.enabled)===!1?e.jsx("p",{className:"text-sm text-gray-500",children:"The agentic fixer is disabled."}):(n==null?void 0:n.auth)==="none"?e.jsx("p",{className:"rounded border border-amber-300 bg-amber-50 p-2 text-xs text-amber-700 dark:border-amber-700 dark:bg-amber-950/30 dark:text-amber-300",children:n.authMessage??"No Claude authentication found. Sign in to Claude or set an API key, then restart."}):e.jsxs(e.Fragment,{children:[r&&!f&&e.jsx("div",{className:"mb-2 rounded border border-blue-200 bg-blue-50 p-2 text-xs text-blue-700 dark:border-blue-800 dark:bg-blue-950/30 dark:text-blue-300",children:"Ready to generate a fix from the selected review."}),e.jsxs("div",{className:"flex flex-wrap items-center gap-2",children:[e.jsx("select",{className:"rounded border border-gray-300 bg-transparent px-2 py-1 text-xs dark:border-gray-700",value:c,onChange:y=>m(y.target.value),disabled:f,children:ue.map(y=>e.jsx("option",{value:y,children:xe[y]},y))}),f?e.jsx("button",{type:"button",className:j,onClick:()=>l.mutate(),children:"Cancel"}):e.jsx("button",{type:"button",className:S,disabled:x.isPending,onClick:M,children:u?"Generate new fix":"Generate fix"}),x.isError&&e.jsx("span",{className:"text-[11px] text-red-500",children:D(x.error)})]}),f&&e.jsxs("div",{className:"mt-3",children:[e.jsx(Q,{active:!0,label:"Running AI fix",value:je(d),timeConstantSec:40}),e.jsx("div",{className:"mt-1 text-[11px] text-gray-500",children:ye[((F=d==null?void 0:d.progress)==null?void 0:F.phase)??""]??"Working…"}),((N=d==null?void 0:d.progress)==null?void 0:N.recentActivity)&&d.progress.recentActivity.length>0&&e.jsx("pre",{className:"mt-1 max-h-32 overflow-auto rounded bg-gray-50 p-2 text-[11px] leading-relaxed text-gray-500 dark:bg-gray-900",children:d.progress.recentActivity.slice(-8).join(`
1
+ import{r as o,j as e}from"./tanstack-R-s9abdD.js";import{D as te,w as W,A as re,c as ne,d as ae,q as ie,G as ce,I as J,M as K,i as le,l as oe,k as de,C as ue,b as xe,R as Q,a as he,p as X,F as Z,r as I,E as ge,J as me,n as be,m as pe,y as fe,j as Y}from"./index-BplNgl1k.js";import"./markdown-BUNN1sk6.js";import"./highlight-C8vPehS1.js";import"./vis-C3BCkP5U.js";const S="whitespace-nowrap rounded border border-blue-400 px-2.5 py-1 text-xs text-blue-600 hover:bg-blue-50 disabled:opacity-50 dark:border-blue-600 dark:text-blue-400 dark:hover:bg-blue-900/30",j="whitespace-nowrap rounded border border-gray-300 px-2.5 py-1 text-xs hover:border-gray-400 disabled:opacity-50 dark:border-gray-700 dark:hover:border-gray-500",ye={fetching_diff:"Reading the PR",cloning:"Checking out the code",fixing:"Applying the fix",capturing:"Capturing changes",persisting:"Saving"};function je(s){var t;if(!s||s.status==="idle")return null;if(s.status==="queued")return 6;const r=s.progress;if(!r)return 10;switch(r.phase){case"fetching_diff":return 12;case"cloning":return 28;case"fixing":return Math.min(90,45+(((t=r.recentActivity)==null?void 0:t.length)??0)*3);case"capturing":return 92;case"persisting":return 96;default:return 20}}const ke={cloning:"Checking out the code",applying_fix:"Applying the fix",fetching_trunk:"Fetching the trunk",rebasing:"Rebasing onto the trunk",merging:"Merging the trunk in",resolving_conflicts:"Resolving conflicts with Claude",verifying:"Verifying the result",pushing:"Pushing"};function Ne(s){var t;if(!s||s.status==="idle")return null;if(s.status==="queued")return 6;const r=s.progress;if(!r)return 10;switch(r.phase){case"cloning":return 15;case"applying_fix":return 25;case"fetching_trunk":return 35;case"rebasing":case"merging":return 45;case"resolving_conflicts":return Math.min(90,55+(((t=r.recentActivity)==null?void 0:t.length)??0)*3);case"verifying":return 92;case"pushing":return 96;default:return 20}}function L({children:s}){return e.jsx("div",{className:"px-4 pb-1 pt-3 text-xs font-semibold uppercase tracking-wide text-gray-400",children:s})}const ve={high:"bg-green-100 text-green-700 dark:bg-green-950/40 dark:text-green-300",medium:"bg-amber-100 text-amber-700 dark:bg-amber-950/40 dark:text-amber-300",low:"bg-red-100 text-red-700 dark:bg-red-950/40 dark:text-red-300"};function z({label:s,value:r}){return r?e.jsxs("span",{className:`rounded px-1.5 py-0.5 text-[10px] font-medium uppercase ${ve[r]}`,title:s==="Fixability"?"How confident the analysis is that Pierre's agent could fix this":"How confident the analysis is about the root cause",children:[s,": ",r]}):null}function De({pr:s}){const{aiAnalysis:r,aiFix:t}=te(),n=W(x=>x.aiFixTabFocus),i=W(x=>x.consumeAiFixTabFocus),[c,m]=o.useState(null);return o.useEffect(()=>{n&&n.prId===s.id&&(n.reviewText&&m(n.reviewText),i())},[n,s.id,i]),!r&&!t?e.jsx("div",{className:"p-4 text-sm text-gray-500",children:"AI Analysis and Fix is not enabled."}):e.jsxs("div",{className:"pb-6",children:[r&&e.jsx("div",{className:"border-b border-gray-200 dark:border-gray-800",children:e.jsx(re,{pr:s})}),e.jsx(Ce,{pr:s}),r&&e.jsx(Pe,{pr:s,canFix:t}),t&&e.jsx(Re,{pr:s,seedReviewText:c,onSeedConsumed:()=>m(null)})]})}function Ce({pr:s}){const r=s.checkRuns;return r.length===0?null:e.jsxs("div",{className:"border-b border-gray-200 dark:border-gray-800",children:[e.jsx(L,{children:"CI status"}),e.jsxs("div",{className:"px-4 pb-3",children:[e.jsx(ne,{prId:s.id,checks:r}),e.jsx(ae,{prId:s.id,checks:r,viewerCanPush:s.viewerCanPush})]})]})}function Pe({pr:s,canFix:r}){const{data:t}=ie(s.id,!0),n=ce(s.id),i=J(s.id),c=o.useMemo(()=>s.checkRuns.filter(l=>l.state==="failure"||l.state==="error").map(l=>({name:l.name,jobId:l.jobId,state:l.state})),[s.checkRuns]);if(!(c.length>0||((t==null?void 0:t.hasFailures)??!1))&&!(t!=null&&t.analysis))return null;const x=(t==null?void 0:t.fixability)==="low";return e.jsxs("div",{className:"border-b border-gray-200 dark:border-gray-800",children:[e.jsx(L,{children:"CI failure analysis"}),e.jsxs("div",{className:"px-4 pb-3",children:[(t==null?void 0:t.analysis)&&(t.rootCauseConfidence||t.fixability)&&e.jsxs("div",{className:"mb-1.5 flex flex-wrap items-center gap-1.5",children:[e.jsx("span",{className:"text-[10px] font-semibold uppercase tracking-wide text-gray-400",title:"How confident the analysis is — not a severity rating",children:"Confidence"}),e.jsx(z,{label:"Root cause",value:t.rootCauseConfidence}),e.jsx(z,{label:"Fixability",value:t.fixability})]}),t!=null&&t.analysis?e.jsx("div",{className:"prose prose-sm max-w-none dark:prose-invert",children:e.jsx(K,{children:t.analysis})}):e.jsx("p",{className:"text-sm text-gray-500",children:"Diagnose the failing CI: likely root cause + a suggested fix, from the full logs of every failing check."}),e.jsxs("div",{className:"mt-2 flex flex-wrap items-center gap-2",children:[e.jsx("button",{type:"button",className:j,disabled:n.isPending||c.length===0,onClick:()=>n.mutate(c),children:n.isPending?"Analyzing…":t!=null&&t.analysis?"Re-analyze":"Analyze CI failure"}),r&&(t==null?void 0:t.analysis)&&e.jsx("button",{type:"button",className:S,disabled:i.isPending,onClick:()=>i.mutate({model:"claude-sonnet-5",seed:"ci_analysis"}),title:"Launch an agent to fix the CI failure",children:"Fix it →"}),r&&x&&(t==null?void 0:t.analysis)&&e.jsx("span",{className:"text-[11px] text-gray-500",children:"low confidence this is auto-fixable"}),n.isError&&e.jsx("span",{className:"text-[11px] text-red-500",children:D(n.error)})]})]})]})}function Re({pr:s,seedReviewText:r,onSeedConsumed:t}){var k,F,N,A;const{data:n,isLoading:i}=le(s.id,!0),[c,m]=o.useState("claude-sonnet-5"),x=J(s.id),l=oe(s.id),p=((k=n==null?void 0:n.fix)==null?void 0:k.status)??null,h=p==="running"||p==="queued"||x.isPending,{status:d}=de(s.id,h),R=(d==null?void 0:d.status)??p??"idle",f=R==="running"||R==="queued",w=r?"review":"plain",M=()=>{x.mutate({model:c,seed:w,reviewText:r??void 0},{onSuccess:()=>t()})},u=(n==null?void 0:n.fix)??null;return e.jsxs("div",{children:[e.jsx(L,{children:"AI Fix"}),e.jsxs("div",{className:"px-4",children:[(n==null?void 0:n.enabled)===!1?e.jsx("p",{className:"text-sm text-gray-500",children:"The agentic fixer is disabled."}):(n==null?void 0:n.auth)==="none"?e.jsx("p",{className:"rounded border border-amber-300 bg-amber-50 p-2 text-xs text-amber-700 dark:border-amber-700 dark:bg-amber-950/30 dark:text-amber-300",children:n.authMessage??"No Claude authentication found. Sign in to Claude or set an API key, then restart."}):e.jsxs(e.Fragment,{children:[r&&!f&&e.jsx("div",{className:"mb-2 rounded border border-blue-200 bg-blue-50 p-2 text-xs text-blue-700 dark:border-blue-800 dark:bg-blue-950/30 dark:text-blue-300",children:"Ready to generate a fix from the selected review."}),e.jsxs("div",{className:"flex flex-wrap items-center gap-2",children:[e.jsx("select",{className:"rounded border border-gray-300 bg-transparent px-2 py-1 text-xs dark:border-gray-700",value:c,onChange:y=>m(y.target.value),disabled:f,children:ue.map(y=>e.jsx("option",{value:y,children:xe[y]},y))}),f?e.jsx("button",{type:"button",className:j,onClick:()=>l.mutate(),children:"Cancel"}):e.jsx("button",{type:"button",className:S,disabled:x.isPending,onClick:M,children:u?"Generate new fix":"Generate fix"}),x.isError&&e.jsx("span",{className:"text-[11px] text-red-500",children:D(x.error)})]}),f&&e.jsxs("div",{className:"mt-3",children:[e.jsx(Q,{active:!0,label:"Running AI fix",value:je(d),timeConstantSec:40}),e.jsx("div",{className:"mt-1 text-[11px] text-gray-500",children:ye[((F=d==null?void 0:d.progress)==null?void 0:F.phase)??""]??"Working…"}),((N=d==null?void 0:d.progress)==null?void 0:N.recentActivity)&&d.progress.recentActivity.length>0&&e.jsx("pre",{className:"mt-1 max-h-32 overflow-auto rounded bg-gray-50 p-2 text-[11px] leading-relaxed text-gray-500 dark:bg-gray-900",children:d.progress.recentActivity.slice(-8).join(`
2
2
  `)})]}),!f&&u&&e.jsx(we,{pr:s,fix:u,headInfo:(n==null?void 0:n.headInfo)??null,viewerCanPush:(n==null?void 0:n.viewerCanPush)??!1}),i&&!u&&e.jsx("p",{className:"mt-3 text-xs text-gray-400",children:"Loading…"})]}),e.jsx(Fe,{history:(n==null?void 0:n.history)??[],currentFixId:((A=n==null?void 0:n.fix)==null?void 0:A.id)??null})]})]})}function we({pr:s,fix:r,headInfo:t,viewerCanPush:n}){const i=o.useMemo(()=>X(r.patch),[r.patch]);if(r.status==="failed")return e.jsxs("div",{className:"mt-3 rounded border border-red-200 bg-red-50 p-2 text-xs text-red-700 dark:border-red-800 dark:bg-red-950/30 dark:text-red-300",children:["The fix run failed: ",r.error??"unknown error"]});if(r.status==="cancelled")return e.jsx("p",{className:"mt-3 text-xs text-gray-500",children:"The fix run was cancelled."});if(r.status!=="succeeded")return e.jsx(e.Fragment,{});const c=!r.patch||r.filesChanged.length===0;return e.jsxs("div",{className:"mt-3",children:[r.summary&&e.jsx("div",{className:"prose prose-sm max-w-none dark:prose-invert",children:e.jsx(K,{children:r.summary})}),c?e.jsx("p",{className:"mt-2 text-xs text-gray-500",children:"The agent made no changes."}):e.jsxs(e.Fragment,{children:[e.jsxs("div",{className:"mb-1 mt-2 text-[11px] text-gray-500",children:[r.filesChanged.length," file",r.filesChanged.length===1?"":"s"," changed"]}),e.jsx("div",{className:"overflow-hidden rounded border border-gray-200 text-gray-800 dark:border-gray-800 dark:text-gray-200",children:e.jsx(Z,{files:i})}),e.jsx(Se,{pr:s,fix:r,headInfo:t,viewerCanPush:n})]})]})}function _({fix:s}){return e.jsxs("div",{className:"mb-2 rounded border border-green-200 bg-green-50 p-2 text-xs text-green-700 dark:border-green-800 dark:bg-green-950/30 dark:text-green-300",children:[e.jsxs("div",{children:["Pushed to ",e.jsx("span",{className:"font-mono",children:s.pushedBranch}),s.pushedPrUrl&&e.jsxs(e.Fragment,{children:[" · ",e.jsxs("a",{href:s.pushedPrUrl,target:"_blank",rel:"noreferrer",className:"underline",children:["PR #",s.pushedPrNumber]})]}),s.pushedAt&&e.jsxs(e.Fragment,{children:[" · ",I(s.pushedAt)]})]}),s.commitMessage&&e.jsx("div",{className:"mt-1 font-mono text-[11px] text-green-800 dark:text-green-300",children:s.commitMessage})]})}function Fe({history:s,currentFixId:r}){const t=s.filter(n=>n.pushedAt!=null&&n.id!==r);return t.length===0?null:e.jsxs("div",{className:"mt-4 border-t border-gray-200 pt-3 dark:border-gray-800",children:[e.jsx("div",{className:"mb-1.5 text-xs font-semibold uppercase tracking-wide text-gray-400",children:"Fixes pushed via Pierre"}),e.jsx("ul",{className:"space-y-2",children:t.map(n=>e.jsxs("li",{className:"text-xs",children:[e.jsx("div",{className:"font-mono text-gray-700 dark:text-gray-200",children:n.commitMessage??"(no commit message)"}),e.jsxs("div",{className:"mt-0.5 text-[11px] text-gray-400",children:[n.pushedBranch&&e.jsx("span",{className:"font-mono",children:n.pushedBranch}),n.pushedPrUrl&&e.jsxs(e.Fragment,{children:[" · ",e.jsxs("a",{href:n.pushedPrUrl,target:"_blank",rel:"noreferrer",className:"underline",children:["PR #",n.pushedPrNumber]})]}),n.pushedAt&&e.jsxs(e.Fragment,{children:[" · ",I(n.pushedAt)]})]})]},n.id))})]})}function Ae({preview:s,loading:r}){if(r)return e.jsx("p",{className:"text-[11px] text-gray-400",children:"Checking against the trunk…"});if(!s||!s.available||!s.trunkSha)return null;if(!s.clean){const t=s.conflictFiles;return e.jsxs("div",{className:"rounded border border-amber-300 bg-amber-50 p-2 text-[11px] text-amber-700 dark:border-amber-700 dark:bg-amber-950/30 dark:text-amber-300",children:["⚠ Conflicts with ",e.jsx("span",{className:"font-mono",children:s.trunk}),t.length>0?e.jsxs(e.Fragment,{children:[" in ",t.length," file",t.length===1?"":"s",e.jsxs("span",{className:"text-amber-600/90 dark:text-amber-400/90",children:[": ",t.slice(0,6).join(", "),t.length>6?"…":""]})]}):null,". Resolving before you push avoids a conflicted PR."]})}return s.behindBy>0?e.jsxs("p",{className:"text-[11px] text-gray-500",children:[e.jsx("span",{className:"font-mono",children:s.trunk})," is ",s.behindBy," ","commit",s.behindBy===1?"":"s"," ahead — the fix merges cleanly."]}):e.jsxs("p",{className:"text-[11px] text-green-600 dark:text-green-400",children:["Up to date with ",e.jsx("span",{className:"font-mono",children:s.trunk})," — no conflicts."]})}function Ee({resolved:s,target:r,pushing:t,onPush:n,onRedo:i,disabled:c}){const m=o.useMemo(()=>X(s.diff),[s.diff]);return e.jsxs("div",{className:"mt-2 space-y-2",children:[e.jsxs("div",{className:"rounded border border-green-200 bg-green-50 p-2 text-[11px] text-green-700 dark:border-green-800 dark:bg-green-950/30 dark:text-green-300",children:["Rebased onto ",e.jsx("span",{className:"font-mono",children:s.trunk}),".",s.resolvedConflicts?` Claude resolved conflicts in ${s.conflictFiles.length} file${s.conflictFiles.length===1?"":"s"}${s.conflictFiles.length?`: ${s.conflictFiles.join(", ")}`:""}.`:" No conflicts."," ","Review the result below, then push."]}),e.jsxs("div",{className:"text-[11px] text-gray-500",children:[s.filesChanged.length," file",s.filesChanged.length===1?"":"s"," in the rebased result"]}),e.jsx("div",{className:"overflow-hidden rounded border border-gray-200 text-gray-800 dark:border-gray-800 dark:text-gray-200",children:e.jsx(Z,{files:m})}),e.jsxs("div",{className:"flex flex-wrap items-center gap-2",children:[e.jsx("button",{type:"button",className:S,disabled:t||c,onClick:n,children:t?"Pushing…":r==="new"?"Push rebased + open PR":"Push rebased (force-with-lease)"}),e.jsx("button",{type:"button",className:j,disabled:t,onClick:i,children:"Re-rebase"})]})]})}function Se({pr:s,fix:r,headInfo:t,viewerCanPush:n}){var V,G;const i=ge(s.id),c=me(),m=be(),x=pe(),l=fe(),p=(t==null?void 0:t.canPushSameBranch)??!1,[h,d]=o.useState(p?"existing":"new"),[R,f]=o.useState((t==null?void 0:t.suggestedBranch)??""),[w,M]=o.useState(!0),[u,k]=o.useState("idle"),[F,N]=o.useState(null),A=o.useRef(!1),y=o.useRef(!1);o.useEffect(()=>{!A.current&&(t!=null&&t.suggestedBranch)&&(f(t.suggestedBranch),A.current=!0,t.canPushSameBranch||d("new"))},[t]);const g=r.pushedAt!=null,q=g&&r.pushedPrNumber==null,U=!g||q;o.useEffect(()=>{!y.current&&n&&U&&(y.current=!0,l.mutate(r.id))},[n,U,r.id]);const a=l.data??null,{status:v}=Y(s.id,r.id,"rebase",u==="rebasing"),{status:C}=Y(s.id,r.id,"push",u==="pushing");if(o.useEffect(()=>{u==="rebasing"&&v&&v.status!=="running"&&v.status!=="queued"&&(k("idle"),v.error&&N(v.error))},[u,v]),o.useEffect(()=>{u==="pushing"&&C&&C.status!=="running"&&C.status!=="queued"&&(k("idle"),C.error&&N(C.error))},[u,C]),!n)return g?e.jsx(_,{fix:r}):e.jsx("p",{className:"mt-2 text-xs text-gray-500",children:"You need write access to this repository to push this fix."});if(g&&!q)return e.jsx(_,{fix:r});const B=h==="new"&&R.trim().length===0,P=u!=="idle"||i.isPending||c.isPending,H=r.model,ee=g&&!!a&&a.available&&a.clean&&a.behindBy===0,T=E=>{N(null),i.mutate({fixId:r.id,body:{target:h,branch:h==="new"?R.trim():void 0,strategy:E,autoResolve:w,model:H}},{onSuccess:se=>{"pushedBranch"in se||k("pushing")}})},O=()=>{N(null),c.mutate({fixId:r.id,body:{autoResolve:w,model:H}},{onSuccess:()=>k("rebasing")})},b=u==="rebasing"?v:C,$=r.resolved;return e.jsxs("div",{className:"mt-3 rounded border border-gray-200 p-2 dark:border-gray-800",children:[g&&e.jsx(_,{fix:r}),e.jsx("div",{className:"mb-2 text-xs font-semibold text-gray-600 dark:text-gray-300",children:g?"Reconcile with the trunk":"Push this fix"}),!g&&r.commitMessage&&e.jsxs("div",{className:"mb-2 rounded bg-gray-50 p-2 text-[11px] dark:bg-gray-900",children:[e.jsx("span",{className:"uppercase tracking-wide text-gray-400",children:"Commit message"}),e.jsx("div",{className:"mt-0.5 font-mono text-gray-700 dark:text-gray-200",children:r.commitMessage})]}),e.jsxs("label",{className:`flex items-center gap-2 text-xs ${p?"":"opacity-40"}`,title:p?void 0:"The PR head is a fork you cannot push to — use a new branch",children:[e.jsx("input",{type:"radio",checked:h==="existing",disabled:!p||P,onChange:()=>d("existing")}),"Push to the PR branch",(t==null?void 0:t.headRef)&&e.jsxs("span",{className:"font-mono text-gray-400",children:["(",t.headRef,")"]})]}),e.jsxs("label",{className:"mt-1 flex items-center gap-2 text-xs",children:[e.jsx("input",{type:"radio",checked:h==="new",disabled:P,onChange:()=>d("new")}),"New branch + open a PR"]}),h==="new"&&e.jsx("input",{type:"text",className:"mt-1 w-full rounded border border-gray-300 bg-transparent px-2 py-1 font-mono text-xs dark:border-gray-700",value:R,disabled:P,onChange:E=>f(E.target.value),placeholder:"branch-name"}),e.jsxs("div",{className:"mt-2 flex flex-wrap items-center gap-2",children:[e.jsx("div",{className:"min-w-0 flex-1",children:e.jsx(Ae,{preview:a,loading:l.isPending})}),e.jsx("button",{type:"button",className:j,disabled:l.isPending||P,onClick:()=>l.mutate(r.id),title:"Re-check this fix against the current trunk",children:l.isPending?"Checking…":"Re-check trunk"}),l.isError&&!l.isPending&&e.jsx("span",{className:"text-[11px] text-red-500",children:"Couldn't check the trunk — try again."})]}),u!=="idle"?e.jsxs("div",{className:"mt-2",children:[e.jsx(Q,{active:!0,label:u==="rebasing"?"Rebasing & resolving":"Pushing",value:Ne(b),timeConstantSec:40}),e.jsxs("div",{className:"mt-1 flex items-center gap-2 text-[11px] text-gray-500",children:[e.jsx("span",{children:ke[((V=b==null?void 0:b.progress)==null?void 0:V.phase)??""]??"Working…"}),e.jsx("button",{type:"button",className:j,onClick:()=>u==="rebasing"?m.mutate(r.id):x.mutate(r.id),children:"Cancel"})]}),((G=b==null?void 0:b.progress)==null?void 0:G.recentActivity)&&b.progress.recentActivity.length>0&&e.jsx("pre",{className:"mt-1 max-h-32 overflow-auto rounded bg-gray-50 p-2 text-[11px] leading-relaxed text-gray-500 dark:bg-gray-900",children:b.progress.recentActivity.slice(-8).join(`
3
3
  `)})]}):$?e.jsx(Ee,{resolved:$,target:h,pushing:i.isPending,disabled:B,onPush:()=>T("rebase"),onRedo:O}):ee?e.jsxs("p",{className:"mt-2 text-[11px] text-gray-500",children:["No trunk changes to reconcile — the pushed fix is up to date with"," ",e.jsx("span",{className:"font-mono",children:a==null?void 0:a.trunk}),"."]}):e.jsxs("div",{className:"mt-2 space-y-2",children:[a&&a.available&&!a.clean&&e.jsxs("div",{className:"text-[11px] text-gray-500",children:["Recommended: rebase onto"," ",e.jsx("span",{className:"font-mono",children:a.trunk})," — Claude resolves the conflicts and you review the result before it pushes."]}),e.jsxs("label",{className:"flex items-center gap-2 text-[11px] text-gray-600 dark:text-gray-300",children:[e.jsx("input",{type:"checkbox",checked:w,onChange:E=>M(E.target.checked)}),"Let Claude resolve conflicts"]}),e.jsxs("div",{className:"flex flex-wrap items-center gap-2",children:[e.jsxs("button",{type:"button",className:a&&!a.clean?S:j,disabled:P||B,onClick:O,title:"Rebase the fix onto the trunk; Claude resolves conflicts and you review before a force-with-lease push",children:["Rebase onto ",(a==null?void 0:a.trunk)??"trunk"]}),!g&&e.jsxs("button",{type:"button",className:j,disabled:P||B,onClick:()=>T("merge"),title:"Merge the trunk into the fix branch (a merge commit; never force-pushes)",children:["Merge ",(a==null?void 0:a.trunk)??"trunk"," in"]}),!g&&e.jsxs("button",{type:"button",className:!a||a.clean?S:j,disabled:P||B,onClick:()=>T("plain"),title:"Push the fix as-is (never force-pushes). If it conflicts with the trunk, the PR will show as conflicted.",children:[h==="new"?"Push + open PR":"Push",a&&!a.clean?" anyway":""]})]}),g&&e.jsxs("p",{className:"text-[11px] text-gray-400",children:["Rebasing replays the fix onto"," ",e.jsx("span",{className:"font-mono",children:(a==null?void 0:a.trunk)??"the trunk"})," ","and force-pushes (with lease) onto the PR branch — the safe way to reconcile an already-pushed fix."]}),h==="existing"&&e.jsxs("p",{className:"text-[11px] text-gray-400",children:["Rebase force-pushes (with lease) onto"," ",e.jsx("span",{className:"font-mono",children:t==null?void 0:t.headRef}),"; merge and push do not."]})]}),(F||i.isError||c.isError)&&e.jsx("div",{className:"mt-2 text-[11px] text-red-500",children:F??D(i.error??c.error)})]})}function D(s){return s instanceof he||s instanceof Error?s.message:"Something went wrong"}export{De as AiFixTab};
@@ -1,4 +1,4 @@
1
- import{e as be,r as c,j as e}from"./tanstack-R-s9abdD.js";import{f as fe,s as Ce,x as Re,o as Pe,L as Se,K as Ee,B as Ae,z as Te,v as $e,t as Ie,C as _e,b as Fe,R as Le,h as ve,g as se,e as ke,H as Me,D as re,w as Be,u as He,M as ee}from"./index-DNiwLu1C.js";import"./markdown-BUNN1sk6.js";import"./highlight-C8vPehS1.js";import"./vis-C3BCkP5U.js";function Oe(t,s){return be({queryKey:["review-learnings",t],queryFn:()=>fe.reviewLearnings(t),enabled:s&&t!=null})}function De(t,s){return be({queryKey:["review-actions",t],queryFn:()=>fe.reviewActions(t),enabled:s&&t!=null})}function Ve({label:t,children:s}){return e.jsxs("div",{className:"flex gap-3 px-4 py-1.5 text-sm",children:[e.jsx("span",{className:"w-28 shrink-0 text-xs uppercase tracking-wide text-gray-400",children:t}),e.jsx("div",{className:"min-w-0 flex-1",children:s})]})}const Z=t=>t?t.slice(0,7):"—",z={skip:"Skipped",diff_only:"Quick",worktree:"Deep"},Ue=[{value:"auto",label:"Auto (router decides)"},{value:"diff_only",label:"Quick — diff only"},{value:"worktree",label:"Deep — full worktree"}],V=(t,s)=>`${t} ${s}${t===1?"":"s"}`;function Ge(t){const s=t.routeReason;if(!s)return;const n=`${V(s.changedFiles,"file")} · ${V(s.linesChanged,"line")} · ${V(s.dirsTouched,"dir")}`;return s.decidedBy==="user"?`You chose ${z[t.reviewMode??"worktree"]} for this run (${n}).`:s.trippedBy?`Auto chose Deep — ${n}; over the ${s.trippedBy} limit.`:`Auto chose ${z[t.reviewMode??"diff_only"]} — ${n}.`}const te={COMMENT:"Comment",REQUEST_CHANGES:"Request changes",APPROVE:"Approve"},qe={APPROVE:"bg-green-500/10 text-green-700 dark:text-green-400",REQUEST_CHANGES:"bg-red-500/10 text-red-700 dark:text-red-400",COMMENT:"bg-gray-500/10 text-gray-600 dark:text-gray-300"};function Ke({verdict:t}){return e.jsx("span",{className:`inline-flex items-center rounded px-1.5 py-0.5 text-xs font-medium ${qe[t]}`,children:te[t]})}const me={blocker:0,warning:1,nit:2,question:3,praise:4},We={blocker:"bg-red-500/10 text-red-700 dark:text-red-400",warning:"bg-orange-500/10 text-orange-700 dark:text-orange-400",nit:"bg-yellow-500/10 text-yellow-700 dark:text-yellow-500",question:"bg-blue-500/10 text-blue-700 dark:text-blue-400",praise:"bg-green-500/10 text-green-700 dark:text-green-400"};function Qe(t){const s=[t.model];return t.costUsd!=null&&s.push(`${ve(t.costUsd)} cr`),t.numTurns!=null&&s.push(`${t.numTurns} turns`),t.excludedFiles.length>0&&s.push(`${t.excludedFiles.length} noise files excluded`),s.join(" · ")}const Ye={cloning:"Cloning the worktree",fetching_diff:"Fetching the diff",deciding:"Deciding scope",reviewing:"Reviewing",persisting:"Saving findings"};function ze(t){var n,a,r;if(t==null)return null;if(t.status==="queued")return 5;const s=(n=t.progress)==null?void 0:n.phase;if(s==null)return 8;switch(s){case"fetching_diff":return 15;case"deciding":return 28;case"cloning":return 42;case"reviewing":{const o=((r=(a=t.progress)==null?void 0:a.recentActivity)==null?void 0:r.length)??0;return Math.min(90,55+o*3)}case"persisting":return 95;default:return null}}function Y(t){return t>=1e6?`${(t/1e6).toFixed(1)}M`:t>=1e3?`${(t/1e3).toFixed(1)}k`:String(t)}function Je({review:t}){const{inputTokens:s,outputTokens:n,cacheReadTokens:a,cacheCreationTokens:r}=t;if((s??0)+(n??0)+(a??0)+(r??0)<=0)return null;const d=[];return n!=null&&d.push({key:"out",label:"↓ out",value:n,title:"Output tokens generated (billed at the output rate — the priciest per token)"}),s!=null&&d.push({key:"in",label:"↑ in",value:s,title:"New (uncached) input tokens"}),a!=null&&d.push({key:"cr",label:"⟳ cache read",value:a,title:"Cached input tokens re-read each turn — billed at ~10% of the input rate, but the volume driver of a multi-turn run"}),r!=null&&d.push({key:"cw",label:"✎ cache write",value:r,title:"Tokens written to the prompt cache (billed at ~1.25× the input rate)"}),e.jsx("div",{className:"flex flex-wrap items-center gap-x-3 gap-y-0.5 font-mono text-xs text-gray-500 dark:text-gray-400",children:d.map(i=>e.jsxs("span",{title:i.title,children:[i.label," ",Y(i.value)]},i.key))})}function Xe({lines:t}){const s=c.useRef(null);return c.useEffect(()=>{const n=s.current;n!=null&&(n.scrollTop=n.scrollHeight)},[t]),t.length===0?null:e.jsx("div",{ref:s,className:"mt-2 max-h-32 overflow-y-auto rounded border border-gray-100 bg-gray-50 px-2 py-1.5 font-mono text-[11px] leading-snug text-gray-500 dark:border-gray-800 dark:bg-gray-900/60 dark:text-gray-400",children:t.map((n,a)=>e.jsx("div",{className:"whitespace-pre-wrap break-words",children:n===""?" ":n},a))})}function ge(t){return t.startsWith("@@")?"text-violet-500":t.startsWith("+")?"text-green-700 dark:text-green-400":t.startsWith("-")?"text-red-700 dark:text-red-400":"text-gray-500 dark:text-gray-400"}const Q="whitespace-nowrap rounded border border-blue-400 px-2 py-0.5 text-xs text-blue-600 hover:bg-blue-50 disabled:opacity-50 dark:border-blue-600 dark:text-blue-400 dark:hover:bg-blue-900/30",L="whitespace-nowrap rounded border border-gray-300 px-2 py-0.5 text-xs hover:border-gray-400 disabled:opacity-50 dark:border-gray-700 dark:hover:border-gray-500";function Ze({hunk:t}){const[s,n]=c.useState(!1),a=t.replace(/\n$/,"").split(`
1
+ import{e as be,r as c,j as e}from"./tanstack-R-s9abdD.js";import{f as fe,s as Ce,x as Re,o as Pe,L as Se,K as Ee,B as Ae,z as Te,v as $e,t as Ie,C as _e,b as Fe,R as Le,h as ve,g as se,e as ke,H as Me,D as re,w as Be,u as He,M as ee}from"./index-BplNgl1k.js";import"./markdown-BUNN1sk6.js";import"./highlight-C8vPehS1.js";import"./vis-C3BCkP5U.js";function Oe(t,s){return be({queryKey:["review-learnings",t],queryFn:()=>fe.reviewLearnings(t),enabled:s&&t!=null})}function De(t,s){return be({queryKey:["review-actions",t],queryFn:()=>fe.reviewActions(t),enabled:s&&t!=null})}function Ve({label:t,children:s}){return e.jsxs("div",{className:"flex gap-3 px-4 py-1.5 text-sm",children:[e.jsx("span",{className:"w-28 shrink-0 text-xs uppercase tracking-wide text-gray-400",children:t}),e.jsx("div",{className:"min-w-0 flex-1",children:s})]})}const Z=t=>t?t.slice(0,7):"—",z={skip:"Skipped",diff_only:"Quick",worktree:"Deep"},Ue=[{value:"auto",label:"Auto (router decides)"},{value:"diff_only",label:"Quick — diff only"},{value:"worktree",label:"Deep — full worktree"}],V=(t,s)=>`${t} ${s}${t===1?"":"s"}`;function Ge(t){const s=t.routeReason;if(!s)return;const n=`${V(s.changedFiles,"file")} · ${V(s.linesChanged,"line")} · ${V(s.dirsTouched,"dir")}`;return s.decidedBy==="user"?`You chose ${z[t.reviewMode??"worktree"]} for this run (${n}).`:s.trippedBy?`Auto chose Deep — ${n}; over the ${s.trippedBy} limit.`:`Auto chose ${z[t.reviewMode??"diff_only"]} — ${n}.`}const te={COMMENT:"Comment",REQUEST_CHANGES:"Request changes",APPROVE:"Approve"},qe={APPROVE:"bg-green-500/10 text-green-700 dark:text-green-400",REQUEST_CHANGES:"bg-red-500/10 text-red-700 dark:text-red-400",COMMENT:"bg-gray-500/10 text-gray-600 dark:text-gray-300"};function Ke({verdict:t}){return e.jsx("span",{className:`inline-flex items-center rounded px-1.5 py-0.5 text-xs font-medium ${qe[t]}`,children:te[t]})}const me={blocker:0,warning:1,nit:2,question:3,praise:4},We={blocker:"bg-red-500/10 text-red-700 dark:text-red-400",warning:"bg-orange-500/10 text-orange-700 dark:text-orange-400",nit:"bg-yellow-500/10 text-yellow-700 dark:text-yellow-500",question:"bg-blue-500/10 text-blue-700 dark:text-blue-400",praise:"bg-green-500/10 text-green-700 dark:text-green-400"};function Qe(t){const s=[t.model];return t.costUsd!=null&&s.push(`${ve(t.costUsd)} cr`),t.numTurns!=null&&s.push(`${t.numTurns} turns`),t.excludedFiles.length>0&&s.push(`${t.excludedFiles.length} noise files excluded`),s.join(" · ")}const Ye={cloning:"Cloning the worktree",fetching_diff:"Fetching the diff",deciding:"Deciding scope",reviewing:"Reviewing",persisting:"Saving findings"};function ze(t){var n,a,r;if(t==null)return null;if(t.status==="queued")return 5;const s=(n=t.progress)==null?void 0:n.phase;if(s==null)return 8;switch(s){case"fetching_diff":return 15;case"deciding":return 28;case"cloning":return 42;case"reviewing":{const o=((r=(a=t.progress)==null?void 0:a.recentActivity)==null?void 0:r.length)??0;return Math.min(90,55+o*3)}case"persisting":return 95;default:return null}}function Y(t){return t>=1e6?`${(t/1e6).toFixed(1)}M`:t>=1e3?`${(t/1e3).toFixed(1)}k`:String(t)}function Je({review:t}){const{inputTokens:s,outputTokens:n,cacheReadTokens:a,cacheCreationTokens:r}=t;if((s??0)+(n??0)+(a??0)+(r??0)<=0)return null;const d=[];return n!=null&&d.push({key:"out",label:"↓ out",value:n,title:"Output tokens generated (billed at the output rate — the priciest per token)"}),s!=null&&d.push({key:"in",label:"↑ in",value:s,title:"New (uncached) input tokens"}),a!=null&&d.push({key:"cr",label:"⟳ cache read",value:a,title:"Cached input tokens re-read each turn — billed at ~10% of the input rate, but the volume driver of a multi-turn run"}),r!=null&&d.push({key:"cw",label:"✎ cache write",value:r,title:"Tokens written to the prompt cache (billed at ~1.25× the input rate)"}),e.jsx("div",{className:"flex flex-wrap items-center gap-x-3 gap-y-0.5 font-mono text-xs text-gray-500 dark:text-gray-400",children:d.map(i=>e.jsxs("span",{title:i.title,children:[i.label," ",Y(i.value)]},i.key))})}function Xe({lines:t}){const s=c.useRef(null);return c.useEffect(()=>{const n=s.current;n!=null&&(n.scrollTop=n.scrollHeight)},[t]),t.length===0?null:e.jsx("div",{ref:s,className:"mt-2 max-h-32 overflow-y-auto rounded border border-gray-100 bg-gray-50 px-2 py-1.5 font-mono text-[11px] leading-snug text-gray-500 dark:border-gray-800 dark:bg-gray-900/60 dark:text-gray-400",children:t.map((n,a)=>e.jsx("div",{className:"whitespace-pre-wrap break-words",children:n===""?" ":n},a))})}function ge(t){return t.startsWith("@@")?"text-violet-500":t.startsWith("+")?"text-green-700 dark:text-green-400":t.startsWith("-")?"text-red-700 dark:text-red-400":"text-gray-500 dark:text-gray-400"}const Q="whitespace-nowrap rounded border border-blue-400 px-2 py-0.5 text-xs text-blue-600 hover:bg-blue-50 disabled:opacity-50 dark:border-blue-600 dark:text-blue-400 dark:hover:bg-blue-900/30",L="whitespace-nowrap rounded border border-gray-300 px-2 py-0.5 text-xs hover:border-gray-400 disabled:opacity-50 dark:border-gray-700 dark:hover:border-gray-500";function Ze({hunk:t}){const[s,n]=c.useState(!1),a=t.replace(/\n$/,"").split(`
2
2
  `),r=a.find(o=>o.startsWith("@@"))??a.at(-1)??"";return s?e.jsxs("div",{className:"mt-1 rounded bg-gray-50 dark:bg-gray-900/60",children:[e.jsx("pre",{className:"overflow-x-auto px-2 py-1.5 font-mono text-xs leading-snug",children:a.map((o,d)=>e.jsx("div",{className:ge(o),children:o===""?" ":o},d))}),e.jsx("button",{type:"button",onClick:()=>n(!1),className:"px-2 pb-1.5 text-[11px] text-gray-400 hover:text-gray-600 dark:hover:text-gray-300",children:"⌃ Hide code"})]}):e.jsxs("button",{type:"button",onClick:()=>n(!0),title:"Show the code hunk",className:"mt-1 flex w-full items-center gap-2 overflow-hidden rounded bg-gray-50 px-2 py-1.5 text-left font-mono text-xs dark:bg-gray-900/60",children:[e.jsx("span",{className:"shrink-0 text-gray-400",children:"▸"}),e.jsx("span",{className:`min-w-0 flex-1 truncate ${ge(r)}`,children:r===""?" ":r}),a.length>1&&e.jsxs("span",{className:"shrink-0 font-sans text-[10px] text-gray-400",children:[a.length," lines"]})]})}function et({prId:t,finding:s,editable:n,prUrl:a,repoFullName:r,headSha:o,posting:d,postError:i,onToggle:h,onReword:k,onPostComment:j}){const[g,E]=c.useState(!1),x=c.useRef(null);c.useEffect(()=>()=>{x.current!=null&&clearTimeout(x.current)},[]);const[p,N]=c.useState(!1),[S,m]=c.useState(s.editedBody??"");c.useEffect(()=>{p||m(s.editedBody??"")},[s.editedBody,p]);const b=s.editedBody!=null&&s.editedBody.trim()!=="",y=p&&S.trim()!==""&&S!==(s.editedBody??"")?S:null,A=y!=null||b,J=y??(b?s.editedBody:s.body),T=s.line!=null?`${s.path}:${s.line}`:s.path,f=s.postedAt!=null,v=s.githubCommentId!=null?s.postedCommentKind==="pr_comment"?`${a}#issuecomment-${s.githubCommentId}`:`${a}#discussion_r${s.githubCommentId}`:null,$=s.line!=null?`${a}/files#diff-${s.diffAnchorId}${s.side==="LEFT"?"L":"R"}${s.line}`:`${a}/files#diff-${s.diffAnchorId}`,U=o!=null&&s.line!=null&&s.side==="RIGHT"?`https://github.com/${r}/blob/${o}/${s.path.split("/").map(encodeURIComponent).join("/")}#L${s.line}`:null,w=v??$,C=v==null?U:null,G=n,q=()=>{let _=`${s.title}
3
3
 
4
4
  ${J}`;s.suggestion!=null&&s.suggestion!==""&&(_+=`